honeymaker 0.12.4-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/native-gems.yml +100 -0
- data/.github/workflows/release.yml +36 -0
- data/.github/workflows/test.yml +33 -0
- data/LICENSE +21 -0
- data/README.md +219 -0
- data/Rakefile +78 -0
- data/crates/honeymaker/tests/vectors/decimal.json +146 -0
- data/crates/honeymaker/tests/vectors/kraken_add_order_wire.json +74 -0
- data/crates/honeymaker/tests/vectors/kraken_signing.json +3314 -0
- data/crates/honeymaker/tests/vectors/kraken_trades_pager.json +1583 -0
- data/lib/honeymaker/3.4/honeymaker_native.so +0 -0
- data/lib/honeymaker/4.0/honeymaker_native.so +0 -0
- data/lib/honeymaker/client.rb +139 -0
- data/lib/honeymaker/clients/binance.rb +687 -0
- data/lib/honeymaker/clients/binance_us.rb +42 -0
- data/lib/honeymaker/clients/bingx.rb +242 -0
- data/lib/honeymaker/clients/bitget.rb +318 -0
- data/lib/honeymaker/clients/bitrue.rb +245 -0
- data/lib/honeymaker/clients/bitvavo.rb +244 -0
- data/lib/honeymaker/clients/bybit.rb +308 -0
- data/lib/honeymaker/clients/coinbase.rb +326 -0
- data/lib/honeymaker/clients/gemini.rb +189 -0
- data/lib/honeymaker/clients/hyperliquid.rb +232 -0
- data/lib/honeymaker/clients/kraken.rb +379 -0
- data/lib/honeymaker/clients/kraken_futures.rb +78 -0
- data/lib/honeymaker/clients/kucoin.rb +310 -0
- data/lib/honeymaker/clients/mexc.rb +249 -0
- data/lib/honeymaker/exchange.rb +129 -0
- data/lib/honeymaker/exchanges/binance.rb +55 -0
- data/lib/honeymaker/exchanges/binance_us.rb +9 -0
- data/lib/honeymaker/exchanges/bingx.rb +56 -0
- data/lib/honeymaker/exchanges/bitget.rb +52 -0
- data/lib/honeymaker/exchanges/bitrue.rb +53 -0
- data/lib/honeymaker/exchanges/bitvavo.rb +65 -0
- data/lib/honeymaker/exchanges/bybit.rb +57 -0
- data/lib/honeymaker/exchanges/coinbase.rb +69 -0
- data/lib/honeymaker/exchanges/gemini.rb +134 -0
- data/lib/honeymaker/exchanges/hyperliquid.rb +67 -0
- data/lib/honeymaker/exchanges/kraken.rb +122 -0
- data/lib/honeymaker/exchanges/kucoin.rb +52 -0
- data/lib/honeymaker/exchanges/mexc.rb +56 -0
- data/lib/honeymaker/native/kraken.rb +263 -0
- data/lib/honeymaker/native.rb +42 -0
- data/lib/honeymaker/result.rb +46 -0
- data/lib/honeymaker/utils.rb +39 -0
- data/lib/honeymaker/version.rb +5 -0
- data/lib/honeymaker.rb +97 -0
- data/script/parity/kraken_client_live.rb +191 -0
- data/script/parity/kraken_live.rb +70 -0
- data/script/parity/kraken_transport.rb +604 -0
- data/script/parity/resolve.c +49 -0
- data/script/release/push_gems.sh +28 -0
- data/script/vectors/kraken.rb +50 -0
- data/script/vectors/kraken_stage2.rb +146 -0
- data/sig/honeymaker.rbs +4 -0
- data/test/fixtures/binance_book_ticker.json +7 -0
- data/test/fixtures/binance_exchange_info.json +30 -0
- data/test/fixtures/bingx_symbols.json +26 -0
- data/test/fixtures/bingx_ticker.json +12 -0
- data/test/fixtures/bitget_symbols.json +28 -0
- data/test/fixtures/bitget_tickers.json +14 -0
- data/test/fixtures/bitmart_symbols.json +26 -0
- data/test/fixtures/bitmart_ticker.json +12 -0
- data/test/fixtures/bitrue_book_ticker.json +7 -0
- data/test/fixtures/bitrue_exchange_info.json +34 -0
- data/test/fixtures/bitvavo_book_ticker.json +7 -0
- data/test/fixtures/bitvavo_markets.json +30 -0
- data/test/fixtures/bybit_instruments.json +23 -0
- data/test/fixtures/bybit_tickers.json +19 -0
- data/test/fixtures/coinbase_product.json +7 -0
- data/test/fixtures/coinbase_products.json +24 -0
- data/test/fixtures/gemini_pubticker.json +10 -0
- data/test/fixtures/gemini_symbol_detail.json +10 -0
- data/test/fixtures/gemini_symbols.json +1 -0
- data/test/fixtures/hyperliquid_all_mids.json +5 -0
- data/test/fixtures/hyperliquid_spot_meta.json +12 -0
- data/test/fixtures/kraken_asset_pairs.json +43 -0
- data/test/fixtures/kraken_ticker.json +16 -0
- data/test/fixtures/kucoin_orderbook_level1.json +10 -0
- data/test/fixtures/kucoin_symbols.json +17 -0
- data/test/fixtures/mexc_book_ticker.json +7 -0
- data/test/fixtures/mexc_exchange_info.json +64 -0
- data/test/honeymaker/client_test.rb +90 -0
- data/test/honeymaker/clients/binance_client_test.rb +151 -0
- data/test/honeymaker/clients/binance_us_client_test.rb +25 -0
- data/test/honeymaker/clients/bingx_client_test.rb +106 -0
- data/test/honeymaker/clients/bitget_client_test.rb +146 -0
- data/test/honeymaker/clients/bitrue_client_test.rb +69 -0
- data/test/honeymaker/clients/bitvavo_client_test.rb +156 -0
- data/test/honeymaker/clients/bybit_client_test.rb +137 -0
- data/test/honeymaker/clients/coinbase_client_test.rb +153 -0
- data/test/honeymaker/clients/gemini_client_test.rb +100 -0
- data/test/honeymaker/clients/honeymaker_client_registry_test.rb +39 -0
- data/test/honeymaker/clients/hyperliquid_client_test.rb +292 -0
- data/test/honeymaker/clients/kraken_client_test.rb +255 -0
- data/test/honeymaker/clients/kraken_futures_client_test.rb +54 -0
- data/test/honeymaker/clients/kucoin_client_test.rb +149 -0
- data/test/honeymaker/clients/mexc_client_test.rb +98 -0
- data/test/honeymaker/clients/validation_test.rb +167 -0
- data/test/honeymaker/error_chain_test.rb +133 -0
- data/test/honeymaker/exchange_test.rb +138 -0
- data/test/honeymaker/exchanges/binance_test.rb +83 -0
- data/test/honeymaker/exchanges/binance_us_test.rb +53 -0
- data/test/honeymaker/exchanges/bingx_test.rb +65 -0
- data/test/honeymaker/exchanges/bitget_test.rb +65 -0
- data/test/honeymaker/exchanges/bitrue_test.rb +65 -0
- data/test/honeymaker/exchanges/bitvavo_test.rb +65 -0
- data/test/honeymaker/exchanges/bybit_test.rb +55 -0
- data/test/honeymaker/exchanges/coinbase_test.rb +106 -0
- data/test/honeymaker/exchanges/gemini_catalogue_test.rb +118 -0
- data/test/honeymaker/exchanges/gemini_test.rb +184 -0
- data/test/honeymaker/exchanges/hyperliquid_test.rb +64 -0
- data/test/honeymaker/exchanges/kraken_test.rb +191 -0
- data/test/honeymaker/exchanges/kucoin_test.rb +55 -0
- data/test/honeymaker/exchanges/mexc_test.rb +97 -0
- data/test/honeymaker/honeymaker_test.rb +27 -0
- data/test/honeymaker/result_test.rb +42 -0
- data/test/honeymaker/utils_test.rb +64 -0
- data/test/native/backend_switch_test.rb +53 -0
- data/test/native/convert_test.rb +137 -0
- data/test/native/gem_isolation_test.rb +17 -0
- data/test/native/loader_test.rb +29 -0
- data/test/native/lockstep_test.rb +23 -0
- data/test/native/signing_smoke_test.rb +28 -0
- data/test/parity/kraken_client_parity_test.rb +398 -0
- data/test/parity/kraken_exchange_parity_test.rb +172 -0
- data/test/release/branch_hardening_test.rb +34 -0
- data/test/release/push_gems_test.rb +79 -0
- data/test/smoke.rb +24 -0
- data/test/support/fixture_helper.rb +13 -0
- data/test/support/kraken_signing_smoke.rb +21 -0
- data/test/support/parity_helper.rb +62 -0
- data/test/support/venue_double.rb +100 -0
- data/test/test_helper.rb +9 -0
- metadata +254 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Push built gems: platform gems first, the source gem last. Idempotent: a gem already on
|
|
3
|
+
# rubygems.org is accepted only if byte-identical (a rerun after a partial publish).
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
dir="${1:?gem dir}"
|
|
6
|
+
version="${2:?version}"
|
|
7
|
+
api="${RUBYGEMS_API:-https://rubygems.org}"
|
|
8
|
+
# Exactly the release set, or nothing is pushed: 5 platform gems + the source gem.
|
|
9
|
+
expected=(x86_64-linux aarch64-linux x86_64-darwin arm64-darwin x64-mingw-ucrt)
|
|
10
|
+
for plat in "${expected[@]}"; do
|
|
11
|
+
[ -f "$dir/honeymaker-$version-$plat.gem" ] || { echo "::error::missing honeymaker-$version-$plat.gem" >&2; exit 1; }
|
|
12
|
+
done
|
|
13
|
+
[ -f "$dir/honeymaker-$version.gem" ] || { echo "::error::missing honeymaker-$version.gem (source)" >&2; exit 1; }
|
|
14
|
+
count=$(find "$dir" -maxdepth 1 -name '*.gem' | wc -l | tr -d ' ')
|
|
15
|
+
[ "$count" = 6 ] || { echo "::error::expected 6 gems in $dir, found $count" >&2; exit 1; }
|
|
16
|
+
push() {
|
|
17
|
+
local g="$1" ver plat sha remote
|
|
18
|
+
if gem push "$g"; then return 0; fi
|
|
19
|
+
ver=$(ruby -rrubygems/package -e 'print Gem::Package.new(ARGV[0]).spec.version' "$g")
|
|
20
|
+
plat=$(ruby -rrubygems/package -e 'print Gem::Package.new(ARGV[0]).spec.platform' "$g")
|
|
21
|
+
sha=$(ruby -rdigest -e 'print Digest::SHA256.file(ARGV[0]).hexdigest' "$g")
|
|
22
|
+
remote=$(curl -fsS "$api/api/v2/rubygems/honeymaker/versions/${ver}.json?platform=${plat}" \
|
|
23
|
+
| ruby -rjson -e 'print JSON.parse($stdin.read)["sha"]')
|
|
24
|
+
if [ "$sha" != "$remote" ]; then echo "::error::$g differs from the published gem" >&2; return 1; fi
|
|
25
|
+
echo "$g already published with the same checksum"
|
|
26
|
+
}
|
|
27
|
+
for plat in "${expected[@]}"; do push "$dir/honeymaker-$version-$plat.gem"; done
|
|
28
|
+
push "$dir/honeymaker-$version.gem"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Regenerates crates/honeymaker/tests/vectors/*.json from the LEGACY Ruby implementation.
|
|
4
|
+
# bundle exec ruby -Ilib script/vectors/kraken.rb
|
|
5
|
+
require "honeymaker"
|
|
6
|
+
require "json"
|
|
7
|
+
require "bigdecimal"
|
|
8
|
+
|
|
9
|
+
out_dir = File.expand_path("../../crates/honeymaker/tests/vectors", __dir__)
|
|
10
|
+
|
|
11
|
+
secrets = [
|
|
12
|
+
Base64.strict_encode64("test_secret_key_1234567890123456"),
|
|
13
|
+
"dGVzd A==\n!!-_x", "abc", "=abc", "", " ", "a", "ab==cd", "not base64 at all ~~~",
|
|
14
|
+
Base64.strict_encode64((0..255).to_a.pack("C*"))
|
|
15
|
+
]
|
|
16
|
+
bodies = [
|
|
17
|
+
[["nonce", "1727000000000001"]],
|
|
18
|
+
[["nonce", "1727000000000002"], ["ordertype", "market"], ["type", "buy"], ["volume", "0.001"], ["pair", "XBTUSDT"],
|
|
19
|
+
["oflags", "viqc"], ["close[ordertype]", "limit"]],
|
|
20
|
+
[["nonce", "1727000000000003"], ["txid", "O1,O2"], ["consolidate_taker", "true"]],
|
|
21
|
+
[["nonce", "1727000000000004"], ["asset", "a b~*-._/,:[]\u00e9+&="]]
|
|
22
|
+
]
|
|
23
|
+
paths = %w[/0/private/BalanceEx /0/private/AddOrder /0/private/QueryOrders /0/private/Earn/Allocations]
|
|
24
|
+
|
|
25
|
+
signing = []
|
|
26
|
+
secrets.each do |secret|
|
|
27
|
+
[["key", secret], ["", secret], ["key", ""]].each do |key, sec|
|
|
28
|
+
bodies.each_with_index do |pairs, i|
|
|
29
|
+
body = URI.encode_www_form(pairs)
|
|
30
|
+
c = Honeymaker::Clients::Kraken.new(api_key: key, api_secret: sec)
|
|
31
|
+
signing << { api_key: key, api_secret: sec, path: paths[i], pairs: pairs, body: body,
|
|
32
|
+
decoded_secret_hex: Base64.decode64(sec).unpack1("H*"),
|
|
33
|
+
headers: c.send(:private_headers, paths[i], body).transform_keys(&:to_s) }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
decimal_inputs = ["0.5", "1", "-0", "-0.0000", "0.0000000001", "123456789012345678901234567890.123456789",
|
|
39
|
+
"1e-9", "1E5", " 1.5 ", "1_000.5", ".5", "5.", "+2", "", "abc", "1,5", "0x10", "NaN",
|
|
40
|
+
"Infinity", "-Infinity", "1d2", "true"]
|
|
41
|
+
decimals = decimal_inputs.map do |s|
|
|
42
|
+
v = BigDecimal(s)
|
|
43
|
+
{ input: s, ok: true, finite: v.finite?, plain: v.finite? ? v.to_s("F") : v.to_s, sign: v.sign }
|
|
44
|
+
rescue ArgumentError => e
|
|
45
|
+
{ input: s, ok: false, error: e.message }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
File.write(File.join(out_dir, "kraken_signing.json"), JSON.pretty_generate(signing))
|
|
49
|
+
File.write(File.join(out_dir, "decimal.json"), JSON.pretty_generate(decimals))
|
|
50
|
+
puts "wrote #{signing.size} signing vectors, #{decimals.size} decimals"
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Regenerates the stage-2 vectors from the LEGACY Ruby implementation.
|
|
4
|
+
# bundle exec ruby -Ilib script/vectors/kraken_stage2.rb
|
|
5
|
+
require "honeymaker"
|
|
6
|
+
require "json"
|
|
7
|
+
require "faraday"
|
|
8
|
+
|
|
9
|
+
OUT = File.expand_path("../../crates/honeymaker/tests/vectors", __dir__)
|
|
10
|
+
SECRET = Base64.strict_encode64("test_secret_key_1234567890123456")
|
|
11
|
+
NONCE = 1_727_000_000_000_001
|
|
12
|
+
|
|
13
|
+
def legacy
|
|
14
|
+
c = Honeymaker::Clients::Kraken.new(api_key: "key", api_secret: SECRET)
|
|
15
|
+
c.define_singleton_method(:nonce) { NONCE }
|
|
16
|
+
c
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# --- A: AddOrder with cl_ord_id and deadline, as legacy signs and sends it ---
|
|
20
|
+
orders = [
|
|
21
|
+
{ ordertype: "market", type: "buy", volume: "0.0012", pair: "XBTEUR", oflags: ["viqc"],
|
|
22
|
+
cl_ord_id: "6f1c1a52-7c8e-4d0e-9a57-0b6f0f1d2e3a", deadline: "2026-09-30T12:00:10.000Z" },
|
|
23
|
+
{ ordertype: "limit", type: "buy", volume: "0.00119", pair: "XBTEUR", price: "49870.1", oflags: [],
|
|
24
|
+
cl_ord_id: "0d9e2c1b-2f4a-4b8c-8d1e-5a6b7c8d9e0f", deadline: "2026-09-30T12:00:10.123Z" },
|
|
25
|
+
{ ordertype: "market", type: "buy", volume: "60", pair: "XBTEUR", oflags: ["viqc"],
|
|
26
|
+
cl_ord_id: "6f1c1a52-7c8e-4d0e-9a57-0b6f0f1d2e3a", deadline: "2026-09-30T12:00:10.000Z", validate: true }
|
|
27
|
+
]
|
|
28
|
+
wire = orders.map do |args|
|
|
29
|
+
body = nil
|
|
30
|
+
stubs = Faraday::Adapter::Test::Stubs.new do |s|
|
|
31
|
+
s.post("/0/private/AddOrder") do |env|
|
|
32
|
+
body = env.body
|
|
33
|
+
[200, { "Content-Type" => "application/json" }, '{"error":[],"result":{"txid":["OTX-1"]}}']
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
c = legacy
|
|
37
|
+
c.instance_variable_set(:@connection, Faraday.new(url: Honeymaker::Clients::Kraken::URL) { |f| f.response :json; f.adapter :test, stubs })
|
|
38
|
+
raise "legacy did not answer" unless c.add_order(**args).success?
|
|
39
|
+
|
|
40
|
+
{ args: args, nonce: NONCE, api_key: "key", api_secret: SECRET, body: body,
|
|
41
|
+
headers: c.send(:private_headers, "/0/private/AddOrder", body).transform_keys(&:to_s) }
|
|
42
|
+
end
|
|
43
|
+
File.write(File.join(OUT, "kraken_add_order_wire.json"), JSON.pretty_generate(wire))
|
|
44
|
+
puts "wrote #{wire.size} AddOrder wire vectors"
|
|
45
|
+
|
|
46
|
+
# --- B: closed_orders_from_trades, the verbatim Ruby loop (with Task 8's dedupe), over scripted
|
|
47
|
+
# pages. Each case says what the STRICT Rust scan must do: "same" as Ruby (a completed scan, a
|
|
48
|
+
# refusal, an unreadable page or a Ruby exception), "incomplete" where Ruby stopped early or
|
|
49
|
+
# trusted a malformed container and returned what it had, or "error" where Ruby skipped a malformed
|
|
50
|
+
# record that Rust must refuse. The label is what Rust must do, not what Ruby did: some
|
|
51
|
+
# "incomplete" cases are Ruby raises (NoMethodError on trades as a non-empty array, a non-empty
|
|
52
|
+
# string or a number, and on count true or an array), not partial answers. Their "outcome" says so.
|
|
53
|
+
def page(trades, count = :none)
|
|
54
|
+
result = { "trades" => trades }
|
|
55
|
+
result["count"] = count unless count == :none
|
|
56
|
+
{ "error" => [], "result" => result }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def trade(o, vol, cost, type: "buy", ordertype: "market", fee: "0.1")
|
|
60
|
+
{ "ordertxid" => o, "vol" => vol, "cost" => cost, "fee" => fee, "type" => type, "ordertype" => ordertype, "time" => 1.5 }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
T = ->(i, o = "O1") { { "T#{i}" => trade(o, "1", "2") } }
|
|
64
|
+
cases = [
|
|
65
|
+
["one page", :same, %w[O1], 1_700_000_000, 20, [page({ "T1" => trade("O1", "0.0006", "30"), "T2" => trade("O1", "0.0006", "30") }, 2)]],
|
|
66
|
+
["partial fill across pages, unwanted skipped", :same, %w[O1 O2], 1_700_000_000, 20,
|
|
67
|
+
[page({ "T1" => trade("O1", "1", "2"), "T2" => trade("O9", "1", "2") }, 3),
|
|
68
|
+
page({ "T3" => trade("O1", "1", "2"), "T4" => trade("O2", "2", "8", ordertype: "limit", type: "sell") }, 3)]],
|
|
69
|
+
["no wanted ids: no request", :same, [], nil, 20, []],
|
|
70
|
+
["a trade on two pages: rows reach count, distinct trades do not", :incomplete, %w[O1], nil, 20,
|
|
71
|
+
[page({ "T1" => trade("O1", "1", "2"), "T2" => trade("O1", "3", "9") }, 3), page({ "T2" => trade("O1", "3", "9") }, 3)]],
|
|
72
|
+
["overlapping pages that cover every trade", :same, %w[O1], nil, 20,
|
|
73
|
+
[page({ "T1" => trade("O1", "1", "2"), "T2" => trade("O1", "3", "9") }, 3),
|
|
74
|
+
page({ "T2" => trade("O1", "3", "9"), "T3" => trade("O1", "1", "1") }, 3)]],
|
|
75
|
+
["complete exactly at the page cap", :same, %w[O1], nil, 2, [page(T[1], 2), page(T[2], 2)]],
|
|
76
|
+
["an empty page after the last trade", :same, %w[O1], nil, 20, [page({}, 0)]],
|
|
77
|
+
["a fill beyond the page cap", :incomplete, %w[O1], nil, 2, [page(T[1, "O9"], 3), page(T[2, "O9"], 3), page(T[3], 3)]],
|
|
78
|
+
["count as a string", :incomplete, %w[O1], nil, 20, [page(T[1], "2"), page(T[2], "2")]],
|
|
79
|
+
["count 1_000 pages to max_pages", :incomplete, %w[O1], nil, 2, [page(T[1], "1_000"), page(T[2], "1_000"), page(T[3], "1_000")]],
|
|
80
|
+
["bignum string count", :incomplete, %w[O1], nil, 3, [page(T[1], "99999999999999999999999"), page(T[2], "1"), page(T[3], "1")]],
|
|
81
|
+
["bignum integer count", :incomplete, %w[O1], nil, 3, [page(T[1], 10**25), page(T[2], 10**25), page(T[3], 10**25), page(T[4], 10**25)]],
|
|
82
|
+
["float count", :incomplete, %w[O1], nil, 20, [page(T[1], 2.9), page(T[2], 2.9), page(T[3], 2.9)]],
|
|
83
|
+
["missing count", :incomplete, %w[O1], nil, 20, [page(T[1]), page(T[2])]],
|
|
84
|
+
["nil count", :incomplete, %w[O1], nil, 20, [page(T[1], nil), page(T[2])]],
|
|
85
|
+
["negative count", :incomplete, %w[O1], nil, 20, [page(T[1], -1), page(T[2])]],
|
|
86
|
+
*[" 3", "3abc", "0x10", "_3", "3__0", "+3", "-3", "", "abc"].map do |c|
|
|
87
|
+
["count #{c.inspect}", :incomplete, %w[O1], nil, 4, (1..4).map { |i| page(T[i], c) }]
|
|
88
|
+
end,
|
|
89
|
+
["empty trades before count", :incomplete, %w[O1], nil, 20, [page({}, 5), page(T[1], 5)]],
|
|
90
|
+
["missing trades", :incomplete, %w[O1], nil, 20, [{ "error" => [], "result" => { "count" => 3 } }, page(T[1])]],
|
|
91
|
+
["trades false", :incomplete, %w[O1], nil, 20, [page(false, 3), page(T[1])]],
|
|
92
|
+
["trades []", :incomplete, %w[O1], nil, 20, [page([], 3), page(T[1])]],
|
|
93
|
+
["trades \"\"", :incomplete, %w[O1], nil, 20, [page("", 3), page(T[1])]],
|
|
94
|
+
["nil result", :incomplete, %w[O1], nil, 20, [{ "error" => [] }, page(T[1])]],
|
|
95
|
+
["result as a string raises", :same, %w[O1], nil, 20, [{ "error" => [], "result" => "x" }]],
|
|
96
|
+
["result as an array raises", :same, %w[O1], nil, 20, [{ "error" => [], "result" => [1] }]],
|
|
97
|
+
["result false raises", :same, %w[O1], nil, 20, [{ "error" => [], "result" => false }]],
|
|
98
|
+
["trades as a non-empty array", :incomplete, %w[O1], nil, 20, [page([1], 1)]],
|
|
99
|
+
["trades as a non-empty string", :incomplete, %w[O1], nil, 20, [page("x", 1)]],
|
|
100
|
+
["trades as a number", :incomplete, %w[O1], nil, 20, [page(5, 1)]],
|
|
101
|
+
*[[false, "false"], [[], "[]"], ["", "\"\""], [nil, "null"]].map do |trades, label|
|
|
102
|
+
["trades #{label} with count 0", :incomplete, %w[O1], nil, 20, [page(trades, 0)]]
|
|
103
|
+
end,
|
|
104
|
+
["trades missing with count 0", :incomplete, %w[O1], nil, 20, [{ "error" => [], "result" => { "count" => 0 } }]],
|
|
105
|
+
["a trade that is a string", :error, %w[O1], nil, 20, [page({ "T1" => "no id here", "T2" => trade("O1", "1", "2") }, 2)]],
|
|
106
|
+
["a record without ordertxid", :error, %w[O1], nil, 20, [page({ "T1" => { "vol" => "1", "cost" => "2" } }, 1)]],
|
|
107
|
+
["a record with a numeric ordertxid", :error, %w[O1], nil, 20, [page({ "T1" => trade(5, "1", "2") }, 1)]],
|
|
108
|
+
["a trade that is a number raises", :same, %w[O1], nil, 20, [page({ "T1" => 5 }, 1)]],
|
|
109
|
+
["a trade that is null raises", :same, %w[O1], nil, 20, [page({ "T1" => nil }, 1)]],
|
|
110
|
+
["count true", :incomplete, %w[O1], nil, 20, [page(T[1], true)]],
|
|
111
|
+
["count as an array", :incomplete, %w[O1], nil, 20, [page(T[1], [1])]],
|
|
112
|
+
["refusal on page 2", :same, %w[O1], nil, 20, [page(T[1], 5), { "error" => ["EAPI:Rate limit exceeded"] }]],
|
|
113
|
+
["falsy errors are not a refusal", :same, %w[O1], nil, 20, [{ "error" => [nil, false], "result" => { "trades" => T[1], "count" => 1 } }]],
|
|
114
|
+
["non-object page 2 is unreadable", :same, %w[O1], nil, 20, [page(T[1], 5), "oops"]],
|
|
115
|
+
["pages run out: legacy sees null", :same, %w[O1], nil, 20, [page(T[1], 5)]],
|
|
116
|
+
["zero volume has no price", :same, %w[O1], nil, 20, [page({ "T1" => trade("O1", "0", "0") }, 1)]]
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
pager = cases.map do |name, strict, ids, start, max_pages, pages|
|
|
120
|
+
texts = pages.map { |p| JSON.generate(p) }
|
|
121
|
+
queue = texts.dup
|
|
122
|
+
requests = []
|
|
123
|
+
c = legacy
|
|
124
|
+
c.define_singleton_method(:get_trades_history) do |start: nil, ofs: nil, **|
|
|
125
|
+
requests << [start, ofs]
|
|
126
|
+
Honeymaker::Result::Success.new(JSON.parse(queue.shift || "null"))
|
|
127
|
+
end
|
|
128
|
+
outcome = begin
|
|
129
|
+
r = c.closed_orders_from_trades(order_ids: ids, start: start, max_pages: max_pages)
|
|
130
|
+
if r.success?
|
|
131
|
+
{ "ok" => r.data.map do |txid, a|
|
|
132
|
+
[txid, { "vol" => a[:amount_exec].to_s("F"), "cost" => a[:quote_amount_exec].to_s("F"), "fee" => a[:fee].to_s("F"),
|
|
133
|
+
"price" => a[:price]&.to_s("F"), "side" => a[:side]&.to_s, "order_type" => a[:order_type].to_s }]
|
|
134
|
+
end }
|
|
135
|
+
elsif r.data.is_a?(Hash) && r.data[:unreadable]
|
|
136
|
+
{ "unreadable" => true }
|
|
137
|
+
else
|
|
138
|
+
{ "venue" => r.errors }
|
|
139
|
+
end
|
|
140
|
+
rescue StandardError => e
|
|
141
|
+
{ "raise" => e.class.name }
|
|
142
|
+
end
|
|
143
|
+
{ name: name, strict: strict, order_ids: ids, start: start, max_pages: max_pages, pages: texts, requests: requests, outcome: outcome }
|
|
144
|
+
end
|
|
145
|
+
File.write(File.join(OUT, "kraken_trades_pager.json"), JSON.pretty_generate(pager))
|
|
146
|
+
puts "wrote #{pager.size} trades-pager vectors"
|
data/sig/honeymaker.rbs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"symbols": [
|
|
3
|
+
{
|
|
4
|
+
"symbol": "BTCUSDT",
|
|
5
|
+
"status": "TRADING",
|
|
6
|
+
"baseAsset": "BTC",
|
|
7
|
+
"quoteAsset": "USDT",
|
|
8
|
+
"quoteAssetPrecision": 8,
|
|
9
|
+
"filters": [
|
|
10
|
+
{
|
|
11
|
+
"filterType": "PRICE_FILTER",
|
|
12
|
+
"minPrice": "0.01",
|
|
13
|
+
"maxPrice": "1000000.00",
|
|
14
|
+
"tickSize": "0.01"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"filterType": "LOT_SIZE",
|
|
18
|
+
"minQty": "0.00000100",
|
|
19
|
+
"maxQty": "9000.00000000",
|
|
20
|
+
"stepSize": "0.000001"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"filterType": "NOTIONAL",
|
|
24
|
+
"minNotional": "5.00000000",
|
|
25
|
+
"maxNotional": "9000000.00000000"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": {
|
|
3
|
+
"symbols": [
|
|
4
|
+
{
|
|
5
|
+
"symbol": "BTC-USDT",
|
|
6
|
+
"minQty": "0.00001",
|
|
7
|
+
"maxQty": "500",
|
|
8
|
+
"minNotional": "1",
|
|
9
|
+
"maxNotional": "5000000",
|
|
10
|
+
"stepSize": "0.00001",
|
|
11
|
+
"tickSize": "0.01",
|
|
12
|
+
"status": 1
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"symbol": "INVALID",
|
|
16
|
+
"minQty": 1,
|
|
17
|
+
"maxQty": 1000,
|
|
18
|
+
"minNotional": 1,
|
|
19
|
+
"maxNotional": 10000,
|
|
20
|
+
"stepSize": "1",
|
|
21
|
+
"tickSize": "0.01",
|
|
22
|
+
"status": 1
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": [
|
|
3
|
+
{
|
|
4
|
+
"symbol": "BTCUSDT",
|
|
5
|
+
"baseCoin": "BTC",
|
|
6
|
+
"quoteCoin": "USDT",
|
|
7
|
+
"minTradeAmount": "0.00001",
|
|
8
|
+
"minTradeUSDT": "5",
|
|
9
|
+
"maxTradeAmount": "9000",
|
|
10
|
+
"quantityPrecision": "6",
|
|
11
|
+
"quotePrecision": "8",
|
|
12
|
+
"pricePrecision": "2",
|
|
13
|
+
"status": "online"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"symbol": "ETHUSDT",
|
|
17
|
+
"baseCoin": "ETH",
|
|
18
|
+
"quoteCoin": "USDT",
|
|
19
|
+
"minTradeAmount": "0.001",
|
|
20
|
+
"minTradeUSDT": "5",
|
|
21
|
+
"maxTradeAmount": "5000",
|
|
22
|
+
"quantityPrecision": "4",
|
|
23
|
+
"quotePrecision": "8",
|
|
24
|
+
"pricePrecision": "2",
|
|
25
|
+
"status": "offline"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": {
|
|
3
|
+
"symbols": [
|
|
4
|
+
{
|
|
5
|
+
"symbol": "BTC_USDT",
|
|
6
|
+
"base_currency": "BTC",
|
|
7
|
+
"quote_currency": "USDT",
|
|
8
|
+
"base_min_size": "0.00001",
|
|
9
|
+
"min_buy_amount": "5",
|
|
10
|
+
"quote_increment": "0.01",
|
|
11
|
+
"price_max_precision": 2,
|
|
12
|
+
"trade_status": "trading"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"symbol": "ETH_USDT",
|
|
16
|
+
"base_currency": "ETH",
|
|
17
|
+
"quote_currency": "USDT",
|
|
18
|
+
"base_min_size": "0.001",
|
|
19
|
+
"min_buy_amount": "5",
|
|
20
|
+
"quote_increment": "0.0001",
|
|
21
|
+
"price_max_precision": 4,
|
|
22
|
+
"trade_status": "pre-trade"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"symbols": [
|
|
3
|
+
{
|
|
4
|
+
"symbol": "BTCUSDT",
|
|
5
|
+
"baseAsset": "btc",
|
|
6
|
+
"quoteAsset": "usdt",
|
|
7
|
+
"baseAssetPrecision": 8,
|
|
8
|
+
"quotePrecision": 2,
|
|
9
|
+
"status": "TRADING",
|
|
10
|
+
"filters": [
|
|
11
|
+
{
|
|
12
|
+
"filterType": "PRICE_FILTER",
|
|
13
|
+
"tickSize": "0.01"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"filterType": "LOT_SIZE",
|
|
17
|
+
"minQty": "0.00001",
|
|
18
|
+
"maxQty": "9000",
|
|
19
|
+
"stepSize": "0.00001",
|
|
20
|
+
"minVal": "5"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"symbol": "ETHUSDT",
|
|
26
|
+
"baseAsset": "eth",
|
|
27
|
+
"quoteAsset": "usdt",
|
|
28
|
+
"baseAssetPrecision": 6,
|
|
29
|
+
"quotePrecision": 2,
|
|
30
|
+
"status": "TRADING",
|
|
31
|
+
"filters": []
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"market": "BTC-EUR",
|
|
4
|
+
"status": "trading",
|
|
5
|
+
"base": "BTC",
|
|
6
|
+
"quote": "EUR",
|
|
7
|
+
"minOrderInBaseAsset": "0.00001",
|
|
8
|
+
"minOrderInQuoteAsset": "5",
|
|
9
|
+
"maxOrderInBaseAsset": "1000",
|
|
10
|
+
"maxOrderInQuoteAsset": "1000000",
|
|
11
|
+
"pricePrecision": null,
|
|
12
|
+
"tickSize": "1.00",
|
|
13
|
+
"quantityDecimals": 8,
|
|
14
|
+
"notionalDecimals": 2,
|
|
15
|
+
"orderTypes": ["market", "limit"]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"market": "ETH-EUR",
|
|
19
|
+
"status": "halted",
|
|
20
|
+
"base": "ETH",
|
|
21
|
+
"quote": "EUR",
|
|
22
|
+
"minOrderInBaseAsset": "0.0001",
|
|
23
|
+
"minOrderInQuoteAsset": "5",
|
|
24
|
+
"pricePrecision": null,
|
|
25
|
+
"tickSize": "0.01",
|
|
26
|
+
"quantityDecimals": 6,
|
|
27
|
+
"notionalDecimals": 2,
|
|
28
|
+
"orderTypes": ["market", "limit"]
|
|
29
|
+
}
|
|
30
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"result": {
|
|
3
|
+
"list": [
|
|
4
|
+
{
|
|
5
|
+
"symbol": "BTCUSDT",
|
|
6
|
+
"baseCoin": "BTC",
|
|
7
|
+
"quoteCoin": "USDT",
|
|
8
|
+
"status": "Trading",
|
|
9
|
+
"lotSizeFilter": {
|
|
10
|
+
"minOrderQty": "0.000048",
|
|
11
|
+
"maxOrderQty": "71.73956243",
|
|
12
|
+
"minOrderAmt": "1",
|
|
13
|
+
"maxOrderAmt": "4000000",
|
|
14
|
+
"basePrecision": "0.000001",
|
|
15
|
+
"quotePrecision": "0.00000001"
|
|
16
|
+
},
|
|
17
|
+
"priceFilter": {
|
|
18
|
+
"tickSize": "0.01"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"retCode": 0,
|
|
3
|
+
"retMsg": "OK",
|
|
4
|
+
"result": {
|
|
5
|
+
"category": "spot",
|
|
6
|
+
"list": [
|
|
7
|
+
{
|
|
8
|
+
"symbol": "BTCUSDT",
|
|
9
|
+
"bid1Price": "67123.45",
|
|
10
|
+
"bid1Size": "1.234567",
|
|
11
|
+
"ask1Price": "67125.67",
|
|
12
|
+
"ask1Size": "0.987654",
|
|
13
|
+
"lastPrice": "67124.56",
|
|
14
|
+
"highPrice24h": "67500.00",
|
|
15
|
+
"lowPrice24h": "66800.00"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"products": [
|
|
3
|
+
{
|
|
4
|
+
"product_id": "BTC-USD",
|
|
5
|
+
"base_increment": "0.00000001",
|
|
6
|
+
"quote_increment": "0.01",
|
|
7
|
+
"price_increment": "0.01",
|
|
8
|
+
"base_min_size": "0.00000001",
|
|
9
|
+
"quote_min_size": "1",
|
|
10
|
+
"base_max_size": "3400",
|
|
11
|
+
"quote_max_size": "250000000"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"product_id": "RNDR-USD",
|
|
15
|
+
"base_increment": "0.01",
|
|
16
|
+
"quote_increment": "0.01",
|
|
17
|
+
"price_increment": "0.01",
|
|
18
|
+
"base_min_size": "0.01",
|
|
19
|
+
"quote_min_size": "1",
|
|
20
|
+
"base_max_size": "10000",
|
|
21
|
+
"quote_max_size": "250000000"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
["btcusd", "ethusd"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"error": [],
|
|
3
|
+
"result": {
|
|
4
|
+
"XBTUSDT": {
|
|
5
|
+
"altname": "XBTUSDT",
|
|
6
|
+
"wsname": "XBT/USDT",
|
|
7
|
+
"base": "XXBT",
|
|
8
|
+
"quote": "USDT",
|
|
9
|
+
"lot_decimals": 8,
|
|
10
|
+
"cost_decimals": 5,
|
|
11
|
+
"pair_decimals": 1,
|
|
12
|
+
"ordermin": "0.00010000",
|
|
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"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|