honeymaker 0.11.1 → 0.11.4
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/lib/honeymaker/clients/kraken.rb +6 -2
- data/lib/honeymaker/exchanges/kraken.rb +22 -4
- data/lib/honeymaker/exchanges/mexc.rb +4 -1
- data/lib/honeymaker/version.rb +1 -1
- data/test/fixtures/kraken_asset_pairs.json +28 -1
- data/test/fixtures/mexc_exchange_info.json +51 -27
- data/test/honeymaker/clients/kraken_client_test.rb +30 -0
- data/test/honeymaker/exchanges/kraken_test.rb +42 -1
- data/test/honeymaker/exchanges/mexc_test.rb +37 -17
- 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: 2367ab9c6994586eb565f383f4b897d7a7bdcfb5cf68c0593da80f6b4d4b0497
|
|
4
|
+
data.tar.gz: 585589c4bc524266bed905b142913b6072e5ff8824602cdf5691e6ac2046bf39
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b4d5824b776181fff4494c1f4fe76e8ff46e21faf198cb23a894ea836fcfb4bc4f3eb8c0eec2315cc072a065ce9de5b4fd9e34d50f52390368069c5fbd091b13
|
|
7
|
+
data.tar.gz: f63b8235706bc5257bc1710939fc68f315316650966476a55ff4a25c1de35bb3035f42f54a94105fd1e446bcb4c6e684b65b2418bb16db68f7310c5bdb17dd16
|
|
@@ -65,9 +65,13 @@ module Honeymaker
|
|
|
65
65
|
post_private("/0/private/CancelOrder", { nonce: nonce, txid: txid, cl_ord_id: cl_ord_id })
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
# aclass_base: "all" also returns tokenized equities (xStocks); omitted, Kraken serves only the
|
|
69
|
+
# "currency" class. Note the asset-class parameter is spelled differently per endpoint —
|
|
70
|
+
# aclass_base here, asset_class on Ticker/OHLC/Depth/AddOrder.
|
|
71
|
+
def get_tradable_asset_pairs(pairs: nil, info: nil, country_code: nil, aclass_base: nil)
|
|
69
72
|
get_public("/0/public/AssetPairs", {
|
|
70
|
-
pair: pairs ? pairs.join(",") : nil, info: info, country_code: country_code
|
|
73
|
+
pair: pairs ? pairs.join(",") : nil, info: info, country_code: country_code,
|
|
74
|
+
aclass_base: aclass_base
|
|
71
75
|
})
|
|
72
76
|
end
|
|
73
77
|
|
|
@@ -48,15 +48,25 @@ module Honeymaker
|
|
|
48
48
|
|
|
49
49
|
def get_tickers_info
|
|
50
50
|
with_rescue do
|
|
51
|
-
|
|
51
|
+
# aclass_base=all, not a bare call: the default response carries only the "currency" class,
|
|
52
|
+
# so Kraken tokenized equities (xStocks) are invisible without it. One request returns both
|
|
53
|
+
# classes and they are disjoint.
|
|
54
|
+
response = connection.get("/0/public/AssetPairs", { aclass_base: "all" })
|
|
52
55
|
|
|
53
56
|
error = response.body["error"]
|
|
54
57
|
return Result::Failure.new(*error) if error.is_a?(Array) && error.any?
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
# Every tokenized pair is returned TWICE - once under an SPV key (NVDASPVUSD), once under
|
|
60
|
+
# the x key (NVDAxUSD) - sharing one wsname and altname. Currency pairs are never
|
|
61
|
+
# duplicated, so keying by wsname collapses exactly the aliases and nothing else.
|
|
62
|
+
deduped = response.body["result"].each_with_object({}) do |(_, info), acc|
|
|
57
63
|
wsname = info["wsname"]
|
|
58
|
-
next
|
|
64
|
+
next if wsname.nil? || wsname.empty?
|
|
59
65
|
|
|
66
|
+
acc[wsname] ||= info
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
deduped.filter_map do |wsname, info|
|
|
60
70
|
base, quote = wsname.split("/")
|
|
61
71
|
|
|
62
72
|
{
|
|
@@ -71,7 +81,15 @@ module Honeymaker
|
|
|
71
81
|
quote_decimals: info["cost_decimals"],
|
|
72
82
|
price_decimals: info["pair_decimals"],
|
|
73
83
|
available: true,
|
|
74
|
-
|
|
84
|
+
# Tokenized equities are listed but never tradable through this client: AddOrder needs
|
|
85
|
+
# an asset_class parameter it does not send, and Kraken closes the tokenized order
|
|
86
|
+
# books to EEA clients over the API regardless of that. Listing them anyway is what
|
|
87
|
+
# lets a holding be resolved and valued.
|
|
88
|
+
trading_enabled: if info["aclass_base"] == "tokenized_asset"
|
|
89
|
+
false
|
|
90
|
+
else
|
|
91
|
+
info.key?("status") ? info["status"] == "online" : true
|
|
92
|
+
end
|
|
75
93
|
}
|
|
76
94
|
end
|
|
77
95
|
end
|
|
@@ -24,7 +24,10 @@ module Honeymaker
|
|
|
24
24
|
quote_decimals: product["quoteAssetPrecision"],
|
|
25
25
|
price_decimals: f[:price] ? Utils.decimals(f[:price]["tickSize"]) : product["quotePrecision"],
|
|
26
26
|
available: true,
|
|
27
|
-
|
|
27
|
+
# MEXC encodes status as "1", not Binance's "TRADING" — every symbol it returns is "1".
|
|
28
|
+
# isSpotTradingAllowed is the real gate: ~5% of listed symbols reject a spot order.
|
|
29
|
+
# `!= false` keeps the fail-open default if the key is ever absent, matching the siblings.
|
|
30
|
+
trading_enabled: product["status"] == "1" && product["isSpotTradingAllowed"] != false
|
|
28
31
|
}
|
|
29
32
|
end
|
|
30
33
|
end
|
data/lib/honeymaker/version.rb
CHANGED
|
@@ -10,7 +10,34 @@
|
|
|
10
10
|
"cost_decimals": 5,
|
|
11
11
|
"pair_decimals": 1,
|
|
12
12
|
"ordermin": "0.00010000",
|
|
13
|
-
"costmin": "0.50000"
|
|
13
|
+
"costmin": "0.50000",
|
|
14
|
+
"aclass_base": "currency"
|
|
15
|
+
},
|
|
16
|
+
"NVDAxUSD": {
|
|
17
|
+
"altname": "NVDAxUSD",
|
|
18
|
+
"wsname": "NVDAx/USD",
|
|
19
|
+
"aclass_base": "tokenized_asset",
|
|
20
|
+
"base": "NVDAx",
|
|
21
|
+
"quote": "ZUSD",
|
|
22
|
+
"lot_decimals": 8,
|
|
23
|
+
"cost_decimals": 5,
|
|
24
|
+
"pair_decimals": 2,
|
|
25
|
+
"ordermin": "0.00000001",
|
|
26
|
+
"costmin": "0.5",
|
|
27
|
+
"status": "online"
|
|
28
|
+
},
|
|
29
|
+
"NVDASPVUSD": {
|
|
30
|
+
"altname": "NVDAxUSD",
|
|
31
|
+
"wsname": "NVDAx/USD",
|
|
32
|
+
"aclass_base": "tokenized_asset",
|
|
33
|
+
"base": "NVDAx",
|
|
34
|
+
"quote": "ZUSD",
|
|
35
|
+
"lot_decimals": 8,
|
|
36
|
+
"cost_decimals": 5,
|
|
37
|
+
"pair_decimals": 2,
|
|
38
|
+
"ordermin": "0.00000001",
|
|
39
|
+
"costmin": "0.5",
|
|
40
|
+
"status": "online"
|
|
14
41
|
}
|
|
15
42
|
}
|
|
16
43
|
}
|
|
@@ -1,40 +1,64 @@
|
|
|
1
1
|
{
|
|
2
|
+
"_provenance": "Real capture: GET https://api.mexc.com/api/v3/exchangeInfo (2026-09-05), trimmed to two symbols and to the fields this parser reads. MEXC returns status \"1\" for every symbol and PERCENT_PRICE_BY_SIDE as its only filter \u2014 it emits neither \"TRADING\" nor LOT_SIZE/PRICE_FILTER/MIN_NOTIONAL. Re-capture and diff rather than hand-editing; mexc_test.rb asserts this file's SHA-256.",
|
|
3
|
+
"timezone": "CST",
|
|
2
4
|
"symbols": [
|
|
3
5
|
{
|
|
4
|
-
"symbol": "
|
|
5
|
-
"status": "
|
|
6
|
-
"baseAsset": "
|
|
6
|
+
"symbol": "METALUSDT",
|
|
7
|
+
"status": "1",
|
|
8
|
+
"baseAsset": "METAL",
|
|
9
|
+
"baseAssetPrecision": 2,
|
|
7
10
|
"quoteAsset": "USDT",
|
|
8
|
-
"
|
|
9
|
-
"quoteAssetPrecision":
|
|
10
|
-
"
|
|
11
|
+
"quotePrecision": 5,
|
|
12
|
+
"quoteAssetPrecision": 5,
|
|
13
|
+
"orderTypes": [
|
|
14
|
+
"LIMIT",
|
|
15
|
+
"MARKET",
|
|
16
|
+
"LIMIT_MAKER"
|
|
17
|
+
],
|
|
18
|
+
"isSpotTradingAllowed": true,
|
|
19
|
+
"isMarginTradingAllowed": false,
|
|
20
|
+
"quoteAmountPrecision": "1",
|
|
21
|
+
"baseSizePrecision": "0.1",
|
|
22
|
+
"permissions": [
|
|
23
|
+
"SPOT"
|
|
24
|
+
],
|
|
11
25
|
"filters": [
|
|
12
26
|
{
|
|
13
|
-
"filterType": "
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
"filterType": "LOT_SIZE",
|
|
18
|
-
"minQty": "0.00001",
|
|
19
|
-
"maxQty": "9000",
|
|
20
|
-
"stepSize": "0.00001"
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"filterType": "MIN_NOTIONAL",
|
|
24
|
-
"minNotional": "5",
|
|
25
|
-
"maxNotional": "9000000"
|
|
27
|
+
"filterType": "PERCENT_PRICE_BY_SIDE",
|
|
28
|
+
"bidMultiplierUp": "0.2",
|
|
29
|
+
"askMultiplierDown": "0.2"
|
|
26
30
|
}
|
|
27
|
-
]
|
|
31
|
+
],
|
|
32
|
+
"maxQuoteAmount": "2000000"
|
|
28
33
|
},
|
|
29
34
|
{
|
|
30
|
-
"symbol": "
|
|
31
|
-
"status": "
|
|
32
|
-
"baseAsset": "
|
|
35
|
+
"symbol": "PALMAIUSDT",
|
|
36
|
+
"status": "1",
|
|
37
|
+
"baseAsset": "PALMAI",
|
|
38
|
+
"baseAssetPrecision": 2,
|
|
33
39
|
"quoteAsset": "USDT",
|
|
34
|
-
"
|
|
35
|
-
"quoteAssetPrecision":
|
|
36
|
-
"
|
|
37
|
-
|
|
40
|
+
"quotePrecision": 4,
|
|
41
|
+
"quoteAssetPrecision": 4,
|
|
42
|
+
"orderTypes": [
|
|
43
|
+
"LIMIT",
|
|
44
|
+
"MARKET",
|
|
45
|
+
"LIMIT_MAKER"
|
|
46
|
+
],
|
|
47
|
+
"isSpotTradingAllowed": false,
|
|
48
|
+
"isMarginTradingAllowed": false,
|
|
49
|
+
"quoteAmountPrecision": "1",
|
|
50
|
+
"baseSizePrecision": "0",
|
|
51
|
+
"permissions": [
|
|
52
|
+
"SPOT"
|
|
53
|
+
],
|
|
54
|
+
"filters": [
|
|
55
|
+
{
|
|
56
|
+
"filterType": "PERCENT_PRICE_BY_SIDE",
|
|
57
|
+
"bidMultiplierUp": "0.2",
|
|
58
|
+
"askMultiplierDown": "0.2"
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"maxQuoteAmount": "2000000"
|
|
38
62
|
}
|
|
39
63
|
]
|
|
40
64
|
}
|
|
@@ -22,6 +22,36 @@ class Honeymaker::Clients::KrakenTest < Minitest::Test
|
|
|
22
22
|
assert result.data["result"].key?("XBTUSDT")
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# Without aclass_base Kraken serves only the "currency" class, so tokenized equities are invisible.
|
|
26
|
+
def test_get_tradable_asset_pairs_forwards_aclass_base
|
|
27
|
+
captured = nil
|
|
28
|
+
req = stub("req")
|
|
29
|
+
req.stubs(:url)
|
|
30
|
+
req.stubs(:headers=)
|
|
31
|
+
req.stubs(:params=).with { |p| captured = p; true }
|
|
32
|
+
connection = stub
|
|
33
|
+
connection.stubs(:get).yields(req).returns(stub(body: { "error" => [], "result" => {} }))
|
|
34
|
+
@client.instance_variable_set(:@connection, connection)
|
|
35
|
+
|
|
36
|
+
assert @client.get_tradable_asset_pairs(aclass_base: "all").success?
|
|
37
|
+
assert_equal "all", captured[:aclass_base]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_get_tradable_asset_pairs_omits_aclass_base_when_not_asked
|
|
41
|
+
captured = nil
|
|
42
|
+
req = stub("req")
|
|
43
|
+
req.stubs(:url)
|
|
44
|
+
req.stubs(:headers=)
|
|
45
|
+
req.stubs(:params=).with { |p| captured = p; true }
|
|
46
|
+
connection = stub
|
|
47
|
+
connection.stubs(:get).yields(req).returns(stub(body: { "error" => [], "result" => {} }))
|
|
48
|
+
@client.instance_variable_set(:@connection, connection)
|
|
49
|
+
|
|
50
|
+
@client.get_tradable_asset_pairs
|
|
51
|
+
|
|
52
|
+
refute captured.key?(:aclass_base), "params are compacted, so nil must not be sent"
|
|
53
|
+
end
|
|
54
|
+
|
|
25
55
|
def test_get_ticker_information
|
|
26
56
|
stub_connection(:get, { "error" => [], "result" => { "XBTUSDT" => { "a" => ["50000"] } } })
|
|
27
57
|
result = @client.get_ticker_information(pair: "XBTUSDT")
|
|
@@ -9,6 +9,45 @@ class Honeymaker::Exchanges::KrakenTest < Minitest::Test
|
|
|
9
9
|
@exchange = Honeymaker::Exchanges::Kraken.new
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
# Tokenized equities (xStocks) live behind aclass_base and are invisible to a bare AssetPairs call.
|
|
13
|
+
# aclass_base=all returns currency + tokenized in one request; the two sets are disjoint.
|
|
14
|
+
def test_get_tickers_info_requests_every_asset_class
|
|
15
|
+
response = stub(body: load_fixture("kraken_asset_pairs.json"))
|
|
16
|
+
connection = stub
|
|
17
|
+
connection.expects(:get).with("/0/public/AssetPairs", { aclass_base: "all" }).returns(response)
|
|
18
|
+
@exchange.instance_variable_set(:@connection, connection)
|
|
19
|
+
|
|
20
|
+
assert @exchange.get_tickers_info.success?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Kraken returns every tokenized pair TWICE - once under an SPV key, once under the x key - with
|
|
24
|
+
# one shared wsname and altname. Only tokenized pairs are double-keyed; currency pairs never are.
|
|
25
|
+
# Mapping the raw response would ingest each of them twice.
|
|
26
|
+
def test_get_tickers_info_deduplicates_the_spv_alias
|
|
27
|
+
body = load_fixture("kraken_asset_pairs.json")
|
|
28
|
+
stub_request(body)
|
|
29
|
+
|
|
30
|
+
result = @exchange.get_tickers_info
|
|
31
|
+
|
|
32
|
+
nvda = result.data.select { |t| t[:base] == "NVDAx" }
|
|
33
|
+
assert_equal 1, nvda.size, "NVDAxUSD and NVDASPVUSD are one pair"
|
|
34
|
+
assert_equal "NVDAxUSD", nvda.first[:ticker]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Listed, so a holding can be resolved and valued; not tradable, because AddOrder needs an
|
|
38
|
+
# asset_class parameter this client does not send, and Kraken closes the tokenized order books to
|
|
39
|
+
# EEA clients over the API regardless.
|
|
40
|
+
def test_tokenized_pairs_are_listed_but_not_trading_enabled
|
|
41
|
+
body = load_fixture("kraken_asset_pairs.json")
|
|
42
|
+
stub_request(body)
|
|
43
|
+
|
|
44
|
+
nvda = @exchange.get_tickers_info.data.find { |t| t[:base] == "NVDAx" }
|
|
45
|
+
|
|
46
|
+
assert_equal "online", body["result"]["NVDAxUSD"]["status"]
|
|
47
|
+
assert nvda[:available], "listed, so balances resolve"
|
|
48
|
+
refute nvda[:trading_enabled], "we cannot place these orders"
|
|
49
|
+
end
|
|
50
|
+
|
|
12
51
|
def test_get_tickers_info_parses_response
|
|
13
52
|
body = load_fixture("kraken_asset_pairs.json")
|
|
14
53
|
stub_request(body)
|
|
@@ -68,7 +107,9 @@ class Honeymaker::Exchanges::KrakenTest < Minitest::Test
|
|
|
68
107
|
result = @exchange.get_tickers_info
|
|
69
108
|
|
|
70
109
|
assert result.success?
|
|
71
|
-
|
|
110
|
+
# Asserts the wsname-less pair is dropped, not that the whole response is - the fixture now
|
|
111
|
+
# carries a tokenized pair too.
|
|
112
|
+
refute result.data.any? { |t| t[:ticker] == "XBTUSDT" }
|
|
72
113
|
end
|
|
73
114
|
|
|
74
115
|
def test_get_tickers_info_uses_real_costmin
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "test_helper"
|
|
4
|
+
require "digest"
|
|
4
5
|
|
|
5
6
|
class Honeymaker::Exchanges::MexcTest < Minitest::Test
|
|
6
7
|
include FixtureHelper
|
|
@@ -9,6 +10,23 @@ class Honeymaker::Exchanges::MexcTest < Minitest::Test
|
|
|
9
10
|
@exchange = Honeymaker::Exchanges::Mexc.new
|
|
10
11
|
end
|
|
11
12
|
|
|
13
|
+
# H1. MEXC's exchangeInfo has never carried Binance's status encoding. The fixture this file
|
|
14
|
+
# drives was a hand-edited clone of the Binance one ("TRADING"/"HALT", LOT_SIZE/PRICE_FILTER/
|
|
15
|
+
# MIN_NOTIONAL filters) written from the same wrong assumption as the parser it tested, so both
|
|
16
|
+
# agreed and both were wrong. Asserting the raw bytes is the only guard that survives an author
|
|
17
|
+
# who believes the wrong thing twice; the digest also binds this file to deltabadger's copy, which
|
|
18
|
+
# drives the same capture through the other implementation of this contract.
|
|
19
|
+
def test_fixture_carries_mexcs_own_status_encoding
|
|
20
|
+
body = load_fixture("mexc_exchange_info.json")
|
|
21
|
+
|
|
22
|
+
assert_equal "1", body["symbols"].first["status"]
|
|
23
|
+
refute body["symbols"].any? { |s| s["status"] == "TRADING" }
|
|
24
|
+
refute body["symbols"].any? { |s| s["filters"].any? { |f| f["filterType"] == "LOT_SIZE" } }
|
|
25
|
+
|
|
26
|
+
assert_equal "13a65c0c8ebde52fac4cdca9a5be9d9a18ba7b5438c496b1b30606cf52fd60d3",
|
|
27
|
+
Digest::SHA256.hexdigest(File.read(fixture_path("mexc_exchange_info.json")))
|
|
28
|
+
end
|
|
29
|
+
|
|
12
30
|
def test_get_tickers_info_parses_response
|
|
13
31
|
body = load_fixture("mexc_exchange_info.json")
|
|
14
32
|
stub_connection(body)
|
|
@@ -19,40 +37,42 @@ class Honeymaker::Exchanges::MexcTest < Minitest::Test
|
|
|
19
37
|
assert_equal 2, result.data.size
|
|
20
38
|
|
|
21
39
|
ticker = result.data.first
|
|
22
|
-
assert_equal "
|
|
23
|
-
assert_equal "
|
|
40
|
+
assert_equal "METALUSDT", ticker[:ticker]
|
|
41
|
+
assert_equal "METAL", ticker[:base]
|
|
24
42
|
assert_equal "USDT", ticker[:quote]
|
|
25
|
-
assert_equal "0.00001", ticker[:minimum_base_size]
|
|
26
|
-
assert_equal "5", ticker[:minimum_quote_size]
|
|
27
|
-
assert_equal "9000", ticker[:maximum_base_size]
|
|
28
|
-
assert_equal 5, ticker[:base_decimals]
|
|
29
|
-
assert_equal 8, ticker[:quote_decimals]
|
|
30
|
-
assert_equal 2, ticker[:price_decimals]
|
|
31
43
|
assert ticker[:available]
|
|
32
44
|
assert ticker[:trading_enabled]
|
|
33
45
|
end
|
|
34
46
|
|
|
35
|
-
|
|
47
|
+
# H2. status is "1" for every MEXC symbol, including the 103 the venue will not accept a spot
|
|
48
|
+
# order for. The status check alone re-enables all of them; isSpotTradingAllowed is what keeps
|
|
49
|
+
# them out of the picker.
|
|
50
|
+
def test_a_symbol_mexc_does_not_allow_for_spot_is_not_trading_enabled
|
|
36
51
|
body = load_fixture("mexc_exchange_info.json")
|
|
37
52
|
stub_connection(body)
|
|
38
53
|
|
|
39
54
|
result = @exchange.get_tickers_info
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
disallowed = result.data.find { |t| t[:ticker] == "PALMAIUSDT" }
|
|
57
|
+
assert_equal "1", body["symbols"].last["status"]
|
|
58
|
+
assert disallowed[:available] # still listed
|
|
59
|
+
refute disallowed[:trading_enabled] # but not spot-tradable
|
|
44
60
|
end
|
|
45
61
|
|
|
46
|
-
|
|
62
|
+
# H5. Real MEXC sends only PERCENT_PRICE_BY_SIDE, so the precision fallback is the ONLY path —
|
|
63
|
+
# there is no filter-bearing symbol to contrast it against.
|
|
64
|
+
def test_falls_back_to_precision_fields_when_no_size_filters
|
|
47
65
|
body = load_fixture("mexc_exchange_info.json")
|
|
48
66
|
stub_connection(body)
|
|
49
67
|
|
|
50
68
|
result = @exchange.get_tickers_info
|
|
51
69
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
assert_equal
|
|
55
|
-
assert_equal
|
|
70
|
+
metal = result.data.find { |t| t[:ticker] == "METALUSDT" }
|
|
71
|
+
assert_equal 2, metal[:base_decimals] # baseAssetPrecision
|
|
72
|
+
assert_equal 5, metal[:quote_decimals] # quoteAssetPrecision
|
|
73
|
+
assert_equal 5, metal[:price_decimals] # quotePrecision
|
|
74
|
+
assert_nil metal[:minimum_base_size]
|
|
75
|
+
assert_nil metal[:minimum_quote_size]
|
|
56
76
|
end
|
|
57
77
|
|
|
58
78
|
def test_get_bid_ask_parses_response
|