honeymaker 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/lib/honeymaker/clients/binance.rb +26 -0
  3. data/lib/honeymaker/clients/bingx.rb +4 -0
  4. data/lib/honeymaker/clients/bitmart.rb +8 -0
  5. data/lib/honeymaker/clients/bitrue.rb +11 -0
  6. data/lib/honeymaker/clients/gemini.rb +8 -0
  7. data/lib/honeymaker/clients/hyperliquid.rb +46 -0
  8. data/lib/honeymaker/clients/kucoin.rb +4 -0
  9. data/lib/honeymaker/exchange.rb +43 -0
  10. data/lib/honeymaker/exchanges/binance.rb +13 -0
  11. data/lib/honeymaker/exchanges/bingx.rb +14 -0
  12. data/lib/honeymaker/exchanges/bitget.rb +14 -0
  13. data/lib/honeymaker/exchanges/bitmart.rb +14 -0
  14. data/lib/honeymaker/exchanges/bitrue.rb +13 -0
  15. data/lib/honeymaker/exchanges/bitvavo.rb +13 -0
  16. data/lib/honeymaker/exchanges/bybit.rb +14 -0
  17. data/lib/honeymaker/exchanges/coinbase.rb +11 -0
  18. data/lib/honeymaker/exchanges/gemini.rb +11 -0
  19. data/lib/honeymaker/exchanges/hyperliquid.rb +14 -0
  20. data/lib/honeymaker/exchanges/kraken.rb +17 -0
  21. data/lib/honeymaker/exchanges/kucoin.rb +14 -0
  22. data/lib/honeymaker/exchanges/mexc.rb +13 -0
  23. data/lib/honeymaker/result.rb +12 -0
  24. data/lib/honeymaker/version.rb +1 -1
  25. data/test/fixtures/binance_book_ticker.json +7 -0
  26. data/test/fixtures/bingx_ticker.json +12 -0
  27. data/test/fixtures/bitget_tickers.json +14 -0
  28. data/test/fixtures/bitmart_ticker.json +12 -0
  29. data/test/fixtures/bitrue_book_ticker.json +7 -0
  30. data/test/fixtures/bitvavo_book_ticker.json +7 -0
  31. data/test/fixtures/bybit_tickers.json +19 -0
  32. data/test/fixtures/coinbase_product.json +7 -0
  33. data/test/fixtures/gemini_pubticker.json +10 -0
  34. data/test/fixtures/hyperliquid_all_mids.json +5 -0
  35. data/test/fixtures/kraken_ticker.json +16 -0
  36. data/test/fixtures/kucoin_orderbook_level1.json +10 -0
  37. data/test/fixtures/mexc_book_ticker.json +7 -0
  38. data/test/honeymaker/exchange_test.rb +97 -0
  39. data/test/honeymaker/exchanges/binance_test.rb +21 -0
  40. data/test/honeymaker/exchanges/binance_us_test.rb +11 -0
  41. data/test/honeymaker/exchanges/bingx_test.rb +11 -0
  42. data/test/honeymaker/exchanges/bitget_test.rb +11 -0
  43. data/test/honeymaker/exchanges/bitmart_test.rb +11 -0
  44. data/test/honeymaker/exchanges/bitrue_test.rb +11 -0
  45. data/test/honeymaker/exchanges/bitvavo_test.rb +11 -0
  46. data/test/honeymaker/exchanges/bybit_test.rb +11 -0
  47. data/test/honeymaker/exchanges/coinbase_test.rb +11 -0
  48. data/test/honeymaker/exchanges/gemini_test.rb +11 -0
  49. data/test/honeymaker/exchanges/hyperliquid_test.rb +11 -0
  50. data/test/honeymaker/exchanges/kraken_test.rb +20 -0
  51. data/test/honeymaker/exchanges/kucoin_test.rb +11 -0
  52. data/test/honeymaker/exchanges/mexc_test.rb +11 -0
  53. metadata +14 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a49e9be8c0e38711e061c2ea61373bbce84dfe3f779c5e6933fdc2c8185b942b
4
- data.tar.gz: c9dda3aa8c109c0db2bdcaf086f4b86fe95214cc7a6690d39b921e1182a08766
3
+ metadata.gz: b5111180bb98ceb8eae9aad595851e28a45a10d908f780f2e69325df702b29aa
4
+ data.tar.gz: e1aea0e0de8989a11f19a7a3fb5cab7d64a9b4944efb86bac69d7ecafb0e6096
5
5
  SHA512:
6
- metadata.gz: 7fe8a3cd0df1bea5355498e117887e854716d8050bdfe12009c9b1f0225771b1a18ad59030dfdd1418629df8db06805d79e38392ab99f3b300f93e4a87a52c3b
7
- data.tar.gz: 49e17edfcc5982629034cc755c315fc772188cc9a232102478eaf984522ed4df261bb55818bb0e62dcbe0b9f3a9a15c20759ef2199015da70d48a1b8098a22b3
6
+ metadata.gz: c0df0bf1c364389aac57e683a6ff74aa1b5282c9ad95b63379901d291d0a5c5f22e11878a87c9f231047d858062fce1038326be2c5c4472296f146fef111122f
7
+ data.tar.gz: d7f22d0e9f0d7af33eaa0d5e82633b6a01caf01d83246558af40befa365df99d59db9d33fd9cd4c8ddccc56ba70b6ec54a1188507bd6c75171873b726c464ac3
@@ -168,6 +168,32 @@ module Honeymaker
168
168
  end
169
169
  end
170
170
 
171
+ def test_new_order(symbol:, side:, type:, time_in_force: nil, quantity: nil, quote_order_qty: nil,
172
+ price: nil, new_client_order_id: nil, strategy_id: nil, strategy_type: nil,
173
+ stop_price: nil, trailing_delta: nil, iceberg_qty: nil, new_order_resp_type: nil,
174
+ self_trade_prevention_mode: nil, recv_window: 5000, compute_commission_rates: false)
175
+ with_rescue do
176
+ response = connection.post do |req|
177
+ req.url "/api/v3/order/test"
178
+ req.headers = headers
179
+ req.params = {
180
+ symbol: symbol, side: side, type: type,
181
+ timeInForce: time_in_force, quantity: quantity,
182
+ quoteOrderQty: quote_order_qty, price: price,
183
+ newClientOrderId: new_client_order_id,
184
+ strategyId: strategy_id, strategyType: strategy_type,
185
+ stopPrice: stop_price, trailingDelta: trailing_delta,
186
+ icebergQty: iceberg_qty, newOrderRespType: new_order_resp_type,
187
+ selfTradePreventionMode: self_trade_prevention_mode,
188
+ computeCommissionRates: compute_commission_rates,
189
+ recvWindow: recv_window, timestamp: timestamp_ms
190
+ }.compact
191
+ req.params[:signature] = sign_params(req.params)
192
+ end
193
+ response.body
194
+ end
195
+ end
196
+
171
197
  def cancel_order(symbol:, order_id: nil, orig_client_order_id: nil, new_client_order_id: nil,
172
198
  cancel_restrictions: nil, recv_window: 5000)
173
199
  with_rescue do
@@ -77,6 +77,10 @@ module Honeymaker
77
77
  })
78
78
  end
79
79
 
80
+ def get_all_coins_info
81
+ get_signed("/openApi/wallets/v1/capital/config/getall")
82
+ end
83
+
80
84
  def get_trade_fills(symbol: nil, order_id: nil, start_time: nil, end_time: nil, from_id: nil, limit: nil)
81
85
  get_signed("/openApi/spot/v1/trade/fills", {
82
86
  symbol: symbol, orderId: order_id,
@@ -17,6 +17,10 @@ module Honeymaker
17
17
  get_public("/spot/v1/symbols/details")
18
18
  end
19
19
 
20
+ def get_currencies
21
+ get_public("/account/v1/currencies")
22
+ end
23
+
20
24
  def get_ticker(symbol: nil)
21
25
  get_public("/spot/quotation/v3/ticker", { symbol: symbol })
22
26
  end
@@ -102,6 +106,10 @@ module Honeymaker
102
106
  })
103
107
  end
104
108
 
109
+ def get_withdraw_addresses
110
+ get_signed("/account/v1/withdraw/address/list")
111
+ end
112
+
105
113
  def withdraw(currency:, amount:, address:, address_memo: nil, destination: nil)
106
114
  post_signed("/account/v1/withdraw/apply", {
107
115
  currency: currency, amount: amount,
@@ -77,6 +77,17 @@ module Honeymaker
77
77
  })
78
78
  end
79
79
 
80
+ def get_all_coins_information(recv_window: 5000)
81
+ get_signed("/api/v1/capital/config/getall", { recvWindow: recv_window })
82
+ end
83
+
84
+ def withdraw(coin:, address:, amount:, network: nil, address_tag: nil, recv_window: 5000)
85
+ post_signed("/api/v1/capital/withdraw/apply", {
86
+ coin: coin, address: address, amount: amount,
87
+ network: network, addressTag: address_tag, recvWindow: recv_window
88
+ })
89
+ end
90
+
80
91
  def account_trade_list(symbol:, start_time: nil, end_time: nil, from_id: nil, limit: 500, recv_window: 5000)
81
92
  get_signed("/api/v1/myTrades", {
82
93
  symbol: symbol, startTime: start_time, endTime: end_time,
@@ -22,6 +22,14 @@ module Honeymaker
22
22
  get_public("/v2/candles/#{symbol}/#{time_frame}")
23
23
  end
24
24
 
25
+ def get_price_feed
26
+ get_public("/v1/pricefeed")
27
+ end
28
+
29
+ def get_approved_addresses(network:)
30
+ post_signed("/v1/approvedAddresses/account/#{network}")
31
+ end
32
+
25
33
  def get_raw_balances
26
34
  post_signed("/v1/balances")
27
35
  end
@@ -6,6 +6,10 @@ module Honeymaker
6
6
  URL = "https://api.hyperliquid.xyz"
7
7
  RATE_LIMITS = { default: 200, orders: 200 }.freeze
8
8
 
9
+ def initialize(api_key: nil, api_secret: nil, proxy: nil, logger: nil)
10
+ super
11
+ end
12
+
9
13
  def spot_meta
10
14
  post_info({ type: "spotMeta" })
11
15
  end
@@ -18,6 +22,23 @@ module Honeymaker
18
22
  post_info({ type: "spotClearinghouseState", user: user })
19
23
  end
20
24
 
25
+ def all_mids
26
+ post_info({ type: "allMids" })
27
+ end
28
+
29
+ def spot_balances(user: nil)
30
+ user ||= @api_key
31
+ spot_clearinghouse_state(user: user)
32
+ end
33
+
34
+ def l2_book(coin:)
35
+ post_info({ type: "l2Book", coin: coin })
36
+ end
37
+
38
+ def candles_snapshot(coin:, interval:, start_time:, end_time:)
39
+ post_info({ type: "candleSnapshot", req: { coin: coin, interval: interval, startTime: start_time, endTime: end_time } })
40
+ end
41
+
21
42
  def get_balances(user: nil)
22
43
  user ||= @api_key
23
44
  result = spot_clearinghouse_state(user: user)
@@ -82,6 +103,20 @@ module Honeymaker
82
103
  post_info(body)
83
104
  end
84
105
 
106
+ # --- Trading (requires hyperliquid gem) ---
107
+
108
+ def order(coin:, is_buy:, size:, limit_px:, order_type: { limit: { tif: "Gtc" } })
109
+ with_rescue do
110
+ exchange_client.order(coin: coin, is_buy: is_buy, size: size, limit_px: limit_px, order_type: order_type)
111
+ end
112
+ end
113
+
114
+ def cancel(coin:, oid:)
115
+ with_rescue do
116
+ exchange_client.cancel(coin: coin, oid: oid)
117
+ end
118
+ end
119
+
85
120
  # --- Futures ---
86
121
 
87
122
  def user_funding(user:, start_time:, end_time: nil)
@@ -118,6 +153,17 @@ module Honeymaker
118
153
  validate_trading_credentials
119
154
  end
120
155
 
156
+ def exchange_client
157
+ raise Error, "Trading requires api_secret (agent key)" unless @api_secret && !@api_secret.empty?
158
+ @exchange ||= begin
159
+ require "hyperliquid"
160
+ sdk = ::Hyperliquid.new(private_key: @api_secret)
161
+ sdk.exchange
162
+ rescue LoadError
163
+ raise Error, "Add 'hyperliquid' to your Gemfile to use Hyperliquid trading"
164
+ end
165
+ end
166
+
121
167
  def post_info(body)
122
168
  with_rescue do
123
169
  response = connection.post do |req|
@@ -82,6 +82,10 @@ module Honeymaker
82
82
  delete_signed("/api/v1/orders/#{order_id}")
83
83
  end
84
84
 
85
+ def get_orderbook(symbol:, limit: 20)
86
+ get_public("/api/v1/market/orderbook/level2_#{limit}", { symbol: symbol })
87
+ end
88
+
85
89
  def get_currencies
86
90
  get_public("/api/v3/currencies")
87
91
  end
@@ -14,6 +14,49 @@ module Honeymaker
14
14
  raise NotImplementedError, "#{self.class} must implement #get_tickers_info"
15
15
  end
16
16
 
17
+ def get_bid_ask(symbol)
18
+ raise NotImplementedError, "#{self.class} must implement #get_bid_ask"
19
+ end
20
+
21
+ def get_price(symbol)
22
+ result = get_bid_ask(symbol)
23
+ return result if result.failure?
24
+
25
+ Result::Success.new((result.data[:bid] + result.data[:ask]) / 2)
26
+ end
27
+
28
+ def tickers_info
29
+ if @tickers_info_cache && @tickers_info_expires_at && @tickers_info_expires_at > Time.now
30
+ return Result::Success.new(@tickers_info_cache)
31
+ end
32
+
33
+ result = get_tickers_info
34
+ if result.success?
35
+ @tickers_info_cache = result.data
36
+ @tickers_info_expires_at = Time.now + cache_ttl
37
+ end
38
+ result
39
+ end
40
+
41
+ def find_ticker(symbol)
42
+ result = tickers_info
43
+ return result if result.failure?
44
+
45
+ ticker = result.data.find { |t| t[:ticker] == symbol }
46
+ ticker ? Result::Success.new(ticker) : Result::Failure.new("Unknown symbol: #{symbol}")
47
+ end
48
+
49
+ def symbols
50
+ result = tickers_info
51
+ return result if result.failure?
52
+
53
+ Result::Success.new(result.data.map { |t| { base: t[:base], quote: t[:quote] } })
54
+ end
55
+
56
+ def cache_ttl
57
+ 3600
58
+ end
59
+
17
60
  private
18
61
 
19
62
  def with_rescue
@@ -31,6 +31,19 @@ module Honeymaker
31
31
  end
32
32
  end
33
33
 
34
+ def get_bid_ask(symbol)
35
+ with_rescue do
36
+ response = connection.get("/api/v3/ticker/bookTicker") do |req|
37
+ req.params = { symbol: symbol }
38
+ end
39
+
40
+ {
41
+ bid: BigDecimal(response.body["bidPrice"]),
42
+ ask: BigDecimal(response.body["askPrice"])
43
+ }
44
+ end
45
+ end
46
+
34
47
  private
35
48
 
36
49
  def connection
@@ -31,6 +31,20 @@ module Honeymaker
31
31
  end
32
32
  end
33
33
 
34
+ def get_bid_ask(symbol)
35
+ with_rescue do
36
+ response = connection.get("/openApi/spot/v2/market/ticker") do |req|
37
+ req.params = { symbol: symbol }
38
+ end
39
+
40
+ data = response.body["data"].first
41
+ {
42
+ bid: BigDecimal(data["bestBidPrice"]),
43
+ ask: BigDecimal(data["bestAskPrice"])
44
+ }
45
+ end
46
+ end
47
+
34
48
  private
35
49
 
36
50
  def connection
@@ -27,6 +27,20 @@ module Honeymaker
27
27
  end
28
28
  end
29
29
 
30
+ def get_bid_ask(symbol)
31
+ with_rescue do
32
+ response = connection.get("/api/v2/spot/market/tickers") do |req|
33
+ req.params = { symbol: symbol }
34
+ end
35
+
36
+ ticker = response.body["data"].first
37
+ {
38
+ bid: BigDecimal(ticker["bestBid"]),
39
+ ask: BigDecimal(ticker["bestAsk"])
40
+ }
41
+ end
42
+ end
43
+
30
44
  private
31
45
 
32
46
  def connection
@@ -27,6 +27,20 @@ module Honeymaker
27
27
  end
28
28
  end
29
29
 
30
+ def get_bid_ask(symbol)
31
+ with_rescue do
32
+ response = connection.get("/spot/quotation/v3/ticker") do |req|
33
+ req.params = { symbol: symbol }
34
+ end
35
+
36
+ data = response.body["data"]
37
+ {
38
+ bid: BigDecimal(data["best_bid"]),
39
+ ask: BigDecimal(data["best_ask"])
40
+ }
41
+ end
42
+ end
43
+
30
44
  private
31
45
 
32
46
  def connection
@@ -29,6 +29,19 @@ module Honeymaker
29
29
  end
30
30
  end
31
31
 
32
+ def get_bid_ask(symbol)
33
+ with_rescue do
34
+ response = connection.get("/api/v1/ticker/bookTicker") do |req|
35
+ req.params = { symbol: symbol }
36
+ end
37
+
38
+ {
39
+ bid: BigDecimal(response.body["bidPrice"]),
40
+ ask: BigDecimal(response.body["askPrice"])
41
+ }
42
+ end
43
+ end
44
+
32
45
  private
33
46
 
34
47
  def connection
@@ -30,6 +30,19 @@ module Honeymaker
30
30
  end
31
31
  end
32
32
 
33
+ def get_bid_ask(symbol)
34
+ with_rescue do
35
+ response = connection.get("/v2/ticker/book") do |req|
36
+ req.params = { market: symbol }
37
+ end
38
+
39
+ {
40
+ bid: BigDecimal(response.body["bid"]),
41
+ ask: BigDecimal(response.body["ask"])
42
+ }
43
+ end
44
+ end
45
+
33
46
  private
34
47
 
35
48
  def connection
@@ -32,6 +32,20 @@ module Honeymaker
32
32
  end
33
33
  end
34
34
 
35
+ def get_bid_ask(symbol)
36
+ with_rescue do
37
+ response = connection.get("/v5/market/tickers") do |req|
38
+ req.params = { category: "spot", symbol: symbol }
39
+ end
40
+
41
+ ticker = response.body["result"]["list"].first
42
+ {
43
+ bid: BigDecimal(ticker["bid1Price"]),
44
+ ask: BigDecimal(ticker["ask1Price"])
45
+ }
46
+ end
47
+ end
48
+
35
49
  private
36
50
 
37
51
  def connection
@@ -37,6 +37,17 @@ module Honeymaker
37
37
  end
38
38
  end
39
39
 
40
+ def get_bid_ask(symbol)
41
+ with_rescue do
42
+ response = connection.get("/api/v3/brokerage/market/products/#{symbol}")
43
+
44
+ {
45
+ bid: BigDecimal(response.body["bid"]),
46
+ ask: BigDecimal(response.body["ask"])
47
+ }
48
+ end
49
+ end
50
+
40
51
  private
41
52
 
42
53
  def connection
@@ -33,6 +33,17 @@ module Honeymaker
33
33
  end
34
34
  end
35
35
 
36
+ def get_bid_ask(symbol)
37
+ with_rescue do
38
+ response = connection.get("/v1/pubticker/#{symbol.downcase}")
39
+
40
+ {
41
+ bid: BigDecimal(response.body["bid"]),
42
+ ask: BigDecimal(response.body["ask"])
43
+ }
44
+ end
45
+ end
46
+
36
47
  private
37
48
 
38
49
  def connection
@@ -37,6 +37,20 @@ module Honeymaker
37
37
  end
38
38
  end
39
39
 
40
+ def get_bid_ask(symbol)
41
+ with_rescue do
42
+ response = connection.post("/info") do |req|
43
+ req.body = { type: "allMids" }.to_json
44
+ end
45
+
46
+ price = BigDecimal(response.body[symbol])
47
+ {
48
+ bid: price,
49
+ ask: price
50
+ }
51
+ end
52
+ end
53
+
40
54
  private
41
55
 
42
56
  def connection
@@ -58,6 +58,23 @@ module Honeymaker
58
58
  end
59
59
  end
60
60
 
61
+ def get_bid_ask(symbol)
62
+ with_rescue do
63
+ response = connection.get("/0/public/Ticker") do |req|
64
+ req.params = { pair: symbol }
65
+ end
66
+
67
+ error = response.body["error"]
68
+ raise StandardError, error.first if error.is_a?(Array) && error.any?
69
+
70
+ _key, data = response.body["result"].first
71
+ {
72
+ bid: BigDecimal(data["b"][0]),
73
+ ask: BigDecimal(data["a"][0])
74
+ }
75
+ end
76
+ end
77
+
61
78
  private
62
79
 
63
80
  def connection
@@ -27,6 +27,20 @@ module Honeymaker
27
27
  end
28
28
  end
29
29
 
30
+ def get_bid_ask(symbol)
31
+ with_rescue do
32
+ response = connection.get("/api/v1/market/orderbook/level1") do |req|
33
+ req.params = { symbol: symbol }
34
+ end
35
+
36
+ data = response.body["data"]
37
+ {
38
+ bid: BigDecimal(data["bestBid"]),
39
+ ask: BigDecimal(data["bestAsk"])
40
+ }
41
+ end
42
+ end
43
+
30
44
  private
31
45
 
32
46
  def connection
@@ -29,6 +29,19 @@ module Honeymaker
29
29
  end
30
30
  end
31
31
 
32
+ def get_bid_ask(symbol)
33
+ with_rescue do
34
+ response = connection.get("/api/v3/ticker/bookTicker") do |req|
35
+ req.params = { symbol: symbol }
36
+ end
37
+
38
+ {
39
+ bid: BigDecimal(response.body["bidPrice"]),
40
+ ask: BigDecimal(response.body["askPrice"])
41
+ }
42
+ end
43
+ end
44
+
32
45
  private
33
46
 
34
47
  def connection
@@ -17,6 +17,18 @@ module Honeymaker
17
17
  !success?
18
18
  end
19
19
 
20
+ def or(other)
21
+ return data if success?
22
+
23
+ other
24
+ end
25
+
26
+ def ==(other)
27
+ return false unless other.is_a?(Result)
28
+
29
+ data == other.data && errors == other.errors
30
+ end
31
+
20
32
  class Success < Result
21
33
  def initialize(data = nil)
22
34
  @data = data
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Honeymaker
4
- VERSION = "0.5.1"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -0,0 +1,7 @@
1
+ {
2
+ "symbol": "BTCUSDT",
3
+ "bidPrice": "67123.45000000",
4
+ "bidQty": "1.23456700",
5
+ "askPrice": "67125.67000000",
6
+ "askQty": "0.98765400"
7
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "code": 0,
3
+ "data": [
4
+ {
5
+ "symbol": "BTC-USDT",
6
+ "bestBidPrice": "67123.45",
7
+ "bestBidQty": "1.234567",
8
+ "bestAskPrice": "67125.67",
9
+ "bestAskQty": "0.987654"
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "code": "00000",
3
+ "msg": "success",
4
+ "data": [
5
+ {
6
+ "symbol": "BTCUSDT",
7
+ "bestBid": "67123.45",
8
+ "bestAsk": "67125.67",
9
+ "lastPr": "67124.56",
10
+ "high24h": "67500.00",
11
+ "low24h": "66800.00"
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "code": 1000,
3
+ "message": "OK",
4
+ "data": {
5
+ "symbol": "BTC_USDT",
6
+ "last": "67124.56",
7
+ "best_bid": "67123.45",
8
+ "best_ask": "67125.67",
9
+ "high_24h": "67500.00",
10
+ "low_24h": "66800.00"
11
+ }
12
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "symbol": "BTCUSDT",
3
+ "bidPrice": "67123.45",
4
+ "bidQty": "1.234567",
5
+ "askPrice": "67125.67",
6
+ "askQty": "0.987654"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "market": "BTC-EUR",
3
+ "bid": "67123.45",
4
+ "bidSize": "1.234567",
5
+ "ask": "67125.67",
6
+ "askSize": "0.987654"
7
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "retCode": 0,
3
+ "retMsg": "OK",
4
+ "result": {
5
+ "category": "spot",
6
+ "list": [
7
+ {
8
+ "symbol": "BTCUSDT",
9
+ "bid1Price": "67123.45",
10
+ "bid1Size": "1.234567",
11
+ "ask1Price": "67125.67",
12
+ "ask1Size": "0.987654",
13
+ "lastPrice": "67124.56",
14
+ "highPrice24h": "67500.00",
15
+ "lowPrice24h": "66800.00"
16
+ }
17
+ ]
18
+ }
19
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "product_id": "BTC-USD",
3
+ "price": "67124.56",
4
+ "bid": "67123.45",
5
+ "ask": "67125.67",
6
+ "volume_24h": "12345.678"
7
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "bid": "67123.45",
3
+ "ask": "67125.67",
4
+ "last": "67124.56",
5
+ "volume": {
6
+ "BTC": "1234.5678",
7
+ "USD": "82888888.88",
8
+ "timestamp": 1700000000000
9
+ }
10
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "PURR/USDC": "0.001234",
3
+ "BTC/USDC": "67124.56",
4
+ "ETH/USDC": "3456.78"
5
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "error": [],
3
+ "result": {
4
+ "XBTUSDT": {
5
+ "a": ["67125.67000", "1", "1.000"],
6
+ "b": ["67123.45000", "2", "2.000"],
7
+ "c": ["67124.50000", "0.00100000"],
8
+ "v": ["1234.56789012", "5678.90123456"],
9
+ "p": ["67100.00000", "67050.00000"],
10
+ "t": [12345, 67890],
11
+ "l": ["66800.00000", "66500.00000"],
12
+ "h": ["67500.00000", "67800.00000"],
13
+ "o": "67000.00000"
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "code": "200000",
3
+ "data": {
4
+ "bestBid": "67123.45",
5
+ "bestBidSize": "1.234567",
6
+ "bestAsk": "67125.67",
7
+ "bestAskSize": "0.987654",
8
+ "time": 1700000000000
9
+ }
10
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "symbol": "BTCUSDT",
3
+ "bidPrice": "67123.45",
4
+ "bidQty": "1.234567",
5
+ "askPrice": "67125.67",
6
+ "askQty": "0.987654"
7
+ }
@@ -28,4 +28,101 @@ class Honeymaker::ExchangeTest < Minitest::Test
28
28
  assert result.success?
29
29
  assert_equal [1, 2, 3], result.data
30
30
  end
31
+
32
+ def test_get_bid_ask_raises_not_implemented
33
+ exchange = Honeymaker::Exchange.new
34
+ assert_raises(NotImplementedError) { exchange.get_bid_ask("BTCUSDT") }
35
+ end
36
+
37
+ def test_get_price_returns_midpoint
38
+ exchange = Honeymaker::Exchange.new
39
+ bid_ask = { bid: BigDecimal("100"), ask: BigDecimal("102") }
40
+ exchange.define_singleton_method(:get_bid_ask) { |_| Honeymaker::Result::Success.new(bid_ask) }
41
+
42
+ result = exchange.get_price("BTCUSDT")
43
+ assert result.success?
44
+ assert_equal BigDecimal("101"), result.data
45
+ end
46
+
47
+ def test_get_price_propagates_failure
48
+ exchange = Honeymaker::Exchange.new
49
+ exchange.define_singleton_method(:get_bid_ask) { |_| Honeymaker::Result::Failure.new("error") }
50
+
51
+ result = exchange.get_price("BTCUSDT")
52
+ assert result.failure?
53
+ end
54
+
55
+ def test_tickers_info_caches_result
56
+ exchange = Honeymaker::Exchange.new
57
+ tickers = [{ ticker: "BTCUSDT", base: "BTC", quote: "USDT" }]
58
+ call_count = 0
59
+ exchange.define_singleton_method(:get_tickers_info) do
60
+ call_count += 1
61
+ Honeymaker::Result::Success.new(tickers)
62
+ end
63
+
64
+ result1 = exchange.tickers_info
65
+ result2 = exchange.tickers_info
66
+ assert result1.success?
67
+ assert result2.success?
68
+ assert_equal 1, call_count
69
+ end
70
+
71
+ def test_tickers_info_does_not_cache_failure
72
+ exchange = Honeymaker::Exchange.new
73
+ call_count = 0
74
+ exchange.define_singleton_method(:get_tickers_info) do
75
+ call_count += 1
76
+ Honeymaker::Result::Failure.new("error")
77
+ end
78
+
79
+ exchange.tickers_info
80
+ exchange.tickers_info
81
+ assert_equal 2, call_count
82
+ end
83
+
84
+ def test_find_ticker_returns_matching_ticker
85
+ exchange = Honeymaker::Exchange.new
86
+ tickers = [
87
+ { ticker: "BTCUSDT", base: "BTC", quote: "USDT" },
88
+ { ticker: "ETHUSDT", base: "ETH", quote: "USDT" }
89
+ ]
90
+ exchange.define_singleton_method(:get_tickers_info) do
91
+ Honeymaker::Result::Success.new(tickers)
92
+ end
93
+
94
+ result = exchange.find_ticker("ETHUSDT")
95
+ assert result.success?
96
+ assert_equal "ETH", result.data[:base]
97
+ end
98
+
99
+ def test_find_ticker_returns_failure_for_unknown_symbol
100
+ exchange = Honeymaker::Exchange.new
101
+ exchange.define_singleton_method(:get_tickers_info) do
102
+ Honeymaker::Result::Success.new([{ ticker: "BTCUSDT" }])
103
+ end
104
+
105
+ result = exchange.find_ticker("NOPE")
106
+ assert result.failure?
107
+ end
108
+
109
+ def test_symbols_returns_base_quote_pairs
110
+ exchange = Honeymaker::Exchange.new
111
+ tickers = [
112
+ { ticker: "BTCUSDT", base: "BTC", quote: "USDT" },
113
+ { ticker: "ETHUSDT", base: "ETH", quote: "USDT" }
114
+ ]
115
+ exchange.define_singleton_method(:get_tickers_info) do
116
+ Honeymaker::Result::Success.new(tickers)
117
+ end
118
+
119
+ result = exchange.symbols
120
+ assert result.success?
121
+ assert_equal [{ base: "BTC", quote: "USDT" }, { base: "ETH", quote: "USDT" }], result.data
122
+ end
123
+
124
+ def test_cache_ttl_defaults_to_3600
125
+ exchange = Honeymaker::Exchange.new
126
+ assert_equal 3600, exchange.cache_ttl
127
+ end
31
128
  end
@@ -38,6 +38,27 @@ class Honeymaker::Exchanges::BinanceTest < Minitest::Test
38
38
  assert result.failure?
39
39
  end
40
40
 
41
+ def test_get_bid_ask_parses_response
42
+ body = load_fixture("binance_book_ticker.json")
43
+ stub_connection(body)
44
+
45
+ result = @exchange.get_bid_ask("BTCUSDT")
46
+
47
+ assert result.success?
48
+ assert_equal BigDecimal("67123.45"), result.data[:bid]
49
+ assert_equal BigDecimal("67125.67"), result.data[:ask]
50
+ end
51
+
52
+ def test_get_bid_ask_handles_api_error
53
+ connection = stub
54
+ connection.stubs(:get).raises(Faraday::ServerError.new("500", { status: 500, body: "Internal Server Error" }))
55
+ @exchange.instance_variable_set(:@connection, connection)
56
+
57
+ result = @exchange.get_bid_ask("BTCUSDT")
58
+
59
+ assert result.failure?
60
+ end
61
+
41
62
  private
42
63
 
43
64
  def stub_connection(body)
@@ -29,6 +29,17 @@ class Honeymaker::Exchanges::BinanceUsTest < Minitest::Test
29
29
  assert_equal "BTC", ticker[:base]
30
30
  end
31
31
 
32
+ def test_get_bid_ask_parses_response
33
+ body = load_fixture("binance_book_ticker.json")
34
+ stub_connection(body)
35
+
36
+ result = @exchange.get_bid_ask("BTCUSDT")
37
+
38
+ assert result.success?
39
+ assert_equal BigDecimal("67123.45"), result.data[:bid]
40
+ assert_equal BigDecimal("67125.67"), result.data[:ask]
41
+ end
42
+
32
43
  private
33
44
 
34
45
  def stub_connection(body)
@@ -42,6 +42,17 @@ class Honeymaker::Exchanges::BingXTest < Minitest::Test
42
42
  refute_includes tickers, "INVALID"
43
43
  end
44
44
 
45
+ def test_get_bid_ask_parses_response
46
+ body = load_fixture("bingx_ticker.json")
47
+ stub_connection(body)
48
+
49
+ result = @exchange.get_bid_ask("BTC-USDT")
50
+
51
+ assert result.success?
52
+ assert_equal BigDecimal("67123.45"), result.data[:bid]
53
+ assert_equal BigDecimal("67125.67"), result.data[:ask]
54
+ end
55
+
45
56
  private
46
57
 
47
58
  def stub_connection(body)
@@ -41,6 +41,17 @@ class Honeymaker::Exchanges::BitgetTest < Minitest::Test
41
41
  refute eth[:available]
42
42
  end
43
43
 
44
+ def test_get_bid_ask_parses_response
45
+ body = load_fixture("bitget_tickers.json")
46
+ stub_connection(body)
47
+
48
+ result = @exchange.get_bid_ask("BTCUSDT")
49
+
50
+ assert result.success?
51
+ assert_equal BigDecimal("67123.45"), result.data[:bid]
52
+ assert_equal BigDecimal("67125.67"), result.data[:ask]
53
+ end
54
+
44
55
  private
45
56
 
46
57
  def stub_connection(body)
@@ -41,6 +41,17 @@ class Honeymaker::Exchanges::BitMartTest < Minitest::Test
41
41
  refute eth[:available]
42
42
  end
43
43
 
44
+ def test_get_bid_ask_parses_response
45
+ body = load_fixture("bitmart_ticker.json")
46
+ stub_connection(body)
47
+
48
+ result = @exchange.get_bid_ask("BTC_USDT")
49
+
50
+ assert result.success?
51
+ assert_equal BigDecimal("67123.45"), result.data[:bid]
52
+ assert_equal BigDecimal("67125.67"), result.data[:ask]
53
+ end
54
+
44
55
  private
45
56
 
46
57
  def stub_connection(body)
@@ -42,6 +42,17 @@ class Honeymaker::Exchanges::BitrueTest < Minitest::Test
42
42
  assert_equal 2, eth[:price_decimals]
43
43
  end
44
44
 
45
+ def test_get_bid_ask_parses_response
46
+ body = load_fixture("bitrue_book_ticker.json")
47
+ stub_connection(body)
48
+
49
+ result = @exchange.get_bid_ask("BTCUSDT")
50
+
51
+ assert result.success?
52
+ assert_equal BigDecimal("67123.45"), result.data[:bid]
53
+ assert_equal BigDecimal("67125.67"), result.data[:ask]
54
+ end
55
+
45
56
  private
46
57
 
47
58
  def stub_connection(body)
@@ -41,6 +41,17 @@ class Honeymaker::Exchanges::BitvavoTest < Minitest::Test
41
41
  refute eth[:available]
42
42
  end
43
43
 
44
+ def test_get_bid_ask_parses_response
45
+ body = load_fixture("bitvavo_book_ticker.json")
46
+ stub_connection(body)
47
+
48
+ result = @exchange.get_bid_ask("BTC-EUR")
49
+
50
+ assert result.success?
51
+ assert_equal BigDecimal("67123.45"), result.data[:bid]
52
+ assert_equal BigDecimal("67125.67"), result.data[:ask]
53
+ end
54
+
44
55
  private
45
56
 
46
57
  def stub_connection(body)
@@ -32,6 +32,17 @@ class Honeymaker::Exchanges::BybitTest < Minitest::Test
32
32
  assert ticker[:available]
33
33
  end
34
34
 
35
+ def test_get_bid_ask_parses_response
36
+ body = load_fixture("bybit_tickers.json")
37
+ stub_connection(body)
38
+
39
+ result = @exchange.get_bid_ask("BTCUSDT")
40
+
41
+ assert result.success?
42
+ assert_equal BigDecimal("67123.45"), result.data[:bid]
43
+ assert_equal BigDecimal("67125.67"), result.data[:ask]
44
+ end
45
+
35
46
  private
36
47
 
37
48
  def stub_connection(body)
@@ -41,6 +41,17 @@ class Honeymaker::Exchanges::CoinbaseTest < Minitest::Test
41
41
  refute_includes bases, "RENDER"
42
42
  end
43
43
 
44
+ def test_get_bid_ask_parses_response
45
+ body = load_fixture("coinbase_product.json")
46
+ stub_connection(body)
47
+
48
+ result = @exchange.get_bid_ask("BTC-USD")
49
+
50
+ assert result.success?
51
+ assert_equal BigDecimal("67123.45"), result.data[:bid]
52
+ assert_equal BigDecimal("67125.67"), result.data[:ask]
53
+ end
54
+
44
55
  private
45
56
 
46
57
  def stub_connection(body)
@@ -37,6 +37,17 @@ class Honeymaker::Exchanges::GeminiTest < Minitest::Test
37
37
  assert ticker[:available]
38
38
  end
39
39
 
40
+ def test_get_bid_ask_parses_response
41
+ body = load_fixture("gemini_pubticker.json")
42
+ stub_connection(body)
43
+
44
+ result = @exchange.get_bid_ask("BTCUSD")
45
+
46
+ assert result.success?
47
+ assert_equal BigDecimal("67123.45"), result.data[:bid]
48
+ assert_equal BigDecimal("67125.67"), result.data[:ask]
49
+ end
50
+
40
51
  private
41
52
 
42
53
  def stub_connection(body)
@@ -41,6 +41,17 @@ class Honeymaker::Exchanges::HyperliquidTest < Minitest::Test
41
41
  assert_equal 1, result.data.size
42
42
  end
43
43
 
44
+ def test_get_bid_ask_parses_response
45
+ body = load_fixture("hyperliquid_all_mids.json")
46
+ stub_connection(body)
47
+
48
+ result = @exchange.get_bid_ask("BTC/USDC")
49
+
50
+ assert result.success?
51
+ assert_equal BigDecimal("67124.56"), result.data[:bid]
52
+ assert_equal BigDecimal("67124.56"), result.data[:ask]
53
+ end
54
+
44
55
  private
45
56
 
46
57
  def stub_connection(body)
@@ -60,6 +60,26 @@ class Honeymaker::Exchanges::KrakenTest < Minitest::Test
60
60
  assert_includes result.errors, "EGeneral:Internal error"
61
61
  end
62
62
 
63
+ def test_get_bid_ask_parses_response
64
+ body = load_fixture("kraken_ticker.json")
65
+ stub_request(body)
66
+
67
+ result = @exchange.get_bid_ask("XBTUSDT")
68
+
69
+ assert result.success?
70
+ assert_equal BigDecimal("67123.45"), result.data[:bid]
71
+ assert_equal BigDecimal("67125.67"), result.data[:ask]
72
+ end
73
+
74
+ def test_get_bid_ask_handles_api_error
75
+ body = { "error" => ["EGeneral:Internal error"], "result" => {} }
76
+ stub_request(body)
77
+
78
+ result = @exchange.get_bid_ask("XBTUSDT")
79
+
80
+ assert result.failure?
81
+ end
82
+
63
83
  private
64
84
 
65
85
  def stub_request(body)
@@ -32,6 +32,17 @@ class Honeymaker::Exchanges::KucoinTest < Minitest::Test
32
32
  assert ticker[:available]
33
33
  end
34
34
 
35
+ def test_get_bid_ask_parses_response
36
+ body = load_fixture("kucoin_orderbook_level1.json")
37
+ stub_connection(body)
38
+
39
+ result = @exchange.get_bid_ask("BTC-USDT")
40
+
41
+ assert result.success?
42
+ assert_equal BigDecimal("67123.45"), result.data[:bid]
43
+ assert_equal BigDecimal("67125.67"), result.data[:ask]
44
+ end
45
+
35
46
  private
36
47
 
37
48
  def stub_connection(body)
@@ -53,6 +53,17 @@ class Honeymaker::Exchanges::MexcTest < Minitest::Test
53
53
  assert_equal 2, eth[:price_decimals]
54
54
  end
55
55
 
56
+ def test_get_bid_ask_parses_response
57
+ body = load_fixture("mexc_book_ticker.json")
58
+ stub_connection(body)
59
+
60
+ result = @exchange.get_bid_ask("BTCUSDT")
61
+
62
+ assert result.success?
63
+ assert_equal BigDecimal("67123.45"), result.data[:bid]
64
+ assert_equal BigDecimal("67125.67"), result.data[:ask]
65
+ end
66
+
56
67
  private
57
68
 
58
69
  def stub_connection(body)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: honeymaker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Deltabadger
@@ -128,19 +128,32 @@ files:
128
128
  - lib/honeymaker/utils.rb
129
129
  - lib/honeymaker/version.rb
130
130
  - sig/honeymaker.rbs
131
+ - test/fixtures/binance_book_ticker.json
131
132
  - test/fixtures/binance_exchange_info.json
132
133
  - test/fixtures/bingx_symbols.json
134
+ - test/fixtures/bingx_ticker.json
133
135
  - test/fixtures/bitget_symbols.json
136
+ - test/fixtures/bitget_tickers.json
134
137
  - test/fixtures/bitmart_symbols.json
138
+ - test/fixtures/bitmart_ticker.json
139
+ - test/fixtures/bitrue_book_ticker.json
135
140
  - test/fixtures/bitrue_exchange_info.json
141
+ - test/fixtures/bitvavo_book_ticker.json
136
142
  - test/fixtures/bitvavo_markets.json
137
143
  - test/fixtures/bybit_instruments.json
144
+ - test/fixtures/bybit_tickers.json
145
+ - test/fixtures/coinbase_product.json
138
146
  - test/fixtures/coinbase_products.json
147
+ - test/fixtures/gemini_pubticker.json
139
148
  - test/fixtures/gemini_symbol_detail.json
140
149
  - test/fixtures/gemini_symbols.json
150
+ - test/fixtures/hyperliquid_all_mids.json
141
151
  - test/fixtures/hyperliquid_spot_meta.json
142
152
  - test/fixtures/kraken_asset_pairs.json
153
+ - test/fixtures/kraken_ticker.json
154
+ - test/fixtures/kucoin_orderbook_level1.json
143
155
  - test/fixtures/kucoin_symbols.json
156
+ - test/fixtures/mexc_book_ticker.json
144
157
  - test/fixtures/mexc_exchange_info.json
145
158
  - test/honeymaker/client_test.rb
146
159
  - test/honeymaker/clients/binance_client_test.rb