Class: Honeymaker::Clients::Hyperliquid
- Inherits:
-
Honeymaker::Client
- Object
- Honeymaker::Client
- Honeymaker::Clients::Hyperliquid
- Defined in:
- lib/honeymaker/clients/hyperliquid.rb
Constant Summary collapse
- URL =
"https://api.hyperliquid.xyz"- RATE_LIMITS =
{ default: 200, orders: 200 }.freeze
Constants inherited from Honeymaker::Client
Instance Attribute Summary
Attributes inherited from Honeymaker::Client
Instance Method Summary collapse
- #all_mids ⇒ Object
- #cancel(coin:, oid:) ⇒ Object
- #candles_snapshot(coin:, interval:, start_time:, end_time:) ⇒ Object
- #get_balances(user: nil) ⇒ Object
-
#initialize(api_key: nil, api_secret: nil, proxy: nil, logger: nil) ⇒ Hyperliquid
constructor
A new instance of Hyperliquid.
- #l2_book(coin:) ⇒ Object
- #open_orders(user:) ⇒ Object
-
#order(coin:, is_buy:, size:, limit_px:, order_type: { limit: { tif: "Gtc" } }) ⇒ Object
— Trading (requires hyperliquid-rb gem) —.
-
#order_status(user:, oid:) ⇒ Object
Hyperliquid’s orderStatus body is NESTED: { “status” => “order”|“unknownOid”, “order” => { “order” => { coin, side, limitPx, sz(remaining), origSz, oid, timestamp, … }, “status” => <real order status>, “statusTimestamp” => … } } The real status/sizes live under order/order — NOT the top level — and the body carries NO fills.
- #spot_balances(user: nil) ⇒ Object
- #spot_clearinghouse_state(user:) ⇒ Object
- #spot_meta ⇒ Object
- #spot_meta_and_asset_ctxs ⇒ Object
- #user_fills(user:, start_time: nil, end_time: nil) ⇒ Object
- #user_fills_by_time(user:, start_time:, end_time: nil) ⇒ Object
-
#user_funding(user:, start_time:, end_time: nil) ⇒ Object
— Futures —.
- #user_non_funding_ledger_updates(user:, start_time:, end_time: nil) ⇒ Object
Methods inherited from Honeymaker::Client
Constructor Details
#initialize(api_key: nil, api_secret: nil, proxy: nil, logger: nil) ⇒ Hyperliquid
Returns a new instance of Hyperliquid.
9 10 11 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 9 def initialize(api_key: nil, api_secret: nil, proxy: nil, logger: nil) super end |
Instance Method Details
#all_mids ⇒ Object
25 26 27 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 25 def all_mids post_info({ type: "allMids" }) end |
#cancel(coin:, oid:) ⇒ Object
138 139 140 141 142 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 138 def cancel(coin:, oid:) with_rescue do exchange_client.cancel(coin, oid) end end |
#candles_snapshot(coin:, interval:, start_time:, end_time:) ⇒ Object
38 39 40 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 38 def candles_snapshot(coin:, interval:, start_time:, end_time:) post_info({ type: "candleSnapshot", req: { coin: coin, interval: interval, startTime: start_time, endTime: end_time } }) end |
#get_balances(user: nil) ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 42 def get_balances(user: nil) user ||= @api_key result = spot_clearinghouse_state(user: user) return result if result.failure? balances = {} (result.data["balances"] || []).each do |balance| symbol = balance["coin"] total = BigDecimal((balance["total"] || "0").to_s) hold = BigDecimal((balance["hold"] || "0").to_s) free = total - hold next if free.zero? && hold.zero? balances[symbol] = { free: free, locked: hold } end Result::Success.new(balances) end |
#l2_book(coin:) ⇒ Object
34 35 36 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 34 def l2_book(coin:) post_info({ type: "l2Book", coin: coin }) end |
#open_orders(user:) ⇒ Object
113 114 115 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 113 def open_orders(user:) post_info({ type: "openOrders", user: user }) end |
#order(coin:, is_buy:, size:, limit_px:, order_type: { limit: { tif: "Gtc" } }) ⇒ Object
— Trading (requires hyperliquid-rb gem) —
132 133 134 135 136 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 132 def order(coin:, is_buy:, size:, limit_px:, order_type: { limit: { tif: "Gtc" } }) with_rescue do exchange_client.order(coin, is_buy: is_buy, sz: size, limit_px: limit_px, order_type: order_type) end end |
#order_status(user:, oid:) ⇒ Object
Hyperliquid’s orderStatus body is NESTED:
{ "status" => "order"|"unknownOid",
"order" => { "order" => { coin, side, limitPx, sz(remaining), origSz, oid, timestamp, ... },
"status" => <real order status>, "statusTimestamp" => ... } }
The real status/sizes live under order/order — NOT the top level — and the body carries NO fills. So the ordered amount is origSz, executed is origSz - remaining sz, and the exact cost comes from a bounded userFillsByTime (only fetched when something actually executed, since userFillsByTime is API weight 20 vs orderStatus’s weight 2).
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 68 def order_status(user:, oid:) result = post_info({ type: "orderStatus", user: user, oid: oid }) return result if result.failure? raw = result.data # A distinct not-found signal — aged-out orders are normal; the caller recovers fills / abandons. return Result::Failure.new("unknownOid", data: { not_found: true }) if raw["status"] == "unknownOid" wrapper = raw["order"] || {} order = wrapper["order"] || {} status_str = wrapper["status"] coin = order["coin"] side = order["side"] == "B" ? :buy : :sell limit_price = BigDecimal((order["limitPx"] || "0").to_s) ordered_size = BigDecimal((order["origSz"] || "0").to_s) remaining_size = BigDecimal((order["sz"] || "0").to_s) amount_exec = [ordered_size - remaining_size, BigDecimal("0")].max quote_amount_exec = BigDecimal("0") price = limit_price if amount_exec.positive? fills_result = order["timestamp"] ? user_fills_by_time(user: user, start_time: order["timestamp"]) : nil # A FAILED exact-cost lookup (timeout / rate-limit) is PROPAGATED so the consumer's typed-error # retry runs — never record an executed order with an estimated cost just because userFills # blipped (that would silently corrupt accounting and skip the retry). return fills_result if fills_result&.failure? matched = Array(fills_result&.data).select { |f| f["oid"].to_s == oid.to_s } matched_quote = matched.sum(BigDecimal("0")) { |f| BigDecimal(f["px"].to_s) * BigDecimal(f["sz"].to_s) } # userFills SUCCEEDED but has no matching fill (aged out of the window) → estimate from the # limit price so a filled order never reports quote_amount_exec 0. Only on success, never failure. quote_amount_exec = matched_quote.positive? ? matched_quote : (limit_price * amount_exec) price = quote_amount_exec / amount_exec end price = nil if price.nil? || price.zero? Result::Success.new({ order_id: "#{coin}-#{oid}", coin: coin, status: parse_order_status(status_str), side: side, order_type: :limit, price: price, amount: ordered_size, quote_amount: nil, amount_exec: amount_exec, quote_amount_exec: quote_amount_exec, raw: raw }) end |
#spot_balances(user: nil) ⇒ Object
29 30 31 32 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 29 def spot_balances(user: nil) user ||= @api_key spot_clearinghouse_state(user: user) end |
#spot_clearinghouse_state(user:) ⇒ Object
21 22 23 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 21 def spot_clearinghouse_state(user:) post_info({ type: "spotClearinghouseState", user: user }) end |
#spot_meta ⇒ Object
13 14 15 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 13 def post_info({ type: "spotMeta" }) end |
#spot_meta_and_asset_ctxs ⇒ Object
17 18 19 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 17 def post_info({ type: "spotMetaAndAssetCtxs" }) end |
#user_fills(user:, start_time: nil, end_time: nil) ⇒ Object
117 118 119 120 121 122 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 117 def user_fills(user:, start_time: nil, end_time: nil) body = { type: "userFills", user: user } body[:startTime] = start_time if start_time body[:endTime] = end_time if end_time post_info(body) end |
#user_fills_by_time(user:, start_time:, end_time: nil) ⇒ Object
124 125 126 127 128 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 124 def user_fills_by_time(user:, start_time:, end_time: nil) body = { type: "userFillsByTime", user: user, startTime: start_time } body[:endTime] = end_time if end_time post_info(body) end |
#user_funding(user:, start_time:, end_time: nil) ⇒ Object
— Futures —
146 147 148 149 150 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 146 def user_funding(user:, start_time:, end_time: nil) body = { type: "userFunding", user: user, startTime: start_time } body[:endTime] = end_time if end_time post_info(body) end |
#user_non_funding_ledger_updates(user:, start_time:, end_time: nil) ⇒ Object
152 153 154 155 156 |
# File 'lib/honeymaker/clients/hyperliquid.rb', line 152 def user_non_funding_ledger_updates(user:, start_time:, end_time: nil) body = { type: "userNonFundingLedgerUpdates", user: user, startTime: start_time } body[:endTime] = end_time if end_time post_info(body) end |