honeymaker 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/README.md +100 -5
- data/lib/honeymaker/client.rb +14 -0
- data/lib/honeymaker/clients/binance.rb +264 -2
- data/lib/honeymaker/clients/binance_us.rb +33 -0
- data/lib/honeymaker/clients/bingx.rb +100 -4
- data/lib/honeymaker/clients/bitget.rb +163 -2
- data/lib/honeymaker/clients/bitmart.rb +108 -2
- data/lib/honeymaker/clients/bitrue.rb +90 -2
- data/lib/honeymaker/clients/bitvavo.rb +80 -4
- data/lib/honeymaker/clients/bybit.rb +120 -2
- data/lib/honeymaker/clients/coinbase.rb +108 -2
- data/lib/honeymaker/clients/gemini.rb +85 -4
- data/lib/honeymaker/clients/hyperliquid.rb +69 -1
- data/lib/honeymaker/clients/kraken.rb +112 -2
- data/lib/honeymaker/clients/kraken_futures.rb +78 -0
- data/lib/honeymaker/clients/kucoin.rb +120 -2
- data/lib/honeymaker/clients/mexc.rb +85 -2
- data/lib/honeymaker/version.rb +1 -1
- data/lib/honeymaker.rb +3 -1
- data/test/honeymaker/clients/binance_client_test.rb +9 -2
- data/test/honeymaker/clients/bitget_client_test.rb +9 -3
- data/test/honeymaker/clients/bitmart_client_test.rb +7 -2
- data/test/honeymaker/clients/bitvavo_client_test.rb +2 -2
- data/test/honeymaker/clients/bybit_client_test.rb +7 -3
- data/test/honeymaker/clients/coinbase_client_test.rb +10 -3
- data/test/honeymaker/clients/honeymaker_client_registry_test.rb +1 -1
- data/test/honeymaker/clients/kraken_client_test.rb +2 -1
- data/test/honeymaker/clients/kraken_futures_client_test.rb +54 -0
- data/test/honeymaker/clients/kucoin_client_test.rb +8 -2
- data/test/honeymaker/clients/mexc_client_test.rb +6 -1
- metadata +17 -1
|
@@ -4,6 +4,7 @@ module Honeymaker
|
|
|
4
4
|
module Clients
|
|
5
5
|
class Bitget < Client
|
|
6
6
|
URL = "https://api.bitget.com"
|
|
7
|
+
RATE_LIMITS = { default: 100, orders: 200 }.freeze
|
|
7
8
|
|
|
8
9
|
attr_reader :passphrase
|
|
9
10
|
|
|
@@ -39,15 +40,46 @@ module Honeymaker
|
|
|
39
40
|
get_signed("/api/v2/spot/account/assets", { coin: coin })
|
|
40
41
|
end
|
|
41
42
|
|
|
43
|
+
def get_balances
|
|
44
|
+
result = get_account_assets
|
|
45
|
+
return result if result.failure?
|
|
46
|
+
|
|
47
|
+
return Result::Failure.new("Bitget API error") unless result.data["code"] == "00000"
|
|
48
|
+
|
|
49
|
+
balances = {}
|
|
50
|
+
(result.data["data"] || []).each do |asset|
|
|
51
|
+
symbol = asset["coin"]
|
|
52
|
+
free = BigDecimal((asset["available"] || "0").to_s)
|
|
53
|
+
locked = BigDecimal((asset["frozen"] || "0").to_s)
|
|
54
|
+
next if free.zero? && locked.zero?
|
|
55
|
+
balances[symbol] = { free: free, locked: locked }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Result::Success.new(balances)
|
|
59
|
+
end
|
|
60
|
+
|
|
42
61
|
def place_order(symbol:, side:, order_type:, size: nil, quote_size: nil, price: nil, force: nil, client_oid: nil)
|
|
43
|
-
post_signed("/api/v2/spot/trade/place-order", {
|
|
62
|
+
result = post_signed("/api/v2/spot/trade/place-order", {
|
|
44
63
|
symbol: symbol, side: side, orderType: order_type,
|
|
45
64
|
size: size, quoteSize: quote_size, price: price, force: force, clientOid: client_oid
|
|
46
65
|
})
|
|
66
|
+
return result if result.failure?
|
|
67
|
+
return Result::Failure.new("Bitget API error") unless result.data["code"] == "00000"
|
|
68
|
+
|
|
69
|
+
order_id = result.data.dig("data", "orderId")
|
|
70
|
+
Result::Success.new({ order_id: order_id, raw: result.data })
|
|
47
71
|
end
|
|
48
72
|
|
|
49
73
|
def get_order(order_id: nil, client_oid: nil)
|
|
50
|
-
get_signed("/api/v2/spot/trade/orderInfo", { orderId: order_id, clientOid: client_oid })
|
|
74
|
+
result = get_signed("/api/v2/spot/trade/orderInfo", { orderId: order_id, clientOid: client_oid })
|
|
75
|
+
return result if result.failure?
|
|
76
|
+
return Result::Failure.new("Bitget API error") unless result.data["code"] == "00000"
|
|
77
|
+
|
|
78
|
+
order_list = result.data["data"]
|
|
79
|
+
raw = order_list.is_a?(Array) ? order_list.first : order_list
|
|
80
|
+
return Result::Failure.new("Order not found") unless raw
|
|
81
|
+
|
|
82
|
+
Result::Success.new(normalize_order(raw["orderId"] || order_id, raw))
|
|
51
83
|
end
|
|
52
84
|
|
|
53
85
|
def cancel_order(symbol:, order_id: nil, client_oid: nil)
|
|
@@ -82,8 +114,137 @@ module Honeymaker
|
|
|
82
114
|
})
|
|
83
115
|
end
|
|
84
116
|
|
|
117
|
+
# --- Margin (Cross) ---
|
|
118
|
+
|
|
119
|
+
def margin_crossed_borrow_history(loan_id: nil, coin: nil, start_time: nil, end_time: nil, limit: nil, id_less_than: nil)
|
|
120
|
+
get_signed("/api/v2/margin/crossed/borrow-history", {
|
|
121
|
+
loanId: loan_id, coin: coin,
|
|
122
|
+
startTime: start_time, endTime: end_time, limit: limit, idLessThan: id_less_than
|
|
123
|
+
})
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def margin_crossed_repay_history(repay_id: nil, coin: nil, start_time: nil, end_time: nil, limit: nil, id_less_than: nil)
|
|
127
|
+
get_signed("/api/v2/margin/crossed/repay-history", {
|
|
128
|
+
repayId: repay_id, coin: coin,
|
|
129
|
+
startTime: start_time, endTime: end_time, limit: limit, idLessThan: id_less_than
|
|
130
|
+
})
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def margin_crossed_interest_history(coin: nil, start_time: nil, end_time: nil, limit: nil, id_less_than: nil)
|
|
134
|
+
get_signed("/api/v2/margin/crossed/interest-history", {
|
|
135
|
+
coin: coin, startTime: start_time, endTime: end_time, limit: limit, idLessThan: id_less_than
|
|
136
|
+
})
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def margin_crossed_liquidation_history(start_time: nil, end_time: nil, limit: nil, id_less_than: nil)
|
|
140
|
+
get_signed("/api/v2/margin/crossed/liquidation-history", {
|
|
141
|
+
startTime: start_time, endTime: end_time, limit: limit, idLessThan: id_less_than
|
|
142
|
+
})
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# --- Margin (Isolated) ---
|
|
146
|
+
|
|
147
|
+
def margin_isolated_borrow_history(symbol: nil, loan_id: nil, coin: nil, start_time: nil, end_time: nil, limit: nil, id_less_than: nil)
|
|
148
|
+
get_signed("/api/v2/margin/isolated/borrow-history", {
|
|
149
|
+
symbol: symbol, loanId: loan_id, coin: coin,
|
|
150
|
+
startTime: start_time, endTime: end_time, limit: limit, idLessThan: id_less_than
|
|
151
|
+
})
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def margin_isolated_repay_history(symbol: nil, repay_id: nil, coin: nil, start_time: nil, end_time: nil, limit: nil, id_less_than: nil)
|
|
155
|
+
get_signed("/api/v2/margin/isolated/repay-history", {
|
|
156
|
+
symbol: symbol, repayId: repay_id, coin: coin,
|
|
157
|
+
startTime: start_time, endTime: end_time, limit: limit, idLessThan: id_less_than
|
|
158
|
+
})
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def margin_isolated_interest_history(symbol: nil, coin: nil, start_time: nil, end_time: nil, limit: nil, id_less_than: nil)
|
|
162
|
+
get_signed("/api/v2/margin/isolated/interest-history", {
|
|
163
|
+
symbol: symbol, coin: coin,
|
|
164
|
+
startTime: start_time, endTime: end_time, limit: limit, idLessThan: id_less_than
|
|
165
|
+
})
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def margin_isolated_liquidation_history(symbol: nil, start_time: nil, end_time: nil, limit: nil, id_less_than: nil)
|
|
169
|
+
get_signed("/api/v2/margin/isolated/liquidation-history", {
|
|
170
|
+
symbol: symbol, startTime: start_time, endTime: end_time, limit: limit, idLessThan: id_less_than
|
|
171
|
+
})
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# --- Futures ---
|
|
175
|
+
|
|
176
|
+
def futures_account_bills(product_type:, coin: nil, business: nil, start_time: nil, end_time: nil, limit: nil, id_less_than: nil)
|
|
177
|
+
get_signed("/api/v2/mix/account/bill", {
|
|
178
|
+
productType: product_type, coin: coin, business: business,
|
|
179
|
+
startTime: start_time, endTime: end_time, limit: limit, idLessThan: id_less_than
|
|
180
|
+
})
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def futures_fills_history(product_type:, symbol: nil, order_id: nil, start_time: nil, end_time: nil, limit: nil, id_less_than: nil)
|
|
184
|
+
get_signed("/api/v2/mix/order/fills-history", {
|
|
185
|
+
productType: product_type, symbol: symbol, orderId: order_id,
|
|
186
|
+
startTime: start_time, endTime: end_time, limit: limit, idLessThan: id_less_than
|
|
187
|
+
})
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# --- Earn ---
|
|
191
|
+
|
|
192
|
+
def earn_savings_assets(coin: nil, filter: nil)
|
|
193
|
+
get_signed("/api/v2/earn/savings/assets", { coin: coin, filter: filter })
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def earn_savings_subscribe_result(coin: nil, start_time: nil, end_time: nil, limit: nil, id_less_than: nil)
|
|
197
|
+
get_signed("/api/v2/earn/savings/subscribe-result", {
|
|
198
|
+
coin: coin, startTime: start_time, endTime: end_time, limit: limit, idLessThan: id_less_than
|
|
199
|
+
})
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def earn_savings_redeem_result(coin: nil, start_time: nil, end_time: nil, limit: nil, id_less_than: nil)
|
|
203
|
+
get_signed("/api/v2/earn/savings/redeem-result", {
|
|
204
|
+
coin: coin, startTime: start_time, endTime: end_time, limit: limit, idLessThan: id_less_than
|
|
205
|
+
})
|
|
206
|
+
end
|
|
207
|
+
|
|
85
208
|
private
|
|
86
209
|
|
|
210
|
+
def normalize_order(order_id, raw)
|
|
211
|
+
order_type = parse_order_type(raw["orderType"])
|
|
212
|
+
side = raw["side"]&.downcase&.to_sym
|
|
213
|
+
status = parse_order_status(raw["status"])
|
|
214
|
+
|
|
215
|
+
price = BigDecimal((raw["priceAvg"] || raw["price"] || "0").to_s)
|
|
216
|
+
price = nil if price.zero?
|
|
217
|
+
|
|
218
|
+
amount = raw["size"] ? BigDecimal(raw["size"].to_s) : nil
|
|
219
|
+
quote_amount = raw["quoteSize"] ? BigDecimal(raw["quoteSize"].to_s) : nil
|
|
220
|
+
amount_exec = BigDecimal((raw["baseVolume"] || "0").to_s)
|
|
221
|
+
quote_amount_exec = BigDecimal((raw["quoteVolume"] || "0").to_s)
|
|
222
|
+
|
|
223
|
+
{
|
|
224
|
+
order_id: order_id, status: status, side: side, order_type: order_type,
|
|
225
|
+
price: price, amount: amount, quote_amount: quote_amount,
|
|
226
|
+
amount_exec: amount_exec, quote_amount_exec: quote_amount_exec, raw: raw
|
|
227
|
+
}
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def parse_order_type(type)
|
|
231
|
+
case type&.downcase
|
|
232
|
+
when "market" then :market
|
|
233
|
+
when "limit" then :limit
|
|
234
|
+
else :unknown
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def parse_order_status(status)
|
|
239
|
+
case status
|
|
240
|
+
when "init", "new" then :unknown
|
|
241
|
+
when "partial_fill", "live" then :open
|
|
242
|
+
when "full_fill" then :closed
|
|
243
|
+
when "cancelled" then :cancelled
|
|
244
|
+
else :unknown
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
87
248
|
def validate_trading_credentials
|
|
88
249
|
result = get_account_assets
|
|
89
250
|
return Result::Failure.new("Invalid trading credentials") if result.failure?
|
|
@@ -4,6 +4,7 @@ module Honeymaker
|
|
|
4
4
|
module Clients
|
|
5
5
|
class BitMart < Client
|
|
6
6
|
URL = "https://api-cloud.bitmart.com"
|
|
7
|
+
RATE_LIMITS = { default: 100, orders: 200 }.freeze
|
|
7
8
|
|
|
8
9
|
attr_reader :memo
|
|
9
10
|
|
|
@@ -34,16 +35,46 @@ module Honeymaker
|
|
|
34
35
|
get_signed("/spot/v1/wallet")
|
|
35
36
|
end
|
|
36
37
|
|
|
38
|
+
def get_balances
|
|
39
|
+
result = get_wallet
|
|
40
|
+
return result if result.failure?
|
|
41
|
+
|
|
42
|
+
return Result::Failure.new("BitMart API error") unless result.data["code"] == 1000
|
|
43
|
+
|
|
44
|
+
balances = {}
|
|
45
|
+
(result.data.dig("data", "wallet") || []).each do |wallet|
|
|
46
|
+
symbol = wallet["id"]
|
|
47
|
+
free = BigDecimal((wallet["available"] || "0").to_s)
|
|
48
|
+
locked = BigDecimal((wallet["frozen"] || "0").to_s)
|
|
49
|
+
next if free.zero? && locked.zero?
|
|
50
|
+
balances[symbol] = { free: free, locked: locked }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
Result::Success.new(balances)
|
|
54
|
+
end
|
|
55
|
+
|
|
37
56
|
def submit_order(symbol:, side:, type:, size: nil, notional: nil, price: nil, client_order_id: nil)
|
|
38
|
-
post_signed("/spot/v2/submit_order", {
|
|
57
|
+
result = post_signed("/spot/v2/submit_order", {
|
|
39
58
|
symbol: symbol, side: side, type: type,
|
|
40
59
|
size: size, notional: notional, price: price,
|
|
41
60
|
client_order_id: client_order_id
|
|
42
61
|
})
|
|
62
|
+
return result if result.failure?
|
|
63
|
+
return Result::Failure.new("BitMart API error") unless result.data["code"] == 1000
|
|
64
|
+
|
|
65
|
+
order_id = result.data.dig("data", "order_id")
|
|
66
|
+
Result::Success.new({ order_id: order_id.to_s, raw: result.data })
|
|
43
67
|
end
|
|
44
68
|
|
|
45
69
|
def get_order(order_id:)
|
|
46
|
-
post_signed("/spot/v2/order_detail", { orderId: order_id })
|
|
70
|
+
result = post_signed("/spot/v2/order_detail", { orderId: order_id })
|
|
71
|
+
return result if result.failure?
|
|
72
|
+
return Result::Failure.new("BitMart API error") unless result.data["code"] == 1000
|
|
73
|
+
|
|
74
|
+
raw = result.data["data"]
|
|
75
|
+
return Result::Failure.new("Order not found") unless raw
|
|
76
|
+
|
|
77
|
+
Result::Success.new(normalize_order(order_id.to_s, raw))
|
|
47
78
|
end
|
|
48
79
|
|
|
49
80
|
def cancel_order(symbol:, order_id: nil, client_order_id: nil)
|
|
@@ -79,8 +110,83 @@ module Honeymaker
|
|
|
79
110
|
})
|
|
80
111
|
end
|
|
81
112
|
|
|
113
|
+
# --- Margin ---
|
|
114
|
+
|
|
115
|
+
def margin_borrow_records(symbol: nil, borrow_id: nil, start_time: nil, end_time: nil, n: nil)
|
|
116
|
+
get_signed("/spot/v1/margin/isolated/borrow_record", {
|
|
117
|
+
symbol: symbol, borrow_id: borrow_id, start_time: start_time, end_time: end_time, N: n
|
|
118
|
+
})
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def margin_repay_records(symbol: nil, repay_id: nil, currency: nil, start_time: nil, end_time: nil, n: nil)
|
|
122
|
+
get_signed("/spot/v1/margin/isolated/repay_record", {
|
|
123
|
+
symbol: symbol, repay_id: repay_id, currency: currency, start_time: start_time, end_time: end_time, N: n
|
|
124
|
+
})
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# --- Futures ---
|
|
128
|
+
|
|
129
|
+
def futures_transaction_history(start_time: nil, end_time: nil, page_num: nil, page_size: nil, flow_type: nil)
|
|
130
|
+
get_signed("/contract/private/transaction-history", {
|
|
131
|
+
start_time: start_time, end_time: end_time,
|
|
132
|
+
page_num: page_num, page_size: page_size, flow_type: flow_type
|
|
133
|
+
})
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def futures_trades(symbol:, start_time: nil, end_time: nil, page_num: nil, page_size: nil)
|
|
137
|
+
get_signed("/contract/private/trades", {
|
|
138
|
+
symbol: symbol, start_time: start_time, end_time: end_time,
|
|
139
|
+
page_num: page_num, page_size: page_size
|
|
140
|
+
})
|
|
141
|
+
end
|
|
142
|
+
|
|
82
143
|
private
|
|
83
144
|
|
|
145
|
+
def normalize_order(order_id, raw)
|
|
146
|
+
order_type = parse_order_type(raw["type"])
|
|
147
|
+
side = raw["side"]&.downcase&.to_sym
|
|
148
|
+
status = parse_order_status(raw["status"])
|
|
149
|
+
|
|
150
|
+
amount = BigDecimal((raw["size"] || "0").to_s)
|
|
151
|
+
amount = nil if amount.zero?
|
|
152
|
+
quote_amount = raw["notional"] ? BigDecimal(raw["notional"].to_s) : nil
|
|
153
|
+
quote_amount = nil if quote_amount&.zero?
|
|
154
|
+
|
|
155
|
+
amount_exec = BigDecimal((raw["filled_size"] || "0").to_s)
|
|
156
|
+
quote_amount_exec = BigDecimal((raw["filled_notional"] || "0").to_s)
|
|
157
|
+
quote_amount_exec = nil if quote_amount_exec.negative?
|
|
158
|
+
|
|
159
|
+
price = BigDecimal((raw["price"] || "0").to_s)
|
|
160
|
+
if price.zero? && quote_amount_exec&.positive? && amount_exec.positive?
|
|
161
|
+
price = quote_amount_exec / amount_exec
|
|
162
|
+
end
|
|
163
|
+
price = nil if price.zero?
|
|
164
|
+
|
|
165
|
+
{
|
|
166
|
+
order_id: order_id, status: status, side: side, order_type: order_type,
|
|
167
|
+
price: price, amount: amount, quote_amount: quote_amount,
|
|
168
|
+
amount_exec: amount_exec, quote_amount_exec: quote_amount_exec, raw: raw
|
|
169
|
+
}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def parse_order_type(type)
|
|
173
|
+
case type
|
|
174
|
+
when "market" then :market
|
|
175
|
+
when "limit" then :limit
|
|
176
|
+
else :unknown
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def parse_order_status(status)
|
|
181
|
+
case status
|
|
182
|
+
when "new", "partially_filled" then :open
|
|
183
|
+
when "filled" then :closed
|
|
184
|
+
when "canceled", "expired", "partially_canceled" then :cancelled
|
|
185
|
+
when "rejected", "failed" then :failed
|
|
186
|
+
else :unknown
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
84
190
|
def validate_trading_credentials
|
|
85
191
|
result = get_wallet
|
|
86
192
|
return Result::Failure.new("Invalid trading credentials") if result.failure?
|
|
@@ -4,6 +4,7 @@ module Honeymaker
|
|
|
4
4
|
module Clients
|
|
5
5
|
class Bitrue < Client
|
|
6
6
|
URL = "https://openapi.bitrue.com"
|
|
7
|
+
RATE_LIMITS = { default: 100, orders: 200 }.freeze
|
|
7
8
|
|
|
8
9
|
def exchange_information
|
|
9
10
|
get_public("/api/v1/exchangeInfo")
|
|
@@ -28,21 +29,45 @@ module Honeymaker
|
|
|
28
29
|
get_signed("/api/v1/account", { recvWindow: recv_window })
|
|
29
30
|
end
|
|
30
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
|
+
|
|
31
48
|
def query_order(symbol:, order_id: nil, orig_client_order_id: nil, recv_window: 5000)
|
|
32
|
-
get_signed("/api/v1/order", {
|
|
49
|
+
result = get_signed("/api/v1/order", {
|
|
33
50
|
symbol: symbol, orderId: order_id,
|
|
34
51
|
origClientOrderId: orig_client_order_id, recvWindow: recv_window
|
|
35
52
|
})
|
|
53
|
+
return result if result.failure?
|
|
54
|
+
|
|
55
|
+
raw = result.data
|
|
56
|
+
Result::Success.new(normalize_order("#{symbol}-#{raw['orderId']}", raw))
|
|
36
57
|
end
|
|
37
58
|
|
|
38
59
|
def new_order(symbol:, side:, type:, time_in_force: nil, quantity: nil, quote_order_qty: nil,
|
|
39
60
|
price: nil, new_client_order_id: nil, recv_window: 5000)
|
|
40
|
-
post_signed("/api/v1/order", {
|
|
61
|
+
result = post_signed("/api/v1/order", {
|
|
41
62
|
symbol: symbol, side: side, type: type,
|
|
42
63
|
timeInForce: time_in_force, quantity: quantity,
|
|
43
64
|
quoteOrderQty: quote_order_qty, price: price,
|
|
44
65
|
newClientOrderId: new_client_order_id, recvWindow: recv_window
|
|
45
66
|
})
|
|
67
|
+
return result if result.failure?
|
|
68
|
+
|
|
69
|
+
raw = result.data
|
|
70
|
+
Result::Success.new({ order_id: "#{symbol}-#{raw['orderId']}", raw: raw })
|
|
46
71
|
end
|
|
47
72
|
|
|
48
73
|
def cancel_order(symbol:, order_id: nil, orig_client_order_id: nil, recv_window: 5000)
|
|
@@ -59,8 +84,71 @@ module Honeymaker
|
|
|
59
84
|
})
|
|
60
85
|
end
|
|
61
86
|
|
|
87
|
+
# --- Futures ---
|
|
88
|
+
|
|
89
|
+
def futures_account(recv_window: 5000)
|
|
90
|
+
with_rescue do
|
|
91
|
+
response = futures_connection.get do |req|
|
|
92
|
+
req.url "/fapi/v1/account"
|
|
93
|
+
req.headers = auth_headers
|
|
94
|
+
req.params = { recvWindow: recv_window, timestamp: timestamp_ms }.compact
|
|
95
|
+
req.params[:signature] = sign_params(req.params)
|
|
96
|
+
end
|
|
97
|
+
response.body
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
62
101
|
private
|
|
63
102
|
|
|
103
|
+
def futures_connection
|
|
104
|
+
@futures_connection ||= build_client_connection("https://fapi.bitrue.com")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def normalize_order(order_id, raw)
|
|
108
|
+
order_type = parse_order_type(raw["type"])
|
|
109
|
+
side = raw["side"]&.downcase&.to_sym
|
|
110
|
+
status = parse_order_status(raw["status"])
|
|
111
|
+
|
|
112
|
+
amount = BigDecimal((raw["origQty"] || "0").to_s)
|
|
113
|
+
amount = nil if amount.zero?
|
|
114
|
+
quote_amount = raw["origQuoteOrderQty"] ? BigDecimal(raw["origQuoteOrderQty"].to_s) : nil
|
|
115
|
+
quote_amount = nil if quote_amount&.zero?
|
|
116
|
+
|
|
117
|
+
amount_exec = BigDecimal((raw["executedQty"] || "0").to_s)
|
|
118
|
+
quote_amount_exec = BigDecimal((raw["cummulativeQuoteQty"] || "0").to_s)
|
|
119
|
+
quote_amount_exec = nil if quote_amount_exec.negative?
|
|
120
|
+
|
|
121
|
+
price = BigDecimal((raw["price"] || "0").to_s)
|
|
122
|
+
if price.zero? && quote_amount_exec&.positive? && amount_exec.positive?
|
|
123
|
+
price = quote_amount_exec / amount_exec
|
|
124
|
+
end
|
|
125
|
+
price = nil if price.zero?
|
|
126
|
+
|
|
127
|
+
{
|
|
128
|
+
order_id: order_id, status: status, side: side, order_type: order_type,
|
|
129
|
+
price: price, amount: amount, quote_amount: quote_amount,
|
|
130
|
+
amount_exec: amount_exec, quote_amount_exec: quote_amount_exec, raw: raw
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def parse_order_type(type)
|
|
135
|
+
case type
|
|
136
|
+
when "MARKET" then :market
|
|
137
|
+
when "LIMIT" then :limit
|
|
138
|
+
else :unknown
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def parse_order_status(status)
|
|
143
|
+
case status
|
|
144
|
+
when "NEW", "PARTIALLY_FILLED" then :open
|
|
145
|
+
when "FILLED" then :closed
|
|
146
|
+
when "CANCELED", "EXPIRED" then :cancelled
|
|
147
|
+
when "REJECTED" then :failed
|
|
148
|
+
else :unknown
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
64
152
|
def validate_trading_credentials
|
|
65
153
|
result = account_information
|
|
66
154
|
result.success? ? Result::Success.new(true) : Result::Failure.new("Invalid trading credentials")
|
|
@@ -5,6 +5,7 @@ module Honeymaker
|
|
|
5
5
|
class Bitvavo < Client
|
|
6
6
|
URL = "https://api.bitvavo.com"
|
|
7
7
|
ACCESS_WINDOW = "10000"
|
|
8
|
+
RATE_LIMITS = { default: 100, orders: 100 }.freeze
|
|
8
9
|
|
|
9
10
|
def get_assets
|
|
10
11
|
get_public("/v2/assets")
|
|
@@ -28,21 +29,46 @@ module Honeymaker
|
|
|
28
29
|
})
|
|
29
30
|
end
|
|
30
31
|
|
|
31
|
-
def
|
|
32
|
+
def get_raw_balance(symbol: nil)
|
|
32
33
|
get_signed("/v2/balance", { symbol: symbol })
|
|
33
34
|
end
|
|
34
35
|
|
|
36
|
+
def get_balances
|
|
37
|
+
result = get_raw_balance
|
|
38
|
+
return result if result.failure?
|
|
39
|
+
|
|
40
|
+
balances = {}
|
|
41
|
+
Array(result.data).each do |balance|
|
|
42
|
+
symbol = balance["symbol"]
|
|
43
|
+
next unless symbol
|
|
44
|
+
free = BigDecimal((balance["available"] || "0").to_s)
|
|
45
|
+
locked = BigDecimal((balance["inOrder"] || "0").to_s)
|
|
46
|
+
next if free.zero? && locked.zero?
|
|
47
|
+
balances[symbol] = { free: free, locked: locked }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
Result::Success.new(balances)
|
|
51
|
+
end
|
|
52
|
+
|
|
35
53
|
def place_order(market:, side:, order_type:, amount: nil, amount_quote: nil, price: nil,
|
|
36
54
|
time_in_force: nil, client_order_id: nil)
|
|
37
|
-
post_signed("/v2/order", {
|
|
55
|
+
result = post_signed("/v2/order", {
|
|
38
56
|
market: market, side: side, orderType: order_type,
|
|
39
57
|
amount: amount, amountQuote: amount_quote, price: price,
|
|
40
58
|
timeInForce: time_in_force, clientOrderId: client_order_id
|
|
41
59
|
})
|
|
60
|
+
return result if result.failure?
|
|
61
|
+
|
|
62
|
+
raw = result.data
|
|
63
|
+
Result::Success.new({ order_id: "#{raw['market']}-#{raw['orderId']}", raw: raw })
|
|
42
64
|
end
|
|
43
65
|
|
|
44
66
|
def get_order(market:, order_id:)
|
|
45
|
-
get_signed("/v2/order", { market: market, orderId: order_id })
|
|
67
|
+
result = get_signed("/v2/order", { market: market, orderId: order_id })
|
|
68
|
+
return result if result.failure?
|
|
69
|
+
|
|
70
|
+
raw = result.data
|
|
71
|
+
Result::Success.new(normalize_order("#{raw['market']}-#{raw['orderId']}", raw))
|
|
46
72
|
end
|
|
47
73
|
|
|
48
74
|
def cancel_order(market:, order_id:)
|
|
@@ -70,10 +96,60 @@ module Honeymaker
|
|
|
70
96
|
})
|
|
71
97
|
end
|
|
72
98
|
|
|
99
|
+
# --- History ---
|
|
100
|
+
|
|
101
|
+
def get_transactions(limit: nil, start_time: nil, end_time: nil)
|
|
102
|
+
get_signed("/v2/transactions", { limit: limit, start: start_time, end: end_time })
|
|
103
|
+
end
|
|
104
|
+
|
|
73
105
|
private
|
|
74
106
|
|
|
107
|
+
def normalize_order(order_id, raw)
|
|
108
|
+
order_type = parse_order_type(raw["orderType"])
|
|
109
|
+
side = raw["side"]&.downcase&.to_sym
|
|
110
|
+
status = parse_order_status(raw["status"])
|
|
111
|
+
|
|
112
|
+
price = BigDecimal((raw["price"] || "0").to_s)
|
|
113
|
+
amount = BigDecimal((raw["amount"] || "0").to_s)
|
|
114
|
+
amount = nil if amount.zero?
|
|
115
|
+
amount_quote = BigDecimal((raw["amountQuote"] || "0").to_s)
|
|
116
|
+
amount_quote = nil if amount_quote.zero?
|
|
117
|
+
|
|
118
|
+
amount_exec = BigDecimal((raw["filledAmount"] || "0").to_s)
|
|
119
|
+
quote_amount_exec = BigDecimal((raw["filledAmountQuote"] || "0").to_s)
|
|
120
|
+
|
|
121
|
+
if price.zero? && quote_amount_exec.positive? && amount_exec.positive?
|
|
122
|
+
price = quote_amount_exec / amount_exec
|
|
123
|
+
end
|
|
124
|
+
price = nil if price.zero?
|
|
125
|
+
|
|
126
|
+
{
|
|
127
|
+
order_id: order_id, status: status, side: side, order_type: order_type,
|
|
128
|
+
price: price, amount: amount, quote_amount: amount_quote,
|
|
129
|
+
amount_exec: amount_exec, quote_amount_exec: quote_amount_exec, raw: raw
|
|
130
|
+
}
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parse_order_type(type)
|
|
134
|
+
case type
|
|
135
|
+
when "market" then :market
|
|
136
|
+
when "limit" then :limit
|
|
137
|
+
else :unknown
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def parse_order_status(status)
|
|
142
|
+
case status
|
|
143
|
+
when "new", "partiallyFilled" then :open
|
|
144
|
+
when "filled" then :closed
|
|
145
|
+
when "canceled", "cancelled", "expired" then :cancelled
|
|
146
|
+
when "rejected" then :failed
|
|
147
|
+
else :unknown
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
75
151
|
def validate_trading_credentials
|
|
76
|
-
result =
|
|
152
|
+
result = get_raw_balance
|
|
77
153
|
result.success? ? Result::Success.new(true) : Result::Failure.new("Invalid trading credentials")
|
|
78
154
|
end
|
|
79
155
|
|