melaya 0.1.2 → 0.1.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/README.md +163 -163
- data/lib/melaya/account.rb +30 -30
- data/lib/melaya/backtest.rb +101 -101
- data/lib/melaya/errors.rb +15 -15
- data/lib/melaya/http_client.rb +97 -97
- data/lib/melaya/market.rb +156 -156
- data/lib/melaya/sim.rb +110 -110
- data/lib/melaya/strategies.rb +155 -155
- data/lib/melaya/stream.rb +336 -336
- data/lib/melaya/trade.rb +168 -168
- data/lib/melaya/version.rb +1 -1
- data/lib/melaya.rb +79 -79
- data/melaya.gemspec +23 -23
- metadata +6 -3
data/lib/melaya/http_client.rb
CHANGED
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "net/http"
|
|
4
|
-
require "uri"
|
|
5
|
-
require "json"
|
|
6
|
-
require "openssl"
|
|
7
|
-
|
|
8
|
-
require_relative "errors"
|
|
9
|
-
|
|
10
|
-
module Melaya
|
|
11
|
-
# Internal HTTP client. Injects the API key on every call as both
|
|
12
|
-
# a query-param (?apiKey=) and Authorization: Bearer header.
|
|
13
|
-
class HttpClient
|
|
14
|
-
DEFAULT_BASE_URL = "https://api.melaya.org"
|
|
15
|
-
|
|
16
|
-
def initialize(api_key:, base_url: DEFAULT_BASE_URL, verify_ssl: true)
|
|
17
|
-
@api_key = api_key
|
|
18
|
-
@base_uri = URI.parse(base_url.chomp("/"))
|
|
19
|
-
@verify_ssl = verify_ssl
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def get(path, params = {})
|
|
23
|
-
request(:get, path, params: params)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def post(path, body = nil)
|
|
27
|
-
request(:post, path, body: body)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def delete(path, params = {})
|
|
31
|
-
request(:delete, path, params: params)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
def build_uri(path, params = {})
|
|
37
|
-
uri = URI.parse("#{@base_uri}#{path}")
|
|
38
|
-
query = { "apiKey" => @api_key }
|
|
39
|
-
params.each { |k, v| query[k.to_s] = v.to_s unless v.nil? }
|
|
40
|
-
uri.query = URI.encode_www_form(query)
|
|
41
|
-
uri
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def request(method, path, params: {}, body: nil)
|
|
45
|
-
uri = build_uri(path, params)
|
|
46
|
-
|
|
47
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
48
|
-
http.use_ssl = uri.scheme == "https"
|
|
49
|
-
http.verify_mode = @verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
|
50
|
-
http.open_timeout = 15
|
|
51
|
-
http.read_timeout = 60
|
|
52
|
-
|
|
53
|
-
req = case method
|
|
54
|
-
when :get then Net::HTTP::Get.new(uri)
|
|
55
|
-
when :post then Net::HTTP::Post.new(uri)
|
|
56
|
-
when :delete then Net::HTTP::Delete.new(uri)
|
|
57
|
-
else raise ArgumentError, "Unknown HTTP method: #{method}"
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
req["Authorization"] = "Bearer #{@api_key}"
|
|
61
|
-
req["Accept"] = "application/json"
|
|
62
|
-
|
|
63
|
-
if body
|
|
64
|
-
req["Content-Type"] = "application/json"
|
|
65
|
-
req.body = JSON.generate(body)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
resp = http.request(req)
|
|
69
|
-
parse(resp)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def parse(resp)
|
|
73
|
-
text = resp.body.to_s.strip
|
|
74
|
-
data = begin
|
|
75
|
-
text.empty? ? nil : JSON.parse(text)
|
|
76
|
-
rescue JSON::ParserError
|
|
77
|
-
text
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
if resp.code.to_i >= 400
|
|
81
|
-
code = data.is_a?(Hash) ? data["error"] : nil
|
|
82
|
-
msg = "Melaya API #{resp.code}" + (code ? " (#{code})" : "")
|
|
83
|
-
raise MelayaError.new(msg, status: resp.code.to_i, code: code, body: data)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# The API wraps every payload in { "ok": true/false, ... }.
|
|
87
|
-
# ok:false is a request-level failure — raise instead of returning silently.
|
|
88
|
-
if data.is_a?(Hash) && data["ok"] == false
|
|
89
|
-
code = data["error"]
|
|
90
|
-
msg = "Melaya API request failed" + (code ? ": #{code}" : "")
|
|
91
|
-
raise MelayaError.new(msg, status: resp.code.to_i, code: code, body: data)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
data
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "json"
|
|
6
|
+
require "openssl"
|
|
7
|
+
|
|
8
|
+
require_relative "errors"
|
|
9
|
+
|
|
10
|
+
module Melaya
|
|
11
|
+
# Internal HTTP client. Injects the API key on every call as both
|
|
12
|
+
# a query-param (?apiKey=) and Authorization: Bearer header.
|
|
13
|
+
class HttpClient
|
|
14
|
+
DEFAULT_BASE_URL = "https://api.melaya.org"
|
|
15
|
+
|
|
16
|
+
def initialize(api_key:, base_url: DEFAULT_BASE_URL, verify_ssl: true)
|
|
17
|
+
@api_key = api_key
|
|
18
|
+
@base_uri = URI.parse(base_url.chomp("/"))
|
|
19
|
+
@verify_ssl = verify_ssl
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def get(path, params = {})
|
|
23
|
+
request(:get, path, params: params)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def post(path, body = nil)
|
|
27
|
+
request(:post, path, body: body)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def delete(path, params = {})
|
|
31
|
+
request(:delete, path, params: params)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def build_uri(path, params = {})
|
|
37
|
+
uri = URI.parse("#{@base_uri}#{path}")
|
|
38
|
+
query = { "apiKey" => @api_key }
|
|
39
|
+
params.each { |k, v| query[k.to_s] = v.to_s unless v.nil? }
|
|
40
|
+
uri.query = URI.encode_www_form(query)
|
|
41
|
+
uri
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def request(method, path, params: {}, body: nil)
|
|
45
|
+
uri = build_uri(path, params)
|
|
46
|
+
|
|
47
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
48
|
+
http.use_ssl = uri.scheme == "https"
|
|
49
|
+
http.verify_mode = @verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
|
50
|
+
http.open_timeout = 15
|
|
51
|
+
http.read_timeout = 60
|
|
52
|
+
|
|
53
|
+
req = case method
|
|
54
|
+
when :get then Net::HTTP::Get.new(uri)
|
|
55
|
+
when :post then Net::HTTP::Post.new(uri)
|
|
56
|
+
when :delete then Net::HTTP::Delete.new(uri)
|
|
57
|
+
else raise ArgumentError, "Unknown HTTP method: #{method}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
req["Authorization"] = "Bearer #{@api_key}"
|
|
61
|
+
req["Accept"] = "application/json"
|
|
62
|
+
|
|
63
|
+
if body
|
|
64
|
+
req["Content-Type"] = "application/json"
|
|
65
|
+
req.body = JSON.generate(body)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
resp = http.request(req)
|
|
69
|
+
parse(resp)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def parse(resp)
|
|
73
|
+
text = resp.body.to_s.strip
|
|
74
|
+
data = begin
|
|
75
|
+
text.empty? ? nil : JSON.parse(text)
|
|
76
|
+
rescue JSON::ParserError
|
|
77
|
+
text
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if resp.code.to_i >= 400
|
|
81
|
+
code = data.is_a?(Hash) ? data["error"] : nil
|
|
82
|
+
msg = "Melaya API #{resp.code}" + (code ? " (#{code})" : "")
|
|
83
|
+
raise MelayaError.new(msg, status: resp.code.to_i, code: code, body: data)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The API wraps every payload in { "ok": true/false, ... }.
|
|
87
|
+
# ok:false is a request-level failure — raise instead of returning silently.
|
|
88
|
+
if data.is_a?(Hash) && data["ok"] == false
|
|
89
|
+
code = data["error"]
|
|
90
|
+
msg = "Melaya API request failed" + (code ? ": #{code}" : "")
|
|
91
|
+
raise MelayaError.new(msg, status: resp.code.to_i, code: code, body: data)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
data
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
data/lib/melaya/market.rb
CHANGED
|
@@ -1,156 +1,156 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Melaya
|
|
4
|
-
# REST market-data API — normalized across all 70+ venues.
|
|
5
|
-
#
|
|
6
|
-
# Every method maps to a documented endpoint under
|
|
7
|
-
# https://api.melaya.org/api/v1/market/*. Unwraps the inner data key from the
|
|
8
|
-
# {ok, <data>} envelope.
|
|
9
|
-
class MarketAPI
|
|
10
|
-
def initialize(http)
|
|
11
|
-
@http = http
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# List the exchanges Melaya supports right now (the source of truth).
|
|
15
|
-
def list_exchanges
|
|
16
|
-
@http.get("/api/v1/market/list-exchanges")["exchanges"]
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Best bid/ask, last price, and 24h aggregates for one symbol.
|
|
20
|
-
# @param exchange [String]
|
|
21
|
-
# @param symbol [String]
|
|
22
|
-
# @param market [String, nil] e.g. "spot", "future", "swap"
|
|
23
|
-
def ticker(exchange:, symbol:, market: nil)
|
|
24
|
-
@http.get("/api/v1/market/ticker",
|
|
25
|
-
"exchange" => exchange, "symbol" => symbol, "market" => market
|
|
26
|
-
)["ticker"]
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Order book to a given depth.
|
|
30
|
-
def orderbook(exchange:, symbol:, limit: nil, market: nil)
|
|
31
|
-
@http.get("/api/v1/market/orderbook",
|
|
32
|
-
"exchange" => exchange, "symbol" => symbol, "limit" => limit, "market" => market
|
|
33
|
-
)["orderbook"]
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# OHLCV candles. Each candle is [timestamp, open, high, low, close, volume].
|
|
37
|
-
def ohlcv(exchange:, symbol:, timeframe:, limit: nil, market: nil)
|
|
38
|
-
@http.get("/api/v1/market/ohlcv",
|
|
39
|
-
"exchange" => exchange, "symbol" => symbol, "timeframe" => timeframe,
|
|
40
|
-
"limit" => limit, "market" => market
|
|
41
|
-
)["candles"]
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Recent public trades.
|
|
45
|
-
def trades(exchange:, symbol:, market: nil)
|
|
46
|
-
@http.get("/api/v1/market/trades",
|
|
47
|
-
"exchange" => exchange, "symbol" => symbol, "market" => market
|
|
48
|
-
)["trades"]
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Tradable markets on a venue.
|
|
52
|
-
def markets(exchange:)
|
|
53
|
-
@http.get("/api/v1/market/markets", "exchange" => exchange)["markets"]
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Listed currencies on a venue. (Not supported on every venue.)
|
|
57
|
-
def currencies(exchange:)
|
|
58
|
-
@http.get("/api/v1/market/currencies", "exchange" => exchange)["currencies"]
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Operational status: ok / maintenance / degraded.
|
|
62
|
-
def status(exchange:)
|
|
63
|
-
@http.get("/api/v1/market/status", "exchange" => exchange)["status"]
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Exchange server time.
|
|
67
|
-
def time(exchange:)
|
|
68
|
-
@http.get("/api/v1/market/time", "exchange" => exchange)["time"]
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# ── Batch / derivatives (POST) ──────────────────────────────────────────
|
|
72
|
-
|
|
73
|
-
# Tickers for many symbols on one venue in a single call. Keyed by symbol.
|
|
74
|
-
def tickers(exchange:, symbols:, market: nil)
|
|
75
|
-
body = compact("exchange" => exchange, "symbols" => symbols, "market" => market)
|
|
76
|
-
@http.post("/api/v1/market/tickers", body)["tickers"]
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Latest funding rates for perpetuals. Keyed by symbol.
|
|
80
|
-
def funding_rates(exchange:, symbols:, market: nil)
|
|
81
|
-
body = compact("exchange" => exchange, "symbols" => symbols, "market" => market)
|
|
82
|
-
@http.post("/api/v1/market/funding-rates", body)["rates"]
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Funding-rate history.
|
|
86
|
-
def funding_rate_history(exchange:, symbol:, hours: nil, market: nil)
|
|
87
|
-
body = compact("exchange" => exchange, "symbol" => symbol, "hours" => hours, "market" => market)
|
|
88
|
-
@http.post("/api/v1/market/funding-rate-history", body)["history"]
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Open interest for one or more perpetuals. Keyed by symbol.
|
|
92
|
-
def open_interest(exchange:, symbols:, market: nil)
|
|
93
|
-
body = compact("exchange" => exchange, "symbols" => symbols, "market" => market)
|
|
94
|
-
@http.post("/api/v1/market/open-interest", body)["openInterest"]
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Open-interest history.
|
|
98
|
-
def open_interest_history(exchange:, symbol:, hours: nil, market: nil)
|
|
99
|
-
body = compact("exchange" => exchange, "symbol" => symbol, "hours" => hours, "market" => market)
|
|
100
|
-
@http.post("/api/v1/market/open-interest-history", body)["history"]
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Instrument list + trading constraints (tick size, min notional, qty step).
|
|
104
|
-
def instruments(exchange:, market: nil)
|
|
105
|
-
body = compact("exchange" => exchange, "market" => market)
|
|
106
|
-
@http.post("/api/v1/market/instruments", body)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Cross-exchange liquidation events (historical query).
|
|
110
|
-
def liquidation_events(exchange: nil, symbol: nil, since_ms: nil, limit: nil)
|
|
111
|
-
body = compact("exchange" => exchange, "symbol" => symbol, "sinceMs" => since_ms, "limit" => limit)
|
|
112
|
-
@http.post("/api/v1/market/liquidation-events", body)["events"]
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Multi-symbol OHLCV in one call. Returns candle arrays keyed by symbol.
|
|
116
|
-
def ohlcv_multi(exchange:, symbols:, timeframe:, limit: nil, market: nil)
|
|
117
|
-
body = compact("exchange" => exchange, "symbols" => symbols, "timeframe" => timeframe,
|
|
118
|
-
"limit" => limit, "market" => market)
|
|
119
|
-
@http.post("/api/v1/market/ohlcv-multi", body)["perSymbol"]
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
# Trading constraints for one symbol (tick size, min notional, qty step, leverage).
|
|
123
|
-
def market_constraints(exchange:, symbol:, market: nil)
|
|
124
|
-
body = compact("exchange" => exchange, "symbol" => symbol, "market" => market)
|
|
125
|
-
@http.post("/api/v1/market/market-constraints", body)["constraints"]
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# Funding-rate history for one symbol across several venues. Keyed by exchange.
|
|
129
|
-
def funding_rate_history_multi(exchanges:, symbol:, hours: nil)
|
|
130
|
-
body = compact("exchanges" => exchanges, "symbol" => symbol, "hours" => hours)
|
|
131
|
-
@http.post("/api/v1/market/funding-rate-history-multi", body)["perExchange"]
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Open-interest history for one symbol across several venues. Keyed by exchange.
|
|
135
|
-
def open_interest_history_multi(exchanges:, symbol:, hours: nil)
|
|
136
|
-
body = compact("exchanges" => exchanges, "symbol" => symbol, "hours" => hours)
|
|
137
|
-
@http.post("/api/v1/market/open-interest-history-multi", body)["perExchange"]
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
# Prediction-market listings for a venue (polymarket, kalshi, drift_pm, sxbet, azuro, overtime).
|
|
141
|
-
def prediction_markets(venue: "polymarket")
|
|
142
|
-
@http.post("/api/v1/market/pm-markets", { "venue" => venue })["markets"]
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Live platform catalog counts (agentic tools, subagents, by category). Public.
|
|
146
|
-
def catalog_counts
|
|
147
|
-
@http.get("/api/v1/public/catalog-counts")
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
private
|
|
151
|
-
|
|
152
|
-
def compact(hash)
|
|
153
|
-
hash.reject { |_, v| v.nil? }
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# REST market-data API — normalized across all 70+ venues.
|
|
5
|
+
#
|
|
6
|
+
# Every method maps to a documented endpoint under
|
|
7
|
+
# https://api.melaya.org/api/v1/market/*. Unwraps the inner data key from the
|
|
8
|
+
# {ok, <data>} envelope.
|
|
9
|
+
class MarketAPI
|
|
10
|
+
def initialize(http)
|
|
11
|
+
@http = http
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# List the exchanges Melaya supports right now (the source of truth).
|
|
15
|
+
def list_exchanges
|
|
16
|
+
@http.get("/api/v1/market/list-exchanges")["exchanges"]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Best bid/ask, last price, and 24h aggregates for one symbol.
|
|
20
|
+
# @param exchange [String]
|
|
21
|
+
# @param symbol [String]
|
|
22
|
+
# @param market [String, nil] e.g. "spot", "future", "swap"
|
|
23
|
+
def ticker(exchange:, symbol:, market: nil)
|
|
24
|
+
@http.get("/api/v1/market/ticker",
|
|
25
|
+
"exchange" => exchange, "symbol" => symbol, "market" => market
|
|
26
|
+
)["ticker"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Order book to a given depth.
|
|
30
|
+
def orderbook(exchange:, symbol:, limit: nil, market: nil)
|
|
31
|
+
@http.get("/api/v1/market/orderbook",
|
|
32
|
+
"exchange" => exchange, "symbol" => symbol, "limit" => limit, "market" => market
|
|
33
|
+
)["orderbook"]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# OHLCV candles. Each candle is [timestamp, open, high, low, close, volume].
|
|
37
|
+
def ohlcv(exchange:, symbol:, timeframe:, limit: nil, market: nil)
|
|
38
|
+
@http.get("/api/v1/market/ohlcv",
|
|
39
|
+
"exchange" => exchange, "symbol" => symbol, "timeframe" => timeframe,
|
|
40
|
+
"limit" => limit, "market" => market
|
|
41
|
+
)["candles"]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Recent public trades.
|
|
45
|
+
def trades(exchange:, symbol:, market: nil)
|
|
46
|
+
@http.get("/api/v1/market/trades",
|
|
47
|
+
"exchange" => exchange, "symbol" => symbol, "market" => market
|
|
48
|
+
)["trades"]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Tradable markets on a venue.
|
|
52
|
+
def markets(exchange:)
|
|
53
|
+
@http.get("/api/v1/market/markets", "exchange" => exchange)["markets"]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Listed currencies on a venue. (Not supported on every venue.)
|
|
57
|
+
def currencies(exchange:)
|
|
58
|
+
@http.get("/api/v1/market/currencies", "exchange" => exchange)["currencies"]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Operational status: ok / maintenance / degraded.
|
|
62
|
+
def status(exchange:)
|
|
63
|
+
@http.get("/api/v1/market/status", "exchange" => exchange)["status"]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Exchange server time.
|
|
67
|
+
def time(exchange:)
|
|
68
|
+
@http.get("/api/v1/market/time", "exchange" => exchange)["time"]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# ── Batch / derivatives (POST) ──────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
# Tickers for many symbols on one venue in a single call. Keyed by symbol.
|
|
74
|
+
def tickers(exchange:, symbols:, market: nil)
|
|
75
|
+
body = compact("exchange" => exchange, "symbols" => symbols, "market" => market)
|
|
76
|
+
@http.post("/api/v1/market/tickers", body)["tickers"]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Latest funding rates for perpetuals. Keyed by symbol.
|
|
80
|
+
def funding_rates(exchange:, symbols:, market: nil)
|
|
81
|
+
body = compact("exchange" => exchange, "symbols" => symbols, "market" => market)
|
|
82
|
+
@http.post("/api/v1/market/funding-rates", body)["rates"]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Funding-rate history.
|
|
86
|
+
def funding_rate_history(exchange:, symbol:, hours: nil, market: nil)
|
|
87
|
+
body = compact("exchange" => exchange, "symbol" => symbol, "hours" => hours, "market" => market)
|
|
88
|
+
@http.post("/api/v1/market/funding-rate-history", body)["history"]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Open interest for one or more perpetuals. Keyed by symbol.
|
|
92
|
+
def open_interest(exchange:, symbols:, market: nil)
|
|
93
|
+
body = compact("exchange" => exchange, "symbols" => symbols, "market" => market)
|
|
94
|
+
@http.post("/api/v1/market/open-interest", body)["openInterest"]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Open-interest history.
|
|
98
|
+
def open_interest_history(exchange:, symbol:, hours: nil, market: nil)
|
|
99
|
+
body = compact("exchange" => exchange, "symbol" => symbol, "hours" => hours, "market" => market)
|
|
100
|
+
@http.post("/api/v1/market/open-interest-history", body)["history"]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Instrument list + trading constraints (tick size, min notional, qty step).
|
|
104
|
+
def instruments(exchange:, market: nil)
|
|
105
|
+
body = compact("exchange" => exchange, "market" => market)
|
|
106
|
+
@http.post("/api/v1/market/instruments", body)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Cross-exchange liquidation events (historical query).
|
|
110
|
+
def liquidation_events(exchange: nil, symbol: nil, since_ms: nil, limit: nil)
|
|
111
|
+
body = compact("exchange" => exchange, "symbol" => symbol, "sinceMs" => since_ms, "limit" => limit)
|
|
112
|
+
@http.post("/api/v1/market/liquidation-events", body)["events"]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Multi-symbol OHLCV in one call. Returns candle arrays keyed by symbol.
|
|
116
|
+
def ohlcv_multi(exchange:, symbols:, timeframe:, limit: nil, market: nil)
|
|
117
|
+
body = compact("exchange" => exchange, "symbols" => symbols, "timeframe" => timeframe,
|
|
118
|
+
"limit" => limit, "market" => market)
|
|
119
|
+
@http.post("/api/v1/market/ohlcv-multi", body)["perSymbol"]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Trading constraints for one symbol (tick size, min notional, qty step, leverage).
|
|
123
|
+
def market_constraints(exchange:, symbol:, market: nil)
|
|
124
|
+
body = compact("exchange" => exchange, "symbol" => symbol, "market" => market)
|
|
125
|
+
@http.post("/api/v1/market/market-constraints", body)["constraints"]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Funding-rate history for one symbol across several venues. Keyed by exchange.
|
|
129
|
+
def funding_rate_history_multi(exchanges:, symbol:, hours: nil)
|
|
130
|
+
body = compact("exchanges" => exchanges, "symbol" => symbol, "hours" => hours)
|
|
131
|
+
@http.post("/api/v1/market/funding-rate-history-multi", body)["perExchange"]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Open-interest history for one symbol across several venues. Keyed by exchange.
|
|
135
|
+
def open_interest_history_multi(exchanges:, symbol:, hours: nil)
|
|
136
|
+
body = compact("exchanges" => exchanges, "symbol" => symbol, "hours" => hours)
|
|
137
|
+
@http.post("/api/v1/market/open-interest-history-multi", body)["perExchange"]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Prediction-market listings for a venue (polymarket, kalshi, drift_pm, sxbet, azuro, overtime).
|
|
141
|
+
def prediction_markets(venue: "polymarket")
|
|
142
|
+
@http.post("/api/v1/market/pm-markets", { "venue" => venue })["markets"]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Live platform catalog counts (agentic tools, subagents, by category). Public.
|
|
146
|
+
def catalog_counts
|
|
147
|
+
@http.get("/api/v1/public/catalog-counts")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
def compact(hash)
|
|
153
|
+
hash.reject { |_, v| v.nil? }
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|