honeymaker 0.9.11 → 0.10.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 +1 -1
- data/lib/honeymaker/exchanges/hyperliquid.rb +6 -1
- data/lib/honeymaker/version.rb +1 -1
- data/lib/honeymaker.rb +1 -5
- data/test/honeymaker/clients/honeymaker_client_registry_test.rb +1 -6
- data/test/honeymaker/clients/validation_test.rb +0 -15
- data/test/honeymaker/exchanges/hyperliquid_test.rb +2 -2
- data/test/honeymaker/honeymaker_test.rb +1 -1
- metadata +1 -5
- data/lib/honeymaker/clients/bitmart.rb +0 -260
- data/lib/honeymaker/exchanges/bitmart.rb +0 -52
- data/test/honeymaker/clients/bitmart_client_test.rb +0 -101
- data/test/honeymaker/exchanges/bitmart_test.rb +0 -65
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bbfab70631b45882e86a1f4af9ea69b9c32c98840a383183b69ebae133b2f3bf
|
|
4
|
+
data.tar.gz: 3d65a28ae18b85f5de60959aac90e327471ff617488956538442b6841ddc4bd9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 00f3d5d33b5c1d231be4f1bafb5815083bc26cafae31a8cfe2c4403be217ece9120f7195bc6f5fa15d14286950c9f8817762767a4824f0c29396a6b679ca483e
|
|
7
|
+
data.tar.gz: 3209e5756f1aab9c2af0537c533b3b2dbadf468c635c5f882d4f85362679597495d78f9ec77a2f7ca1b10a8c1c534687d19b95be73989ce88fe23fbf5f03120a
|
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Ruby clients for cryptocurrency exchange APIs used by [Deltabadger](https://gith
|
|
|
8
8
|
|
|
9
9
|
## Supported Exchanges
|
|
10
10
|
|
|
11
|
-
Binance, Binance US, Kraken, Kraken Futures, Coinbase, Bybit, KuCoin, Bitget, MEXC, Bitvavo, Gemini, Hyperliquid, BingX, Bitrue
|
|
11
|
+
Binance, Binance US, Kraken, Kraken Futures, Coinbase, Bybit, KuCoin, Bitget, MEXC, Bitvavo, Gemini, Hyperliquid, BingX, Bitrue.
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
@@ -5,6 +5,11 @@ module Honeymaker
|
|
|
5
5
|
class Hyperliquid < Exchange
|
|
6
6
|
BASE_URL = "https://api.hyperliquid.xyz"
|
|
7
7
|
|
|
8
|
+
# Hyperliquid enforces a hard 10-USDC minimum order value on every spot pair (spotMeta exposes
|
|
9
|
+
# no per-token base floor). Surfaced as minimum_quote_size so consumers skip/reject sub-10
|
|
10
|
+
# orders up front instead of having the exchange reject them.
|
|
11
|
+
MINIMUM_QUOTE_SIZE = 10
|
|
12
|
+
|
|
8
13
|
def get_tickers_info
|
|
9
14
|
with_rescue do
|
|
10
15
|
response = connection.post("/info") do |req|
|
|
@@ -25,7 +30,7 @@ module Honeymaker
|
|
|
25
30
|
base: base_token["name"],
|
|
26
31
|
quote: quote_token["name"],
|
|
27
32
|
minimum_base_size: nil,
|
|
28
|
-
minimum_quote_size:
|
|
33
|
+
minimum_quote_size: MINIMUM_QUOTE_SIZE,
|
|
29
34
|
maximum_base_size: nil,
|
|
30
35
|
maximum_quote_size: nil,
|
|
31
36
|
base_decimals: base_token["szDecimals"] || 0,
|
data/lib/honeymaker/version.rb
CHANGED
data/lib/honeymaker.rb
CHANGED
|
@@ -21,7 +21,6 @@ require_relative "honeymaker/clients/bitvavo"
|
|
|
21
21
|
require_relative "honeymaker/clients/gemini"
|
|
22
22
|
require_relative "honeymaker/clients/bingx"
|
|
23
23
|
require_relative "honeymaker/clients/bitrue"
|
|
24
|
-
require_relative "honeymaker/clients/bitmart"
|
|
25
24
|
require_relative "honeymaker/clients/hyperliquid"
|
|
26
25
|
require_relative "honeymaker/clients/kraken_futures"
|
|
27
26
|
require_relative "honeymaker/exchanges/binance"
|
|
@@ -37,7 +36,6 @@ require_relative "honeymaker/exchanges/kucoin"
|
|
|
37
36
|
require_relative "honeymaker/exchanges/hyperliquid"
|
|
38
37
|
require_relative "honeymaker/exchanges/bingx"
|
|
39
38
|
require_relative "honeymaker/exchanges/bitrue"
|
|
40
|
-
require_relative "honeymaker/exchanges/bitmart"
|
|
41
39
|
|
|
42
40
|
module Honeymaker
|
|
43
41
|
class Error < StandardError; end
|
|
@@ -55,8 +53,7 @@ module Honeymaker
|
|
|
55
53
|
"kucoin" => Exchanges::Kucoin,
|
|
56
54
|
"hyperliquid" => Exchanges::Hyperliquid,
|
|
57
55
|
"bingx" => Exchanges::BingX,
|
|
58
|
-
"bitrue" => Exchanges::Bitrue
|
|
59
|
-
"bitmart" => Exchanges::BitMart
|
|
56
|
+
"bitrue" => Exchanges::Bitrue
|
|
60
57
|
}.freeze
|
|
61
58
|
|
|
62
59
|
CLIENTS = {
|
|
@@ -72,7 +69,6 @@ module Honeymaker
|
|
|
72
69
|
"gemini" => Clients::Gemini,
|
|
73
70
|
"bingx" => Clients::BingX,
|
|
74
71
|
"bitrue" => Clients::Bitrue,
|
|
75
|
-
"bitmart" => Clients::BitMart,
|
|
76
72
|
"hyperliquid" => Clients::Hyperliquid,
|
|
77
73
|
"kraken_futures" => Clients::KrakenFutures
|
|
78
74
|
}.freeze
|
|
@@ -18,7 +18,7 @@ class HoneymakerClientRegistryTest < Minitest::Test
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def test_all_clients_registered
|
|
21
|
-
assert_equal
|
|
21
|
+
assert_equal 14, Honeymaker::CLIENTS.size
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def test_client_passes_credentials
|
|
@@ -36,9 +36,4 @@ class HoneymakerClientRegistryTest < Minitest::Test
|
|
|
36
36
|
client = Honeymaker.client("kucoin", api_key: "k", api_secret: "s", passphrase: "p")
|
|
37
37
|
assert_equal "p", client.passphrase
|
|
38
38
|
end
|
|
39
|
-
|
|
40
|
-
def test_bitmart_accepts_memo
|
|
41
|
-
client = Honeymaker.client("bitmart", api_key: "k", api_secret: "s", memo: "m")
|
|
42
|
-
assert_equal "m", client.memo
|
|
43
|
-
end
|
|
44
39
|
end
|
|
@@ -148,21 +148,6 @@ class ValidationTest < Minitest::Test
|
|
|
148
148
|
assert result.success?
|
|
149
149
|
end
|
|
150
150
|
|
|
151
|
-
# BitMart
|
|
152
|
-
def test_bitmart_validate_trading_success
|
|
153
|
-
client = Honeymaker::Clients::BitMart.new(api_key: "k", api_secret: "s", memo: "m")
|
|
154
|
-
stub_connection(client, :get, { "code" => 1000, "data" => {} })
|
|
155
|
-
result = client.validate(:trading)
|
|
156
|
-
assert result.success?
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def test_bitmart_validate_trading_failure
|
|
160
|
-
client = Honeymaker::Clients::BitMart.new(api_key: "k", api_secret: "s", memo: "m")
|
|
161
|
-
stub_connection(client, :get, { "code" => 30004, "message" => "Unauthorized" })
|
|
162
|
-
result = client.validate(:trading)
|
|
163
|
-
assert result.failure?
|
|
164
|
-
end
|
|
165
|
-
|
|
166
151
|
# Hyperliquid
|
|
167
152
|
def test_hyperliquid_validate_trading_success
|
|
168
153
|
client = Honeymaker::Clients::Hyperliquid.new(api_key: "0xabc", api_secret: "s")
|
|
@@ -22,8 +22,8 @@ class Honeymaker::Exchanges::HyperliquidTest < Minitest::Test
|
|
|
22
22
|
assert_equal "PURR/USDC", ticker[:ticker]
|
|
23
23
|
assert_equal "PURR", ticker[:base]
|
|
24
24
|
assert_equal "USDC", ticker[:quote]
|
|
25
|
-
assert_nil ticker[:minimum_base_size]
|
|
26
|
-
|
|
25
|
+
assert_nil ticker[:minimum_base_size] # no per-token base floor in spotMeta
|
|
26
|
+
assert_equal 10, ticker[:minimum_quote_size] # Hyperliquid's hard 10-USDC spot order floor
|
|
27
27
|
assert_equal 2, ticker[:base_decimals]
|
|
28
28
|
assert_equal 2, ticker[:quote_decimals]
|
|
29
29
|
assert_equal 5, ticker[:price_decimals]
|
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.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Deltabadger
|
|
@@ -98,7 +98,6 @@ files:
|
|
|
98
98
|
- lib/honeymaker/clients/binance_us.rb
|
|
99
99
|
- lib/honeymaker/clients/bingx.rb
|
|
100
100
|
- lib/honeymaker/clients/bitget.rb
|
|
101
|
-
- lib/honeymaker/clients/bitmart.rb
|
|
102
101
|
- lib/honeymaker/clients/bitrue.rb
|
|
103
102
|
- lib/honeymaker/clients/bitvavo.rb
|
|
104
103
|
- lib/honeymaker/clients/bybit.rb
|
|
@@ -114,7 +113,6 @@ files:
|
|
|
114
113
|
- lib/honeymaker/exchanges/binance_us.rb
|
|
115
114
|
- lib/honeymaker/exchanges/bingx.rb
|
|
116
115
|
- lib/honeymaker/exchanges/bitget.rb
|
|
117
|
-
- lib/honeymaker/exchanges/bitmart.rb
|
|
118
116
|
- lib/honeymaker/exchanges/bitrue.rb
|
|
119
117
|
- lib/honeymaker/exchanges/bitvavo.rb
|
|
120
118
|
- lib/honeymaker/exchanges/bybit.rb
|
|
@@ -160,7 +158,6 @@ files:
|
|
|
160
158
|
- test/honeymaker/clients/binance_us_client_test.rb
|
|
161
159
|
- test/honeymaker/clients/bingx_client_test.rb
|
|
162
160
|
- test/honeymaker/clients/bitget_client_test.rb
|
|
163
|
-
- test/honeymaker/clients/bitmart_client_test.rb
|
|
164
161
|
- test/honeymaker/clients/bitrue_client_test.rb
|
|
165
162
|
- test/honeymaker/clients/bitvavo_client_test.rb
|
|
166
163
|
- test/honeymaker/clients/bybit_client_test.rb
|
|
@@ -178,7 +175,6 @@ files:
|
|
|
178
175
|
- test/honeymaker/exchanges/binance_us_test.rb
|
|
179
176
|
- test/honeymaker/exchanges/bingx_test.rb
|
|
180
177
|
- test/honeymaker/exchanges/bitget_test.rb
|
|
181
|
-
- test/honeymaker/exchanges/bitmart_test.rb
|
|
182
178
|
- test/honeymaker/exchanges/bitrue_test.rb
|
|
183
179
|
- test/honeymaker/exchanges/bitvavo_test.rb
|
|
184
180
|
- test/honeymaker/exchanges/bybit_test.rb
|
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Honeymaker
|
|
4
|
-
module Clients
|
|
5
|
-
class BitMart < Client
|
|
6
|
-
URL = "https://api-cloud.bitmart.com"
|
|
7
|
-
RATE_LIMITS = { default: 100, orders: 200 }.freeze
|
|
8
|
-
|
|
9
|
-
attr_reader :memo
|
|
10
|
-
|
|
11
|
-
def initialize(api_key: nil, api_secret: nil, memo: nil, proxy: nil, logger: nil)
|
|
12
|
-
super(api_key: api_key, api_secret: api_secret, proxy: proxy, logger: logger)
|
|
13
|
-
@memo = memo
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def get_symbols_details
|
|
17
|
-
get_public("/spot/v1/symbols/details")
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def get_currencies
|
|
21
|
-
get_public("/account/v1/currencies")
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def get_ticker(symbol: nil)
|
|
25
|
-
get_public("/spot/quotation/v3/ticker", { symbol: symbol })
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def get_depth(symbol:, limit: nil)
|
|
29
|
-
get_public("/spot/quotation/v3/books", { symbol: symbol, limit: limit })
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def get_klines(symbol:, step:, before: nil, after_time: nil, limit: nil)
|
|
33
|
-
get_public("/spot/quotation/v3/lite-klines", {
|
|
34
|
-
symbol: symbol, step: step, before: before, after: after_time, limit: limit
|
|
35
|
-
})
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def get_wallet
|
|
39
|
-
get_signed("/spot/v1/wallet")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def get_balances
|
|
43
|
-
result = get_wallet
|
|
44
|
-
return result if result.failure?
|
|
45
|
-
|
|
46
|
-
return Result::Failure.new("BitMart API error") unless result.data["code"] == 1000
|
|
47
|
-
|
|
48
|
-
balances = {}
|
|
49
|
-
(result.data.dig("data", "wallet") || []).each do |wallet|
|
|
50
|
-
symbol = wallet["id"]
|
|
51
|
-
free = BigDecimal((wallet["available"] || "0").to_s)
|
|
52
|
-
locked = BigDecimal((wallet["frozen"] || "0").to_s)
|
|
53
|
-
next if free.zero? && locked.zero?
|
|
54
|
-
balances[symbol] = { free: free, locked: locked }
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
Result::Success.new(balances)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def submit_order(symbol:, side:, type:, size: nil, notional: nil, price: nil, client_order_id: nil)
|
|
61
|
-
result = post_signed("/spot/v2/submit_order", {
|
|
62
|
-
symbol: symbol, side: side, type: type,
|
|
63
|
-
size: size, notional: notional, price: price,
|
|
64
|
-
client_order_id: client_order_id
|
|
65
|
-
})
|
|
66
|
-
return result if result.failure?
|
|
67
|
-
return Result::Failure.new("BitMart API error") unless result.data["code"] == 1000
|
|
68
|
-
|
|
69
|
-
order_id = result.data.dig("data", "order_id")
|
|
70
|
-
Result::Success.new({ order_id: order_id.to_s, raw: result.data })
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def get_order(order_id:)
|
|
74
|
-
result = post_signed("/spot/v2/order_detail", { orderId: order_id })
|
|
75
|
-
return result if result.failure?
|
|
76
|
-
return Result::Failure.new("BitMart API error") unless result.data["code"] == 1000
|
|
77
|
-
|
|
78
|
-
raw = result.data["data"]
|
|
79
|
-
return Result::Failure.new("Order not found") unless raw
|
|
80
|
-
|
|
81
|
-
Result::Success.new(normalize_order(order_id.to_s, raw))
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def cancel_order(symbol:, order_id: nil, client_order_id: nil)
|
|
85
|
-
post_signed("/spot/v3/cancel_order", {
|
|
86
|
-
symbol: symbol, order_id: order_id, client_order_id: client_order_id
|
|
87
|
-
})
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def get_trades(symbol:, order_mode: nil, start_time: nil, end_time: nil, limit: nil, recv_window: nil)
|
|
91
|
-
get_signed("/spot/v2/trades", {
|
|
92
|
-
symbol: symbol, orderMode: order_mode,
|
|
93
|
-
startTime: start_time, endTime: end_time, N: limit, recvWindow: recv_window
|
|
94
|
-
})
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def deposit_list(currency: nil, n: nil, status: nil)
|
|
98
|
-
get_signed("/account/v1/deposit-withdraw/detail", {
|
|
99
|
-
currency: currency, N: n, type: "deposit", operation_type: "deposit", status: status
|
|
100
|
-
})
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def withdraw_list(currency: nil, n: nil, status: nil)
|
|
104
|
-
get_signed("/account/v1/deposit-withdraw/detail", {
|
|
105
|
-
currency: currency, N: n, type: "withdraw", operation_type: "withdraw", status: status
|
|
106
|
-
})
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def get_withdraw_addresses
|
|
110
|
-
get_signed("/account/v1/withdraw/address/list")
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def withdraw(currency:, amount:, address:, address_memo: nil, destination: nil)
|
|
114
|
-
post_signed("/account/v1/withdraw/apply", {
|
|
115
|
-
currency: currency, amount: amount,
|
|
116
|
-
destination: destination || "To Digital Address",
|
|
117
|
-
address: address, address_memo: address_memo
|
|
118
|
-
})
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# --- Margin ---
|
|
122
|
-
|
|
123
|
-
def margin_borrow_records(symbol: nil, borrow_id: nil, start_time: nil, end_time: nil, n: nil)
|
|
124
|
-
get_signed("/spot/v1/margin/isolated/borrow_record", {
|
|
125
|
-
symbol: symbol, borrow_id: borrow_id, start_time: start_time, end_time: end_time, N: n
|
|
126
|
-
})
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def margin_repay_records(symbol: nil, repay_id: nil, currency: nil, start_time: nil, end_time: nil, n: nil)
|
|
130
|
-
get_signed("/spot/v1/margin/isolated/repay_record", {
|
|
131
|
-
symbol: symbol, repay_id: repay_id, currency: currency, start_time: start_time, end_time: end_time, N: n
|
|
132
|
-
})
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# --- Futures ---
|
|
136
|
-
|
|
137
|
-
def futures_transaction_history(start_time: nil, end_time: nil, page_num: nil, page_size: nil, flow_type: nil)
|
|
138
|
-
get_signed("/contract/private/transaction-history", {
|
|
139
|
-
start_time: start_time, end_time: end_time,
|
|
140
|
-
page_num: page_num, page_size: page_size, flow_type: flow_type
|
|
141
|
-
})
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def futures_trades(symbol:, start_time: nil, end_time: nil, page_num: nil, page_size: nil)
|
|
145
|
-
get_signed("/contract/private/trades", {
|
|
146
|
-
symbol: symbol, start_time: start_time, end_time: end_time,
|
|
147
|
-
page_num: page_num, page_size: page_size
|
|
148
|
-
})
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
private
|
|
152
|
-
|
|
153
|
-
def normalize_order(order_id, raw)
|
|
154
|
-
order_type = parse_order_type(raw["type"])
|
|
155
|
-
side = raw["side"]&.downcase&.to_sym
|
|
156
|
-
status = parse_order_status(raw["status"])
|
|
157
|
-
|
|
158
|
-
amount = BigDecimal((raw["size"] || "0").to_s)
|
|
159
|
-
amount = nil if amount.zero?
|
|
160
|
-
quote_amount = raw["notional"] ? BigDecimal(raw["notional"].to_s) : nil
|
|
161
|
-
quote_amount = nil if quote_amount&.zero?
|
|
162
|
-
|
|
163
|
-
amount_exec = BigDecimal((raw["filled_size"] || "0").to_s)
|
|
164
|
-
quote_amount_exec = BigDecimal((raw["filled_notional"] || "0").to_s)
|
|
165
|
-
quote_amount_exec = nil if quote_amount_exec.negative?
|
|
166
|
-
|
|
167
|
-
price = BigDecimal((raw["price"] || "0").to_s)
|
|
168
|
-
if price.zero? && quote_amount_exec&.positive? && amount_exec.positive?
|
|
169
|
-
price = quote_amount_exec / amount_exec
|
|
170
|
-
end
|
|
171
|
-
price = nil if price.zero?
|
|
172
|
-
|
|
173
|
-
{
|
|
174
|
-
order_id: order_id, status: status, side: side, order_type: order_type,
|
|
175
|
-
price: price, amount: amount, quote_amount: quote_amount,
|
|
176
|
-
amount_exec: amount_exec, quote_amount_exec: quote_amount_exec, raw: raw
|
|
177
|
-
}
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def parse_order_type(type)
|
|
181
|
-
case type
|
|
182
|
-
when "market" then :market
|
|
183
|
-
when "limit" then :limit
|
|
184
|
-
else :unknown
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def parse_order_status(status)
|
|
189
|
-
case status
|
|
190
|
-
when "new", "partially_filled" then :open
|
|
191
|
-
when "filled" then :closed
|
|
192
|
-
when "canceled", "expired", "partially_canceled" then :cancelled
|
|
193
|
-
when "rejected", "failed" then :failed
|
|
194
|
-
else :unknown
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def validate_trading_credentials
|
|
199
|
-
result = get_wallet
|
|
200
|
-
return Result::Failure.new("Invalid trading credentials") if result.failure?
|
|
201
|
-
result.data["code"] == 1000 ? Result::Success.new(true) : Result::Failure.new("Invalid trading credentials")
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def validate_read_credentials
|
|
205
|
-
validate_trading_credentials
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def get_public(path, params = {})
|
|
209
|
-
with_rescue do
|
|
210
|
-
response = connection.get do |req|
|
|
211
|
-
req.url path
|
|
212
|
-
req.params = params.compact
|
|
213
|
-
end
|
|
214
|
-
response.body
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
def get_signed(path, params = {})
|
|
219
|
-
with_rescue do
|
|
220
|
-
ts = timestamp_ms.to_s
|
|
221
|
-
query_string = params.compact.empty? ? "" : "?#{Faraday::Utils.build_query(params.compact)}"
|
|
222
|
-
pre_sign = "#{ts}##{@memo}##{query_string}"
|
|
223
|
-
|
|
224
|
-
response = connection.get do |req|
|
|
225
|
-
req.url path
|
|
226
|
-
req.headers = signed_headers(ts, pre_sign)
|
|
227
|
-
req.params = params.compact
|
|
228
|
-
end
|
|
229
|
-
response.body
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
def post_signed(path, body = {})
|
|
234
|
-
with_rescue do
|
|
235
|
-
ts = timestamp_ms.to_s
|
|
236
|
-
body_json = body.compact.to_json
|
|
237
|
-
pre_sign = "#{ts}##{@memo}##{body_json}"
|
|
238
|
-
|
|
239
|
-
response = connection.post do |req|
|
|
240
|
-
req.url path
|
|
241
|
-
req.headers = signed_headers(ts, pre_sign)
|
|
242
|
-
req.body = body.compact
|
|
243
|
-
end
|
|
244
|
-
response.body
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
def signed_headers(timestamp, pre_sign)
|
|
249
|
-
signature = hmac_sha256(@api_secret, pre_sign)
|
|
250
|
-
{
|
|
251
|
-
"X-BM-KEY": @api_key,
|
|
252
|
-
"X-BM-SIGN": signature,
|
|
253
|
-
"X-BM-TIMESTAMP": timestamp,
|
|
254
|
-
Accept: "application/json",
|
|
255
|
-
"Content-Type": "application/json"
|
|
256
|
-
}
|
|
257
|
-
end
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
end
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Honeymaker
|
|
4
|
-
module Exchanges
|
|
5
|
-
class BitMart < Exchange
|
|
6
|
-
BASE_URL = "https://api-cloud.bitmart.com"
|
|
7
|
-
|
|
8
|
-
def get_tickers_info
|
|
9
|
-
with_rescue do
|
|
10
|
-
response = connection.get("/spot/v1/symbols/details")
|
|
11
|
-
|
|
12
|
-
response.body["data"]["symbols"].filter_map do |product|
|
|
13
|
-
{
|
|
14
|
-
ticker: product["symbol"],
|
|
15
|
-
base: product["base_currency"],
|
|
16
|
-
quote: product["quote_currency"],
|
|
17
|
-
minimum_base_size: product["base_min_size"],
|
|
18
|
-
minimum_quote_size: product["min_buy_amount"],
|
|
19
|
-
maximum_base_size: nil,
|
|
20
|
-
maximum_quote_size: nil,
|
|
21
|
-
base_decimals: Utils.decimals(product["base_min_size"]),
|
|
22
|
-
quote_decimals: Utils.decimals(product["quote_increment"]),
|
|
23
|
-
price_decimals: product["price_max_precision"].to_i,
|
|
24
|
-
available: true,
|
|
25
|
-
trading_enabled: product["trade_status"] == "trading"
|
|
26
|
-
}
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def get_bid_ask(symbol)
|
|
32
|
-
with_rescue do
|
|
33
|
-
response = connection.get("/spot/quotation/v3/ticker") do |req|
|
|
34
|
-
req.params = { symbol: symbol }
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
data = response.body["data"]
|
|
38
|
-
{
|
|
39
|
-
bid: BigDecimal(data["best_bid"]),
|
|
40
|
-
ask: BigDecimal(data["best_ask"])
|
|
41
|
-
}
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
def connection
|
|
48
|
-
@connection ||= build_connection(BASE_URL)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "test_helper"
|
|
4
|
-
|
|
5
|
-
class Honeymaker::Clients::BitMartTest < Minitest::Test
|
|
6
|
-
def setup
|
|
7
|
-
@client = Honeymaker::Clients::BitMart.new(
|
|
8
|
-
api_key: "test_key", api_secret: "test_secret", memo: "test_memo"
|
|
9
|
-
)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def test_url
|
|
13
|
-
assert_equal "https://api-cloud.bitmart.com", Honeymaker::Clients::BitMart::URL
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def test_accepts_memo
|
|
17
|
-
assert_equal "test_memo", @client.memo
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def test_get_symbols_details
|
|
21
|
-
stub_connection(:get, { "data" => { "symbols" => [{ "symbol" => "BTC_USDT" }] } })
|
|
22
|
-
result = @client.get_symbols_details
|
|
23
|
-
assert result.success?
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def test_get_ticker
|
|
27
|
-
stub_connection(:get, { "data" => [{ "symbol" => "BTC_USDT" }] })
|
|
28
|
-
result = @client.get_ticker(symbol: "BTC_USDT")
|
|
29
|
-
assert result.success?
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def test_get_wallet
|
|
33
|
-
stub_connection(:get, { "data" => { "wallet" => [{ "id" => "BTC" }] } })
|
|
34
|
-
result = @client.get_wallet
|
|
35
|
-
assert result.success?
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def test_submit_order
|
|
39
|
-
stub_connection(:post, { "code" => 1000, "data" => { "order_id" => 123 } })
|
|
40
|
-
result = @client.submit_order(symbol: "BTC_USDT", side: "buy", type: "market", notional: "100")
|
|
41
|
-
assert result.success?
|
|
42
|
-
assert_equal "123", result.data[:order_id]
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def test_get_order
|
|
46
|
-
stub_connection(:post, { "code" => 1000, "data" => {
|
|
47
|
-
"symbol" => "BTC_USDT", "type" => "market", "side" => "buy", "status" => "filled",
|
|
48
|
-
"size" => "0.001", "filled_size" => "0.001", "filled_notional" => "50", "price" => "50000"
|
|
49
|
-
} })
|
|
50
|
-
result = @client.get_order(order_id: "123")
|
|
51
|
-
assert result.success?
|
|
52
|
-
assert_equal :closed, result.data[:status]
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def test_cancel_order
|
|
56
|
-
stub_connection(:post, { "data" => true })
|
|
57
|
-
result = @client.cancel_order(symbol: "BTC_USDT", order_id: "123")
|
|
58
|
-
assert result.success?
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def test_withdraw
|
|
62
|
-
stub_connection(:post, { "data" => { "withdraw_id" => "w1" } })
|
|
63
|
-
result = @client.withdraw(currency: "BTC", amount: "0.1", address: "addr")
|
|
64
|
-
assert result.success?
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def test_get_trades
|
|
68
|
-
stub_connection(:get, { "data" => [{ "tradeId" => "t1" }] })
|
|
69
|
-
result = @client.get_trades(symbol: "BTC_USDT")
|
|
70
|
-
assert result.success?
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def test_deposit_list
|
|
74
|
-
stub_connection(:get, { "data" => { "records" => [{ "currency" => "BTC" }] } })
|
|
75
|
-
result = @client.deposit_list
|
|
76
|
-
assert result.success?
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def test_withdraw_list
|
|
80
|
-
stub_connection(:get, { "data" => { "records" => [{ "currency" => "ETH" }] } })
|
|
81
|
-
result = @client.withdraw_list
|
|
82
|
-
assert result.success?
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def test_signed_headers_include_memo_in_signature
|
|
86
|
-
ts = "1234567890"
|
|
87
|
-
pre_sign = "#{ts}#test_memo#body"
|
|
88
|
-
headers = @client.send(:signed_headers, ts, pre_sign)
|
|
89
|
-
assert_equal "test_key", headers[:"X-BM-KEY"]
|
|
90
|
-
assert headers.key?(:"X-BM-SIGN")
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
private
|
|
94
|
-
|
|
95
|
-
def stub_connection(method, body)
|
|
96
|
-
response = stub(body: body)
|
|
97
|
-
connection = stub
|
|
98
|
-
connection.stubs(method).returns(response)
|
|
99
|
-
@client.instance_variable_set(:@connection, connection)
|
|
100
|
-
end
|
|
101
|
-
end
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "test_helper"
|
|
4
|
-
|
|
5
|
-
class Honeymaker::Exchanges::BitMartTest < Minitest::Test
|
|
6
|
-
include FixtureHelper
|
|
7
|
-
|
|
8
|
-
def setup
|
|
9
|
-
@exchange = Honeymaker::Exchanges::BitMart.new
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def test_get_tickers_info_parses_response
|
|
13
|
-
body = load_fixture("bitmart_symbols.json")
|
|
14
|
-
stub_connection(body)
|
|
15
|
-
|
|
16
|
-
result = @exchange.get_tickers_info
|
|
17
|
-
|
|
18
|
-
assert result.success?
|
|
19
|
-
assert_equal 2, result.data.size
|
|
20
|
-
|
|
21
|
-
ticker = result.data.first
|
|
22
|
-
assert_equal "BTC_USDT", ticker[:ticker]
|
|
23
|
-
assert_equal "BTC", ticker[:base]
|
|
24
|
-
assert_equal "USDT", ticker[:quote]
|
|
25
|
-
assert_equal "0.00001", ticker[:minimum_base_size]
|
|
26
|
-
assert_equal "5", ticker[:minimum_quote_size]
|
|
27
|
-
assert_nil ticker[:maximum_base_size]
|
|
28
|
-
assert_equal 5, ticker[:base_decimals]
|
|
29
|
-
assert_equal 2, ticker[:quote_decimals]
|
|
30
|
-
assert_equal 2, ticker[:price_decimals]
|
|
31
|
-
assert ticker[:available]
|
|
32
|
-
assert ticker[:trading_enabled]
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def test_pre_trade_listed_but_not_trading_enabled
|
|
36
|
-
body = load_fixture("bitmart_symbols.json")
|
|
37
|
-
stub_connection(body)
|
|
38
|
-
|
|
39
|
-
result = @exchange.get_tickers_info
|
|
40
|
-
|
|
41
|
-
eth = result.data.find { |t| t[:ticker] == "ETH_USDT" }
|
|
42
|
-
assert eth[:available] # still listed
|
|
43
|
-
refute eth[:trading_enabled] # but not trading
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def test_get_bid_ask_parses_response
|
|
47
|
-
body = load_fixture("bitmart_ticker.json")
|
|
48
|
-
stub_connection(body)
|
|
49
|
-
|
|
50
|
-
result = @exchange.get_bid_ask("BTC_USDT")
|
|
51
|
-
|
|
52
|
-
assert result.success?
|
|
53
|
-
assert_equal BigDecimal("67123.45"), result.data[:bid]
|
|
54
|
-
assert_equal BigDecimal("67125.67"), result.data[:ask]
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
def stub_connection(body)
|
|
60
|
-
response = stub(body: body)
|
|
61
|
-
connection = stub
|
|
62
|
-
connection.stubs(:get).returns(response)
|
|
63
|
-
@exchange.instance_variable_set(:@connection, connection)
|
|
64
|
-
end
|
|
65
|
-
end
|