honeymaker 0.12.4-aarch64-linux

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 (136) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/native-gems.yml +100 -0
  3. data/.github/workflows/release.yml +36 -0
  4. data/.github/workflows/test.yml +33 -0
  5. data/LICENSE +21 -0
  6. data/README.md +219 -0
  7. data/Rakefile +78 -0
  8. data/crates/honeymaker/tests/vectors/decimal.json +146 -0
  9. data/crates/honeymaker/tests/vectors/kraken_add_order_wire.json +74 -0
  10. data/crates/honeymaker/tests/vectors/kraken_signing.json +3314 -0
  11. data/crates/honeymaker/tests/vectors/kraken_trades_pager.json +1583 -0
  12. data/lib/honeymaker/3.4/honeymaker_native.so +0 -0
  13. data/lib/honeymaker/4.0/honeymaker_native.so +0 -0
  14. data/lib/honeymaker/client.rb +139 -0
  15. data/lib/honeymaker/clients/binance.rb +687 -0
  16. data/lib/honeymaker/clients/binance_us.rb +42 -0
  17. data/lib/honeymaker/clients/bingx.rb +242 -0
  18. data/lib/honeymaker/clients/bitget.rb +318 -0
  19. data/lib/honeymaker/clients/bitrue.rb +245 -0
  20. data/lib/honeymaker/clients/bitvavo.rb +244 -0
  21. data/lib/honeymaker/clients/bybit.rb +308 -0
  22. data/lib/honeymaker/clients/coinbase.rb +326 -0
  23. data/lib/honeymaker/clients/gemini.rb +189 -0
  24. data/lib/honeymaker/clients/hyperliquid.rb +232 -0
  25. data/lib/honeymaker/clients/kraken.rb +379 -0
  26. data/lib/honeymaker/clients/kraken_futures.rb +78 -0
  27. data/lib/honeymaker/clients/kucoin.rb +310 -0
  28. data/lib/honeymaker/clients/mexc.rb +249 -0
  29. data/lib/honeymaker/exchange.rb +129 -0
  30. data/lib/honeymaker/exchanges/binance.rb +55 -0
  31. data/lib/honeymaker/exchanges/binance_us.rb +9 -0
  32. data/lib/honeymaker/exchanges/bingx.rb +56 -0
  33. data/lib/honeymaker/exchanges/bitget.rb +52 -0
  34. data/lib/honeymaker/exchanges/bitrue.rb +53 -0
  35. data/lib/honeymaker/exchanges/bitvavo.rb +65 -0
  36. data/lib/honeymaker/exchanges/bybit.rb +57 -0
  37. data/lib/honeymaker/exchanges/coinbase.rb +69 -0
  38. data/lib/honeymaker/exchanges/gemini.rb +134 -0
  39. data/lib/honeymaker/exchanges/hyperliquid.rb +67 -0
  40. data/lib/honeymaker/exchanges/kraken.rb +122 -0
  41. data/lib/honeymaker/exchanges/kucoin.rb +52 -0
  42. data/lib/honeymaker/exchanges/mexc.rb +56 -0
  43. data/lib/honeymaker/native/kraken.rb +263 -0
  44. data/lib/honeymaker/native.rb +42 -0
  45. data/lib/honeymaker/result.rb +46 -0
  46. data/lib/honeymaker/utils.rb +39 -0
  47. data/lib/honeymaker/version.rb +5 -0
  48. data/lib/honeymaker.rb +97 -0
  49. data/script/parity/kraken_client_live.rb +191 -0
  50. data/script/parity/kraken_live.rb +70 -0
  51. data/script/parity/kraken_transport.rb +604 -0
  52. data/script/parity/resolve.c +49 -0
  53. data/script/release/push_gems.sh +28 -0
  54. data/script/vectors/kraken.rb +50 -0
  55. data/script/vectors/kraken_stage2.rb +146 -0
  56. data/sig/honeymaker.rbs +4 -0
  57. data/test/fixtures/binance_book_ticker.json +7 -0
  58. data/test/fixtures/binance_exchange_info.json +30 -0
  59. data/test/fixtures/bingx_symbols.json +26 -0
  60. data/test/fixtures/bingx_ticker.json +12 -0
  61. data/test/fixtures/bitget_symbols.json +28 -0
  62. data/test/fixtures/bitget_tickers.json +14 -0
  63. data/test/fixtures/bitmart_symbols.json +26 -0
  64. data/test/fixtures/bitmart_ticker.json +12 -0
  65. data/test/fixtures/bitrue_book_ticker.json +7 -0
  66. data/test/fixtures/bitrue_exchange_info.json +34 -0
  67. data/test/fixtures/bitvavo_book_ticker.json +7 -0
  68. data/test/fixtures/bitvavo_markets.json +30 -0
  69. data/test/fixtures/bybit_instruments.json +23 -0
  70. data/test/fixtures/bybit_tickers.json +19 -0
  71. data/test/fixtures/coinbase_product.json +7 -0
  72. data/test/fixtures/coinbase_products.json +24 -0
  73. data/test/fixtures/gemini_pubticker.json +10 -0
  74. data/test/fixtures/gemini_symbol_detail.json +10 -0
  75. data/test/fixtures/gemini_symbols.json +1 -0
  76. data/test/fixtures/hyperliquid_all_mids.json +5 -0
  77. data/test/fixtures/hyperliquid_spot_meta.json +12 -0
  78. data/test/fixtures/kraken_asset_pairs.json +43 -0
  79. data/test/fixtures/kraken_ticker.json +16 -0
  80. data/test/fixtures/kucoin_orderbook_level1.json +10 -0
  81. data/test/fixtures/kucoin_symbols.json +17 -0
  82. data/test/fixtures/mexc_book_ticker.json +7 -0
  83. data/test/fixtures/mexc_exchange_info.json +64 -0
  84. data/test/honeymaker/client_test.rb +90 -0
  85. data/test/honeymaker/clients/binance_client_test.rb +151 -0
  86. data/test/honeymaker/clients/binance_us_client_test.rb +25 -0
  87. data/test/honeymaker/clients/bingx_client_test.rb +106 -0
  88. data/test/honeymaker/clients/bitget_client_test.rb +146 -0
  89. data/test/honeymaker/clients/bitrue_client_test.rb +69 -0
  90. data/test/honeymaker/clients/bitvavo_client_test.rb +156 -0
  91. data/test/honeymaker/clients/bybit_client_test.rb +137 -0
  92. data/test/honeymaker/clients/coinbase_client_test.rb +153 -0
  93. data/test/honeymaker/clients/gemini_client_test.rb +100 -0
  94. data/test/honeymaker/clients/honeymaker_client_registry_test.rb +39 -0
  95. data/test/honeymaker/clients/hyperliquid_client_test.rb +292 -0
  96. data/test/honeymaker/clients/kraken_client_test.rb +255 -0
  97. data/test/honeymaker/clients/kraken_futures_client_test.rb +54 -0
  98. data/test/honeymaker/clients/kucoin_client_test.rb +149 -0
  99. data/test/honeymaker/clients/mexc_client_test.rb +98 -0
  100. data/test/honeymaker/clients/validation_test.rb +167 -0
  101. data/test/honeymaker/error_chain_test.rb +133 -0
  102. data/test/honeymaker/exchange_test.rb +138 -0
  103. data/test/honeymaker/exchanges/binance_test.rb +83 -0
  104. data/test/honeymaker/exchanges/binance_us_test.rb +53 -0
  105. data/test/honeymaker/exchanges/bingx_test.rb +65 -0
  106. data/test/honeymaker/exchanges/bitget_test.rb +65 -0
  107. data/test/honeymaker/exchanges/bitrue_test.rb +65 -0
  108. data/test/honeymaker/exchanges/bitvavo_test.rb +65 -0
  109. data/test/honeymaker/exchanges/bybit_test.rb +55 -0
  110. data/test/honeymaker/exchanges/coinbase_test.rb +106 -0
  111. data/test/honeymaker/exchanges/gemini_catalogue_test.rb +118 -0
  112. data/test/honeymaker/exchanges/gemini_test.rb +184 -0
  113. data/test/honeymaker/exchanges/hyperliquid_test.rb +64 -0
  114. data/test/honeymaker/exchanges/kraken_test.rb +191 -0
  115. data/test/honeymaker/exchanges/kucoin_test.rb +55 -0
  116. data/test/honeymaker/exchanges/mexc_test.rb +97 -0
  117. data/test/honeymaker/honeymaker_test.rb +27 -0
  118. data/test/honeymaker/result_test.rb +42 -0
  119. data/test/honeymaker/utils_test.rb +64 -0
  120. data/test/native/backend_switch_test.rb +53 -0
  121. data/test/native/convert_test.rb +137 -0
  122. data/test/native/gem_isolation_test.rb +17 -0
  123. data/test/native/loader_test.rb +29 -0
  124. data/test/native/lockstep_test.rb +23 -0
  125. data/test/native/signing_smoke_test.rb +28 -0
  126. data/test/parity/kraken_client_parity_test.rb +398 -0
  127. data/test/parity/kraken_exchange_parity_test.rb +172 -0
  128. data/test/release/branch_hardening_test.rb +34 -0
  129. data/test/release/push_gems_test.rb +79 -0
  130. data/test/smoke.rb +24 -0
  131. data/test/support/fixture_helper.rb +13 -0
  132. data/test/support/kraken_signing_smoke.rb +21 -0
  133. data/test/support/parity_helper.rb +62 -0
  134. data/test/support/venue_double.rb +100 -0
  135. data/test/test_helper.rb +9 -0
  136. metadata +254 -0
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Honeymaker
4
+ module Clients
5
+ class Bitrue < Client
6
+ URL = "https://openapi.bitrue.com"
7
+ RATE_LIMITS = { default: 100, orders: 200 }.freeze
8
+
9
+ def exchange_information
10
+ get_public("/api/v1/exchangeInfo")
11
+ end
12
+
13
+ def symbol_price_ticker(symbol: nil)
14
+ get_public("/api/v1/ticker/price", { symbol: symbol })
15
+ end
16
+
17
+ def symbol_order_book_ticker(symbol: nil)
18
+ get_public("/api/v1/ticker/bookTicker", { symbol: symbol })
19
+ end
20
+
21
+ def candlestick_data(symbol:, interval:, start_time: nil, end_time: nil, limit: 500)
22
+ get_public("/api/v1/market/kline", {
23
+ symbol: symbol, scale: interval, startTime: start_time,
24
+ endTime: end_time, limit: limit
25
+ })
26
+ end
27
+
28
+ def account_information(recv_window: 5000)
29
+ get_signed("/api/v1/account", { recvWindow: recv_window })
30
+ end
31
+
32
+ def get_balances
33
+ result = account_information
34
+ return result if result.failure?
35
+
36
+ balances = {}
37
+ Array(result.data["balances"]).each do |balance|
38
+ symbol = balance["asset"]
39
+ free = BigDecimal(balance["free"].to_s)
40
+ locked = BigDecimal(balance["locked"].to_s)
41
+ next if free.zero? && locked.zero?
42
+ balances[symbol] = { free: free, locked: locked }
43
+ end
44
+
45
+ Result::Success.new(balances)
46
+ end
47
+
48
+ def query_order(symbol:, order_id: nil, orig_client_order_id: nil, recv_window: 5000)
49
+ result = get_signed("/api/v1/order", {
50
+ symbol: symbol, orderId: order_id,
51
+ origClientOrderId: orig_client_order_id, recvWindow: recv_window
52
+ })
53
+ return result if result.failure?
54
+
55
+ raw = result.data
56
+ Result::Success.new(normalize_order("#{symbol}-#{raw['orderId']}", raw))
57
+ end
58
+
59
+ def new_order(symbol:, side:, type:, time_in_force: nil, quantity: nil, quote_order_qty: nil,
60
+ price: nil, new_client_order_id: nil, recv_window: 5000)
61
+ result = post_signed("/api/v1/order", {
62
+ symbol: symbol, side: side, type: type,
63
+ timeInForce: time_in_force, quantity: quantity,
64
+ quoteOrderQty: quote_order_qty, price: price,
65
+ newClientOrderId: new_client_order_id, recvWindow: recv_window
66
+ })
67
+ return result if result.failure?
68
+
69
+ raw = result.data
70
+ Result::Success.new({ order_id: "#{symbol}-#{raw['orderId']}", raw: raw })
71
+ end
72
+
73
+ def cancel_order(symbol:, order_id: nil, orig_client_order_id: nil, recv_window: 5000)
74
+ delete_signed("/api/v1/order", {
75
+ symbol: symbol, orderId: order_id,
76
+ origClientOrderId: orig_client_order_id, recvWindow: recv_window
77
+ })
78
+ end
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
+
91
+ def account_trade_list(symbol:, start_time: nil, end_time: nil, from_id: nil, limit: 500, recv_window: 5000)
92
+ get_signed("/api/v1/myTrades", {
93
+ symbol: symbol, startTime: start_time, endTime: end_time,
94
+ fromId: from_id, limit: limit, recvWindow: recv_window
95
+ })
96
+ end
97
+
98
+ def deposit_history(coin: nil, status: nil, start_time: nil, end_time: nil, offset: nil, limit: 1000, recv_window: 5000)
99
+ get_signed("/api/v1/capital/deposit/hisrec", {
100
+ coin: coin, status: status,
101
+ startTime: start_time, endTime: end_time,
102
+ offset: offset, limit: limit, recvWindow: recv_window
103
+ })
104
+ end
105
+
106
+ def withdraw_history(coin: nil, status: nil, start_time: nil, end_time: nil, offset: nil, limit: 1000, recv_window: 5000)
107
+ get_signed("/api/v1/capital/withdraw/history", {
108
+ coin: coin, status: status,
109
+ startTime: start_time, endTime: end_time,
110
+ offset: offset, limit: limit, recvWindow: recv_window
111
+ })
112
+ end
113
+
114
+ # --- Futures ---
115
+
116
+ def futures_account(recv_window: 5000)
117
+ with_rescue do
118
+ response = futures_connection.get do |req|
119
+ req.url "/fapi/v1/account"
120
+ req.headers = auth_headers
121
+ req.params = { recvWindow: recv_window, timestamp: timestamp_ms }.compact
122
+ req.params[:signature] = sign_params(req.params)
123
+ end
124
+ response.body
125
+ end
126
+ end
127
+
128
+ private
129
+
130
+ def futures_connection
131
+ @futures_connection ||= build_client_connection("https://fapi.bitrue.com")
132
+ end
133
+
134
+ def normalize_order(order_id, raw)
135
+ order_type = parse_order_type(raw["type"])
136
+ side = raw["side"]&.downcase&.to_sym
137
+ status = parse_order_status(raw["status"])
138
+
139
+ amount = BigDecimal((raw["origQty"] || "0").to_s)
140
+ amount = nil if amount.zero?
141
+ quote_amount = raw["origQuoteOrderQty"] ? BigDecimal(raw["origQuoteOrderQty"].to_s) : nil
142
+ quote_amount = nil if quote_amount&.zero?
143
+
144
+ amount_exec = BigDecimal((raw["executedQty"] || "0").to_s)
145
+ quote_amount_exec = BigDecimal((raw["cummulativeQuoteQty"] || "0").to_s)
146
+ quote_amount_exec = nil if quote_amount_exec.negative?
147
+
148
+ price = BigDecimal((raw["price"] || "0").to_s)
149
+ if price.zero? && quote_amount_exec&.positive? && amount_exec.positive?
150
+ price = quote_amount_exec / amount_exec
151
+ end
152
+ price = nil if price.zero?
153
+
154
+ {
155
+ order_id: order_id, status: status, side: side, order_type: order_type,
156
+ price: price, amount: amount, quote_amount: quote_amount,
157
+ amount_exec: amount_exec, quote_amount_exec: quote_amount_exec, raw: raw
158
+ }
159
+ end
160
+
161
+ def parse_order_type(type)
162
+ case type
163
+ when "MARKET" then :market
164
+ when "LIMIT" then :limit
165
+ else :unknown
166
+ end
167
+ end
168
+
169
+ def parse_order_status(status)
170
+ case status
171
+ when "NEW", "PARTIALLY_FILLED" then :open
172
+ when "FILLED" then :closed
173
+ when "CANCELED", "EXPIRED" then :cancelled
174
+ when "REJECTED" then :failed
175
+ else :unknown
176
+ end
177
+ end
178
+
179
+ def validate_trading_credentials
180
+ result = account_information
181
+ result.success? ? Result::Success.new(true) : Result::Failure.new("Invalid trading credentials")
182
+ end
183
+
184
+ def validate_read_credentials
185
+ validate_trading_credentials
186
+ end
187
+
188
+ def get_public(path, params = {})
189
+ with_rescue do
190
+ response = connection.get do |req|
191
+ req.url path
192
+ req.params = params.compact
193
+ end
194
+ response.body
195
+ end
196
+ end
197
+
198
+ def get_signed(path, params = {})
199
+ with_rescue do
200
+ response = connection.get do |req|
201
+ req.url path
202
+ req.headers = auth_headers
203
+ req.params = params.compact.merge(timestamp: timestamp_ms)
204
+ req.params[:signature] = sign_params(req.params)
205
+ end
206
+ response.body
207
+ end
208
+ end
209
+
210
+ def post_signed(path, params = {})
211
+ with_rescue do
212
+ response = connection.post do |req|
213
+ req.url path
214
+ req.headers = auth_headers
215
+ req.params = params.compact.merge(timestamp: timestamp_ms)
216
+ req.params[:signature] = sign_params(req.params)
217
+ end
218
+ response.body
219
+ end
220
+ end
221
+
222
+ def delete_signed(path, params = {})
223
+ with_rescue do
224
+ response = connection.delete do |req|
225
+ req.url path
226
+ req.headers = auth_headers
227
+ req.params = params.compact.merge(timestamp: timestamp_ms)
228
+ req.params[:signature] = sign_params(req.params)
229
+ end
230
+ response.body
231
+ end
232
+ end
233
+
234
+ def auth_headers
235
+ { "X-MBX-APIKEY": @api_key, Accept: "application/json", "Content-Type": "application/json" }
236
+ end
237
+
238
+ def sign_params(params)
239
+ return unless @api_secret
240
+ query = Faraday::Utils.build_query(params)
241
+ hmac_sha256(@api_secret, query)
242
+ end
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Honeymaker
4
+ module Clients
5
+ class Bitvavo < Client
6
+ URL = "https://api.bitvavo.com"
7
+ ACCESS_WINDOW = "10000"
8
+ RATE_LIMITS = { default: 100, orders: 100 }.freeze
9
+
10
+ # Bitvavo requires a mandatory operatorId on every order create/cancel
11
+ # (errorCode 203). It's a positive int64 the caller sets themselves to
12
+ # attribute an order to a trader/bot for audit; the value is cosmetic.
13
+ DEFAULT_OPERATOR_ID = 1
14
+
15
+ def get_assets
16
+ get_public("/v2/assets")
17
+ end
18
+
19
+ def get_markets(market: nil)
20
+ get_public("/v2/markets", { market: market })
21
+ end
22
+
23
+ def get_ticker_price(market: nil)
24
+ get_public("/v2/ticker/price", { market: market })
25
+ end
26
+
27
+ def get_ticker_book(market: nil)
28
+ get_public("/v2/ticker/book", { market: market })
29
+ end
30
+
31
+ def get_candles(market:, interval:, start_time: nil, end_time: nil, limit: nil)
32
+ get_public("/v2/#{market}/candles", {
33
+ interval: interval, start: start_time, end: end_time, limit: limit
34
+ })
35
+ end
36
+
37
+ def get_raw_balance(symbol: nil)
38
+ get_signed("/v2/balance", { symbol: symbol })
39
+ end
40
+
41
+ def get_balances
42
+ result = get_raw_balance
43
+ return result if result.failure?
44
+
45
+ balances = {}
46
+ Array(result.data).each do |balance|
47
+ symbol = balance["symbol"]
48
+ next unless symbol
49
+ free = BigDecimal((balance["available"] || "0").to_s)
50
+ locked = BigDecimal((balance["inOrder"] || "0").to_s)
51
+ next if free.zero? && locked.zero?
52
+ balances[symbol] = { free: free, locked: locked }
53
+ end
54
+
55
+ Result::Success.new(balances)
56
+ end
57
+
58
+ def place_order(market:, side:, order_type:, amount: nil, amount_quote: nil, price: nil,
59
+ time_in_force: nil, client_order_id: nil, operator_id: nil)
60
+ result = post_signed("/v2/order", {
61
+ market: market, side: side, orderType: order_type,
62
+ amount: amount, amountQuote: amount_quote, price: price,
63
+ timeInForce: time_in_force, clientOrderId: client_order_id,
64
+ operatorId: operator_id || DEFAULT_OPERATOR_ID
65
+ })
66
+ return result if result.failure?
67
+
68
+ raw = result.data
69
+ Result::Success.new({ order_id: "#{raw['market']}-#{raw['orderId']}", raw: raw })
70
+ end
71
+
72
+ def get_order(market:, order_id:)
73
+ result = get_signed("/v2/order", { market: market, orderId: order_id })
74
+ return result if result.failure?
75
+
76
+ raw = result.data
77
+ Result::Success.new(normalize_order("#{raw['market']}-#{raw['orderId']}", raw))
78
+ end
79
+
80
+ def cancel_order(market:, order_id:, operator_id: nil)
81
+ delete_signed("/v2/order", {
82
+ market: market, orderId: order_id, operatorId: operator_id || DEFAULT_OPERATOR_ID
83
+ })
84
+ end
85
+
86
+ def get_trades(market:, limit: nil, start_time: nil, end_time: nil, trade_id_from: nil, trade_id_to: nil)
87
+ get_signed("/v2/trades", {
88
+ market: market, limit: limit, start: start_time, end: end_time,
89
+ tradeIdFrom: trade_id_from, tradeIdTo: trade_id_to
90
+ })
91
+ end
92
+
93
+ def get_deposit_history(symbol: nil, limit: nil, start_time: nil, end_time: nil)
94
+ get_signed("/v2/deposit", { symbol: symbol, limit: limit, start: start_time, end: end_time })
95
+ end
96
+
97
+ def get_withdrawal_history(symbol: nil, limit: nil, start_time: nil, end_time: nil)
98
+ get_signed("/v2/withdrawal", { symbol: symbol, limit: limit, start: start_time, end: end_time })
99
+ end
100
+
101
+ def withdraw(symbol:, amount:, address:, payment_id: nil)
102
+ post_signed("/v2/withdrawal", {
103
+ symbol: symbol, amount: amount, address: address, paymentId: payment_id
104
+ })
105
+ end
106
+
107
+ # --- History ---
108
+
109
+ def get_transactions(limit: nil, start_time: nil, end_time: nil)
110
+ get_signed("/v2/transactions", { limit: limit, start: start_time, end: end_time })
111
+ end
112
+
113
+ private
114
+
115
+ def normalize_order(order_id, raw)
116
+ order_type = parse_order_type(raw["orderType"])
117
+ side = raw["side"]&.downcase&.to_sym
118
+ status = parse_order_status(raw["status"])
119
+
120
+ price = BigDecimal((raw["price"] || "0").to_s)
121
+ amount = BigDecimal((raw["amount"] || "0").to_s)
122
+ amount = nil if amount.zero?
123
+ amount_quote = BigDecimal((raw["amountQuote"] || "0").to_s)
124
+ amount_quote = nil if amount_quote.zero?
125
+
126
+ amount_exec = BigDecimal((raw["filledAmount"] || "0").to_s)
127
+ quote_amount_exec = BigDecimal((raw["filledAmountQuote"] || "0").to_s)
128
+
129
+ if price.zero? && quote_amount_exec.positive? && amount_exec.positive?
130
+ price = quote_amount_exec / amount_exec
131
+ end
132
+ price = nil if price.zero?
133
+
134
+ {
135
+ order_id: order_id, status: status, side: side, order_type: order_type,
136
+ price: price, amount: amount, quote_amount: amount_quote,
137
+ amount_exec: amount_exec, quote_amount_exec: quote_amount_exec, raw: raw
138
+ }
139
+ end
140
+
141
+ def parse_order_type(type)
142
+ case type
143
+ when "market" then :market
144
+ when "limit" then :limit
145
+ else :unknown
146
+ end
147
+ end
148
+
149
+ def parse_order_status(status)
150
+ case status
151
+ when "new", "partiallyFilled" then :open
152
+ when "filled" then :closed
153
+ when "canceled", "cancelled", "expired" then :cancelled
154
+ when "rejected" then :failed
155
+ else :unknown
156
+ end
157
+ end
158
+
159
+ def validate_trading_credentials
160
+ result = get_raw_balance
161
+ result.success? ? Result::Success.new(true) : Result::Failure.new("Invalid trading credentials")
162
+ end
163
+
164
+ def validate_read_credentials
165
+ validate_trading_credentials
166
+ end
167
+
168
+ def get_public(path, params = {})
169
+ with_rescue do
170
+ response = connection.get do |req|
171
+ req.url path
172
+ req.headers = unauthenticated_headers
173
+ req.params = params.compact
174
+ end
175
+ response.body
176
+ end
177
+ end
178
+
179
+ def get_signed(path, params = {})
180
+ with_rescue do
181
+ params = params.compact
182
+ query_string = params.any? ? "?#{Faraday::Utils.build_query(params)}" : ""
183
+ ts = timestamp_ms.to_s
184
+ payload = "#{ts}GET#{path}#{query_string}"
185
+
186
+ response = connection.get do |req|
187
+ req.url path
188
+ req.headers = signed_headers(ts, payload)
189
+ req.params = params
190
+ end
191
+ response.body
192
+ end
193
+ end
194
+
195
+ def post_signed(path, body = {})
196
+ with_rescue do
197
+ body = body.compact
198
+ body_string = body.to_json
199
+ ts = timestamp_ms.to_s
200
+ payload = "#{ts}POST#{path}#{body_string}"
201
+
202
+ response = connection.post do |req|
203
+ req.url path
204
+ req.headers = signed_headers(ts, payload)
205
+ req.body = body
206
+ end
207
+ response.body
208
+ end
209
+ end
210
+
211
+ def delete_signed(path, params = {})
212
+ with_rescue do
213
+ params = params.compact
214
+ query_string = params.any? ? "?#{Faraday::Utils.build_query(params)}" : ""
215
+ ts = timestamp_ms.to_s
216
+ payload = "#{ts}DELETE#{path}#{query_string}"
217
+
218
+ response = connection.delete do |req|
219
+ req.url path
220
+ req.headers = signed_headers(ts, payload)
221
+ req.params = params
222
+ end
223
+ response.body
224
+ end
225
+ end
226
+
227
+ def unauthenticated_headers
228
+ { Accept: "application/json", "Content-Type": "application/json" }
229
+ end
230
+
231
+ def signed_headers(timestamp, payload)
232
+ signature = hmac_sha256(@api_secret, payload)
233
+ {
234
+ "BITVAVO-ACCESS-KEY": @api_key,
235
+ "BITVAVO-ACCESS-SIGNATURE": signature,
236
+ "BITVAVO-ACCESS-TIMESTAMP": timestamp,
237
+ "BITVAVO-ACCESS-WINDOW": ACCESS_WINDOW,
238
+ Accept: "application/json",
239
+ "Content-Type": "application/json"
240
+ }
241
+ end
242
+ end
243
+ end
244
+ end