honeymaker 0.8.0 → 0.9.2
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/Rakefile +22 -10
- data/lib/honeymaker/clients/kraken.rb +18 -1
- data/lib/honeymaker/exchanges/binance.rb +2 -1
- data/lib/honeymaker/exchanges/bingx.rb +2 -1
- data/lib/honeymaker/exchanges/bitget.rb +2 -1
- data/lib/honeymaker/exchanges/bitmart.rb +2 -1
- data/lib/honeymaker/exchanges/bitrue.rb +2 -1
- data/lib/honeymaker/exchanges/bitvavo.rb +2 -1
- data/lib/honeymaker/exchanges/bybit.rb +2 -1
- data/lib/honeymaker/exchanges/coinbase.rb +11 -1
- data/lib/honeymaker/exchanges/gemini.rb +2 -1
- data/lib/honeymaker/exchanges/hyperliquid.rb +2 -1
- data/lib/honeymaker/exchanges/kraken.rb +2 -1
- data/lib/honeymaker/exchanges/kucoin.rb +2 -1
- data/lib/honeymaker/exchanges/mexc.rb +2 -1
- data/lib/honeymaker/version.rb +1 -1
- data/test/honeymaker/clients/kraken_client_test.rb +58 -0
- data/test/honeymaker/exchanges/binance_test.rb +13 -0
- data/test/honeymaker/exchanges/binance_us_test.rb +2 -0
- data/test/honeymaker/exchanges/bingx_test.rb +1 -0
- data/test/honeymaker/exchanges/bitget_test.rb +4 -2
- data/test/honeymaker/exchanges/bitmart_test.rb +4 -2
- data/test/honeymaker/exchanges/bitrue_test.rb +1 -0
- data/test/honeymaker/exchanges/bitvavo_test.rb +4 -2
- data/test/honeymaker/exchanges/bybit_test.rb +1 -0
- data/test/honeymaker/exchanges/coinbase_test.rb +23 -0
- data/test/honeymaker/exchanges/gemini_test.rb +1 -0
- data/test/honeymaker/exchanges/hyperliquid_test.rb +1 -0
- data/test/honeymaker/exchanges/kraken_test.rb +32 -0
- data/test/honeymaker/exchanges/kucoin_test.rb +1 -0
- data/test/honeymaker/exchanges/mexc_test.rb +4 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac4aba6f18266cfffed7dfe3939a60198cb6bdb7361818db8f25f435e19d92f9
|
|
4
|
+
data.tar.gz: e0e398092c55d7483ac386a94846fd5eff4abf115feb9dffd59a85c59462da37
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aabbbc8fdd2c884c85770fc21a5862fe05c014860775f44e362e59c7db7c841cf92e74494af5028abdf10f4af70d88dc5fe13cc4fdc1838e359dc44f6f5c6dd5
|
|
7
|
+
data.tar.gz: a6cb39b92b07b6a5d04bb9b5d3adc14f11f7ea045b628c3b7dc09dd7501ddc0b9268bd3b477d8f2aac5072cfd379afe527738899c706b3d60ace82edd91073ad
|
data/Rakefile
CHANGED
|
@@ -29,26 +29,38 @@ def bump_version(segment)
|
|
|
29
29
|
puts "Bumped version to #{new_version}"
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
task
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
# Bundler's `release` task is defined when this Rakefile loads — it captures
|
|
33
|
+
# the gemspec version at that moment in `Bundler::GemHelper`. If we bump the
|
|
34
|
+
# version mid-run (as a build prerequisite), the gem is published correctly
|
|
35
|
+
# but the final "Pushed <gem> <ver>" log line still prints the old cached
|
|
36
|
+
# version. Fix: bump first, then invoke bundler's release in a subprocess so
|
|
37
|
+
# it re-reads the gemspec fresh.
|
|
38
|
+
|
|
39
|
+
Rake::Task[:release].clear
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
def do_release(segment)
|
|
42
|
+
bump_version(segment)
|
|
37
43
|
sh "bundle install"
|
|
38
44
|
Rake::Task[:test].invoke
|
|
39
|
-
sh
|
|
45
|
+
sh %(git add -A && git diff --cached --quiet || git commit -m "v#{current_version}")
|
|
46
|
+
sh "bundle exec rake _bundler_release"
|
|
40
47
|
end
|
|
41
48
|
|
|
42
|
-
|
|
49
|
+
task _bundler_release: %w[build release:guard_clean release:source_control_push release:rubygem_push]
|
|
50
|
+
|
|
51
|
+
desc "Bump patch, run tests, push to rubygems"
|
|
52
|
+
task :release do
|
|
53
|
+
do_release(:patch)
|
|
54
|
+
end
|
|
43
55
|
|
|
44
56
|
namespace :release do
|
|
57
|
+
desc "Bump minor, run tests, push to rubygems"
|
|
45
58
|
task :minor do
|
|
46
|
-
|
|
47
|
-
Rake::Task[:release].invoke
|
|
59
|
+
do_release(:minor)
|
|
48
60
|
end
|
|
49
61
|
|
|
62
|
+
desc "Bump major, run tests, push to rubygems"
|
|
50
63
|
task :major do
|
|
51
|
-
|
|
52
|
-
Rake::Task[:release].invoke
|
|
64
|
+
do_release(:major)
|
|
53
65
|
end
|
|
54
66
|
end
|
|
@@ -237,8 +237,25 @@ module Honeymaker
|
|
|
237
237
|
end
|
|
238
238
|
end
|
|
239
239
|
|
|
240
|
+
# Kraken rejects any request whose nonce is <= the last nonce it saw for the
|
|
241
|
+
# same API key. We protect against in-process collisions (microsecond bursts,
|
|
242
|
+
# backward clock corrections) by tracking the last issued nonce per API key
|
|
243
|
+
# under a mutex. Cross-process reuse of the same API key (other workers,
|
|
244
|
+
# other containers, other tools) is not protected here — affected users
|
|
245
|
+
# should increase the Nonce Window on their Kraken API key.
|
|
246
|
+
@@nonce_mutex = Mutex.new
|
|
247
|
+
@@last_nonces = {}
|
|
248
|
+
|
|
249
|
+
def self.reset_nonce_state!
|
|
250
|
+
@@nonce_mutex.synchronize { @@last_nonces.clear }
|
|
251
|
+
end
|
|
252
|
+
|
|
240
253
|
def nonce
|
|
241
|
-
(
|
|
254
|
+
key = @api_key ? Digest::SHA256.hexdigest(@api_key) : :__no_api_key__
|
|
255
|
+
@@nonce_mutex.synchronize do
|
|
256
|
+
candidate = (Time.now.utc.to_f * 1_000_000).to_i
|
|
257
|
+
@@last_nonces[key] = [candidate, (@@last_nonces[key] || 0) + 1].max
|
|
258
|
+
end
|
|
242
259
|
end
|
|
243
260
|
|
|
244
261
|
def private_headers(path, body)
|
|
@@ -25,7 +25,8 @@ module Honeymaker
|
|
|
25
25
|
base_decimals: Utils.decimals(f[:lot_size]["stepSize"]),
|
|
26
26
|
quote_decimals: product["quoteAssetPrecision"],
|
|
27
27
|
price_decimals: Utils.decimals(f[:price]["tickSize"]),
|
|
28
|
-
available:
|
|
28
|
+
available: true,
|
|
29
|
+
trading_enabled: product["status"] == "TRADING"
|
|
29
30
|
}
|
|
30
31
|
end
|
|
31
32
|
end
|
|
@@ -25,7 +25,8 @@ module Honeymaker
|
|
|
25
25
|
base_decimals: Utils.decimals(product["stepSize"]),
|
|
26
26
|
quote_decimals: Utils.decimals(product["tickSize"]),
|
|
27
27
|
price_decimals: Utils.decimals(product["tickSize"]),
|
|
28
|
-
available:
|
|
28
|
+
available: true,
|
|
29
|
+
trading_enabled: product["status"].to_i == 1
|
|
29
30
|
}
|
|
30
31
|
end
|
|
31
32
|
end
|
|
@@ -21,7 +21,8 @@ module Honeymaker
|
|
|
21
21
|
base_decimals: product["quantityPrecision"].to_i,
|
|
22
22
|
quote_decimals: product["quotePrecision"].to_i,
|
|
23
23
|
price_decimals: product["pricePrecision"].to_i,
|
|
24
|
-
available:
|
|
24
|
+
available: true,
|
|
25
|
+
trading_enabled: product["status"] == "online"
|
|
25
26
|
}
|
|
26
27
|
end
|
|
27
28
|
end
|
|
@@ -21,7 +21,8 @@ module Honeymaker
|
|
|
21
21
|
base_decimals: Utils.decimals(product["base_min_size"]),
|
|
22
22
|
quote_decimals: Utils.decimals(product["quote_increment"]),
|
|
23
23
|
price_decimals: product["price_max_precision"].to_i,
|
|
24
|
-
available:
|
|
24
|
+
available: true,
|
|
25
|
+
trading_enabled: product["trade_status"] == "trading"
|
|
25
26
|
}
|
|
26
27
|
end
|
|
27
28
|
end
|
|
@@ -23,7 +23,8 @@ module Honeymaker
|
|
|
23
23
|
base_decimals: f[:lot_size] ? Utils.decimals(f[:lot_size]["stepSize"]) : product["baseAssetPrecision"],
|
|
24
24
|
quote_decimals: product["quotePrecision"],
|
|
25
25
|
price_decimals: f[:price] ? Utils.decimals(f[:price]["tickSize"]) : product["quotePrecision"],
|
|
26
|
-
available:
|
|
26
|
+
available: true,
|
|
27
|
+
trading_enabled: product["status"] == "TRADING"
|
|
27
28
|
}
|
|
28
29
|
end
|
|
29
30
|
end
|
|
@@ -24,7 +24,8 @@ module Honeymaker
|
|
|
24
24
|
base_decimals: product["pricePrecision"] || 8,
|
|
25
25
|
quote_decimals: product["pricePrecision"] || 8,
|
|
26
26
|
price_decimals: product["pricePrecision"] || 8,
|
|
27
|
-
available:
|
|
27
|
+
available: true,
|
|
28
|
+
trading_enabled: product["status"] == "trading"
|
|
28
29
|
}
|
|
29
30
|
end
|
|
30
31
|
end
|
|
@@ -26,7 +26,8 @@ module Honeymaker
|
|
|
26
26
|
base_decimals: Utils.decimals(lot_size_filter["basePrecision"]),
|
|
27
27
|
quote_decimals: Utils.decimals(lot_size_filter["quotePrecision"]),
|
|
28
28
|
price_decimals: Utils.decimals(price_filter["tickSize"]),
|
|
29
|
-
available:
|
|
29
|
+
available: true,
|
|
30
|
+
trading_enabled: product["status"] == "Trading"
|
|
30
31
|
}
|
|
31
32
|
end
|
|
32
33
|
end
|
|
@@ -31,7 +31,8 @@ module Honeymaker
|
|
|
31
31
|
base_decimals: Utils.decimals(product["base_increment"]),
|
|
32
32
|
quote_decimals: Utils.decimals(product["quote_increment"]),
|
|
33
33
|
price_decimals: Utils.decimals(product["price_increment"]),
|
|
34
|
-
available: true
|
|
34
|
+
available: true,
|
|
35
|
+
trading_enabled: trading_enabled?(product)
|
|
35
36
|
}
|
|
36
37
|
end
|
|
37
38
|
end
|
|
@@ -50,6 +51,15 @@ module Honeymaker
|
|
|
50
51
|
|
|
51
52
|
private
|
|
52
53
|
|
|
54
|
+
# Coinbase only signals problems explicitly: a `trading_disabled` flag or a non-"online"
|
|
55
|
+
# `status`. Absent either signal, treat the pair as enabled.
|
|
56
|
+
def trading_enabled?(product)
|
|
57
|
+
return false if product["trading_disabled"] == true
|
|
58
|
+
return false if product.key?("status") && product["status"] != "online"
|
|
59
|
+
|
|
60
|
+
true
|
|
61
|
+
end
|
|
62
|
+
|
|
53
63
|
def connection
|
|
54
64
|
@connection ||= build_connection(BASE_URL)
|
|
55
65
|
end
|
|
@@ -27,7 +27,8 @@ module Honeymaker
|
|
|
27
27
|
base_decimals: Utils.decimals(tick_size),
|
|
28
28
|
quote_decimals: Utils.decimals(quote_increment),
|
|
29
29
|
price_decimals: Utils.decimals(quote_increment),
|
|
30
|
-
available:
|
|
30
|
+
available: true,
|
|
31
|
+
trading_enabled: detail["status"] == "open"
|
|
31
32
|
}
|
|
32
33
|
end
|
|
33
34
|
end
|
|
@@ -59,7 +59,8 @@ module Honeymaker
|
|
|
59
59
|
base_decimals: info["lot_decimals"],
|
|
60
60
|
quote_decimals: info["cost_decimals"],
|
|
61
61
|
price_decimals: info["pair_decimals"],
|
|
62
|
-
available: true
|
|
62
|
+
available: true,
|
|
63
|
+
trading_enabled: info.key?("status") ? info["status"] == "online" : true
|
|
63
64
|
}
|
|
64
65
|
end
|
|
65
66
|
end
|
|
@@ -21,7 +21,8 @@ module Honeymaker
|
|
|
21
21
|
base_decimals: Utils.decimals(product["baseIncrement"]),
|
|
22
22
|
quote_decimals: Utils.decimals(product["quoteIncrement"]),
|
|
23
23
|
price_decimals: Utils.decimals(product["priceIncrement"]),
|
|
24
|
-
available:
|
|
24
|
+
available: true,
|
|
25
|
+
trading_enabled: product["enableTrading"]
|
|
25
26
|
}
|
|
26
27
|
end
|
|
27
28
|
end
|
|
@@ -23,7 +23,8 @@ module Honeymaker
|
|
|
23
23
|
base_decimals: f[:lot_size] ? Utils.decimals(f[:lot_size]["stepSize"]) : product["baseAssetPrecision"],
|
|
24
24
|
quote_decimals: product["quoteAssetPrecision"],
|
|
25
25
|
price_decimals: f[:price] ? Utils.decimals(f[:price]["tickSize"]) : product["quotePrecision"],
|
|
26
|
-
available:
|
|
26
|
+
available: true,
|
|
27
|
+
trading_enabled: product["status"] == "TRADING"
|
|
27
28
|
}
|
|
28
29
|
end
|
|
29
30
|
end
|
data/lib/honeymaker/version.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "test_helper"
|
|
|
4
4
|
|
|
5
5
|
class Honeymaker::Clients::KrakenTest < Minitest::Test
|
|
6
6
|
def setup
|
|
7
|
+
Honeymaker::Clients::Kraken.reset_nonce_state! if Honeymaker::Clients::Kraken.respond_to?(:reset_nonce_state!)
|
|
7
8
|
@client = Honeymaker::Clients::Kraken.new(
|
|
8
9
|
api_key: "test_key",
|
|
9
10
|
api_secret: Base64.strict_encode64("test_secret_key_1234567890123456")
|
|
@@ -74,8 +75,65 @@ class Honeymaker::Clients::KrakenTest < Minitest::Test
|
|
|
74
75
|
refute headers.key?(:"API-Key")
|
|
75
76
|
end
|
|
76
77
|
|
|
78
|
+
def test_reset_nonce_state_is_available_for_test_isolation
|
|
79
|
+
assert_respond_to Honeymaker::Clients::Kraken, :reset_nonce_state!
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def test_nonce_is_strictly_increasing_in_a_tight_loop
|
|
83
|
+
nonces = Array.new(10_000) { @client.send(:nonce) }
|
|
84
|
+
|
|
85
|
+
assert_strictly_increasing nonces
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_nonce_is_strictly_increasing_when_clock_is_frozen
|
|
89
|
+
frozen_time = Time.utc(2026, 1, 1, 12, 0, 0)
|
|
90
|
+
Time.stubs(:now).returns(frozen_time)
|
|
91
|
+
|
|
92
|
+
nonces = Array.new(3) { @client.send(:nonce) }
|
|
93
|
+
|
|
94
|
+
assert_strictly_increasing nonces
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_nonce_is_strictly_increasing_across_clients_with_the_same_api_key
|
|
98
|
+
frozen_time = Time.utc(2026, 1, 1, 12, 0, 0)
|
|
99
|
+
Time.stubs(:now).returns(frozen_time)
|
|
100
|
+
other_client = Honeymaker::Clients::Kraken.new(
|
|
101
|
+
api_key: "test_key",
|
|
102
|
+
api_secret: Base64.strict_encode64("test_secret_key_1234567890123456")
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
nonces = [
|
|
106
|
+
@client.send(:nonce),
|
|
107
|
+
other_client.send(:nonce),
|
|
108
|
+
@client.send(:nonce),
|
|
109
|
+
other_client.send(:nonce)
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
assert_strictly_increasing nonces
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_nonce_sequences_are_independent_for_different_api_keys
|
|
116
|
+
frozen_time = Time.utc(2026, 1, 1, 12, 0, 0)
|
|
117
|
+
Time.stubs(:now).returns(frozen_time)
|
|
118
|
+
first_key_client = Honeymaker::Clients::Kraken.new(api_key: "first_key", api_secret: @client.api_secret)
|
|
119
|
+
second_key_client = Honeymaker::Clients::Kraken.new(api_key: "second_key", api_secret: @client.api_secret)
|
|
120
|
+
|
|
121
|
+
first_key_first_nonce = first_key_client.send(:nonce)
|
|
122
|
+
first_key_second_nonce = first_key_client.send(:nonce)
|
|
123
|
+
second_key_first_nonce = second_key_client.send(:nonce)
|
|
124
|
+
|
|
125
|
+
assert_operator first_key_second_nonce, :>, first_key_first_nonce
|
|
126
|
+
assert_equal first_key_first_nonce, second_key_first_nonce
|
|
127
|
+
end
|
|
128
|
+
|
|
77
129
|
private
|
|
78
130
|
|
|
131
|
+
def assert_strictly_increasing(values)
|
|
132
|
+
values.each_cons(2) do |previous, current|
|
|
133
|
+
assert_operator current, :>, previous
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
79
137
|
def stub_connection(method, body)
|
|
80
138
|
response = stub(body: body)
|
|
81
139
|
connection = stub
|
|
@@ -26,6 +26,19 @@ class Honeymaker::Exchanges::BinanceTest < Minitest::Test
|
|
|
26
26
|
assert_equal 8, ticker[:quote_decimals]
|
|
27
27
|
assert_equal 2, ticker[:price_decimals]
|
|
28
28
|
assert ticker[:available]
|
|
29
|
+
assert ticker[:trading_enabled]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_get_tickers_info_marks_non_trading_pairs_disabled
|
|
33
|
+
body = load_fixture("binance_exchange_info.json")
|
|
34
|
+
body["symbols"].first["status"] = "BREAK"
|
|
35
|
+
stub_connection(body)
|
|
36
|
+
|
|
37
|
+
result = @exchange.get_tickers_info
|
|
38
|
+
|
|
39
|
+
ticker = result.data.first
|
|
40
|
+
assert ticker[:available] # still listed
|
|
41
|
+
refute ticker[:trading_enabled] # but not trading
|
|
29
42
|
end
|
|
30
43
|
|
|
31
44
|
def test_get_tickers_info_handles_api_error
|
|
@@ -27,6 +27,8 @@ class Honeymaker::Exchanges::BinanceUsTest < Minitest::Test
|
|
|
27
27
|
ticker = result.data.first
|
|
28
28
|
assert_equal "BTCUSDT", ticker[:ticker]
|
|
29
29
|
assert_equal "BTC", ticker[:base]
|
|
30
|
+
assert ticker[:available]
|
|
31
|
+
assert ticker[:trading_enabled]
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def test_get_bid_ask_parses_response
|
|
@@ -29,16 +29,18 @@ class Honeymaker::Exchanges::BitgetTest < Minitest::Test
|
|
|
29
29
|
assert_equal 8, ticker[:quote_decimals]
|
|
30
30
|
assert_equal 2, ticker[:price_decimals]
|
|
31
31
|
assert ticker[:available]
|
|
32
|
+
assert ticker[:trading_enabled]
|
|
32
33
|
end
|
|
33
34
|
|
|
34
|
-
def
|
|
35
|
+
def test_offline_symbol_listed_but_not_trading_enabled
|
|
35
36
|
body = load_fixture("bitget_symbols.json")
|
|
36
37
|
stub_connection(body)
|
|
37
38
|
|
|
38
39
|
result = @exchange.get_tickers_info
|
|
39
40
|
|
|
40
41
|
eth = result.data.find { |t| t[:ticker] == "ETHUSDT" }
|
|
41
|
-
|
|
42
|
+
assert eth[:available] # still listed
|
|
43
|
+
refute eth[:trading_enabled] # but not trading
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def test_get_bid_ask_parses_response
|
|
@@ -29,16 +29,18 @@ class Honeymaker::Exchanges::BitMartTest < Minitest::Test
|
|
|
29
29
|
assert_equal 2, ticker[:quote_decimals]
|
|
30
30
|
assert_equal 2, ticker[:price_decimals]
|
|
31
31
|
assert ticker[:available]
|
|
32
|
+
assert ticker[:trading_enabled]
|
|
32
33
|
end
|
|
33
34
|
|
|
34
|
-
def
|
|
35
|
+
def test_pre_trade_listed_but_not_trading_enabled
|
|
35
36
|
body = load_fixture("bitmart_symbols.json")
|
|
36
37
|
stub_connection(body)
|
|
37
38
|
|
|
38
39
|
result = @exchange.get_tickers_info
|
|
39
40
|
|
|
40
41
|
eth = result.data.find { |t| t[:ticker] == "ETH_USDT" }
|
|
41
|
-
|
|
42
|
+
assert eth[:available] # still listed
|
|
43
|
+
refute eth[:trading_enabled] # but not trading
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def test_get_bid_ask_parses_response
|
|
@@ -29,6 +29,7 @@ class Honeymaker::Exchanges::BitrueTest < Minitest::Test
|
|
|
29
29
|
assert_equal 2, ticker[:quote_decimals]
|
|
30
30
|
assert_equal 2, ticker[:price_decimals]
|
|
31
31
|
assert ticker[:available]
|
|
32
|
+
assert ticker[:trading_enabled]
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
def test_falls_back_to_precision_fields_when_no_filters
|
|
@@ -29,16 +29,18 @@ class Honeymaker::Exchanges::BitvavoTest < Minitest::Test
|
|
|
29
29
|
assert_equal 5, ticker[:quote_decimals]
|
|
30
30
|
assert_equal 5, ticker[:price_decimals]
|
|
31
31
|
assert ticker[:available]
|
|
32
|
+
assert ticker[:trading_enabled]
|
|
32
33
|
end
|
|
33
34
|
|
|
34
|
-
def
|
|
35
|
+
def test_halted_market_listed_but_not_trading_enabled
|
|
35
36
|
body = load_fixture("bitvavo_markets.json")
|
|
36
37
|
stub_connection(body)
|
|
37
38
|
|
|
38
39
|
result = @exchange.get_tickers_info
|
|
39
40
|
|
|
40
41
|
eth = result.data.find { |t| t[:ticker] == "ETH-EUR" }
|
|
41
|
-
|
|
42
|
+
assert eth[:available] # still listed
|
|
43
|
+
refute eth[:trading_enabled] # but not trading
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def test_get_bid_ask_parses_response
|
|
@@ -29,6 +29,29 @@ class Honeymaker::Exchanges::CoinbaseTest < Minitest::Test
|
|
|
29
29
|
assert_equal 2, ticker[:quote_decimals]
|
|
30
30
|
assert_equal 2, ticker[:price_decimals]
|
|
31
31
|
assert ticker[:available]
|
|
32
|
+
assert ticker[:trading_enabled] # no disabled signal -> defaults to true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_get_tickers_info_disables_when_trading_disabled
|
|
36
|
+
body = load_fixture("coinbase_products.json")
|
|
37
|
+
body["products"].first["trading_disabled"] = true
|
|
38
|
+
stub_connection(body)
|
|
39
|
+
|
|
40
|
+
result = @exchange.get_tickers_info
|
|
41
|
+
|
|
42
|
+
ticker = result.data.first
|
|
43
|
+
assert ticker[:available] # still listed
|
|
44
|
+
refute ticker[:trading_enabled] # but not trading
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_get_tickers_info_disables_when_status_not_online
|
|
48
|
+
body = load_fixture("coinbase_products.json")
|
|
49
|
+
body["products"].first["status"] = "delisted"
|
|
50
|
+
stub_connection(body)
|
|
51
|
+
|
|
52
|
+
result = @exchange.get_tickers_info
|
|
53
|
+
|
|
54
|
+
refute result.data.first[:trading_enabled]
|
|
32
55
|
end
|
|
33
56
|
|
|
34
57
|
def test_filters_blacklisted_assets
|
|
@@ -28,6 +28,7 @@ class Honeymaker::Exchanges::HyperliquidTest < Minitest::Test
|
|
|
28
28
|
assert_equal 2, ticker[:quote_decimals]
|
|
29
29
|
assert_equal 5, ticker[:price_decimals]
|
|
30
30
|
assert ticker[:available]
|
|
31
|
+
assert ticker[:trading_enabled] # no native status -> always true
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
def test_skips_pairs_with_missing_tokens
|
|
@@ -26,6 +26,38 @@ class Honeymaker::Exchanges::KrakenTest < Minitest::Test
|
|
|
26
26
|
assert_equal 5, ticker[:quote_decimals]
|
|
27
27
|
assert_equal 1, ticker[:price_decimals]
|
|
28
28
|
assert ticker[:available]
|
|
29
|
+
assert ticker[:trading_enabled] # fixture has no status -> defaults to true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_get_tickers_info_disables_non_online_status
|
|
33
|
+
body = load_fixture("kraken_asset_pairs.json")
|
|
34
|
+
body["result"]["XBTUSDT"]["status"] = "cancel_only"
|
|
35
|
+
stub_request(body)
|
|
36
|
+
|
|
37
|
+
result = @exchange.get_tickers_info
|
|
38
|
+
|
|
39
|
+
ticker = result.data.first
|
|
40
|
+
assert ticker[:available] # still listed
|
|
41
|
+
refute ticker[:trading_enabled] # but not trading
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_get_tickers_info_enables_online_status
|
|
45
|
+
body = load_fixture("kraken_asset_pairs.json")
|
|
46
|
+
body["result"]["XBTUSDT"]["status"] = "online"
|
|
47
|
+
stub_request(body)
|
|
48
|
+
|
|
49
|
+
result = @exchange.get_tickers_info
|
|
50
|
+
|
|
51
|
+
assert result.data.first[:trading_enabled]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_get_tickers_info_defaults_trading_enabled_when_status_absent
|
|
55
|
+
body = load_fixture("kraken_asset_pairs.json") # no status key
|
|
56
|
+
stub_request(body)
|
|
57
|
+
|
|
58
|
+
result = @exchange.get_tickers_info
|
|
59
|
+
|
|
60
|
+
assert result.data.first[:trading_enabled]
|
|
29
61
|
end
|
|
30
62
|
|
|
31
63
|
def test_get_tickers_info_skips_pairs_without_wsname
|
|
@@ -29,16 +29,18 @@ class Honeymaker::Exchanges::MexcTest < Minitest::Test
|
|
|
29
29
|
assert_equal 8, ticker[:quote_decimals]
|
|
30
30
|
assert_equal 2, ticker[:price_decimals]
|
|
31
31
|
assert ticker[:available]
|
|
32
|
+
assert ticker[:trading_enabled]
|
|
32
33
|
end
|
|
33
34
|
|
|
34
|
-
def
|
|
35
|
+
def test_halted_symbol_listed_but_not_trading_enabled
|
|
35
36
|
body = load_fixture("mexc_exchange_info.json")
|
|
36
37
|
stub_connection(body)
|
|
37
38
|
|
|
38
39
|
result = @exchange.get_tickers_info
|
|
39
40
|
|
|
40
41
|
halted = result.data.find { |t| t[:ticker] == "ETHUSDT" }
|
|
41
|
-
|
|
42
|
+
assert halted[:available] # still listed
|
|
43
|
+
refute halted[:trading_enabled] # but not trading
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def test_falls_back_to_precision_fields_when_no_filters
|