melaya 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/melaya/market.rb CHANGED
@@ -1,156 +1,198 @@
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
+ # ── restRoutes.ts market endpoints ──────────────────────────────────────────
151
+
152
+ # POST /api/v1/private/market/liquidations
153
+ # Get aggregated CEX liquidation data. (requireAuth)
154
+ # @param params [Hash] e.g. exchange, symbol, since_ms
155
+ def cex_liquidations(params = {})
156
+ @http.post("/api/v1/private/market/liquidations", params)
157
+ end
158
+
159
+ # GET /api/v1/market/mdd-pairs (public)
160
+ # Get max-drawdown pairs list (public screener data).
161
+ def mdd_pairs
162
+ @http.get("/api/v1/market/mdd-pairs")
163
+ end
164
+
165
+ # GET /api/v1/private/market/onchain-yields (Forge+ tier)
166
+ # Get on-chain yield data.
167
+ # @param params [Hash]
168
+ def onchain_yields(params = {})
169
+ @http.get("/api/v1/private/market/onchain-yields", params)
170
+ end
171
+
172
+ # GET /api/v1/private/market/onchain-liquidity (Forge+ tier)
173
+ # Get on-chain liquidity data.
174
+ # @param params [Hash]
175
+ def onchain_liquidity(params = {})
176
+ @http.get("/api/v1/private/market/onchain-liquidity", params)
177
+ end
178
+
179
+ # GET /api/v1/market/banner (public)
180
+ # Get marketing/notification banner content.
181
+ def banner
182
+ @http.get("/api/v1/market/banner")
183
+ end
184
+
185
+ # GET /api/v1/market/price-history (public)
186
+ # Get price history for chart display.
187
+ # @param params [Hash] e.g. exchange, symbol, timeframe
188
+ def price_history(params = {})
189
+ @http.get("/api/v1/market/price-history", params)
190
+ end
191
+
192
+ private
193
+
194
+ def compact(hash)
195
+ hash.reject { |_, v| v.nil? }
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Melaya
4
+ # ── Domain namespace objects ─────────────────────────────────────────────────
5
+ #
6
+ # Three read-only namespace objects hang off every +Melaya::Client+ instance,
7
+ # grouping the flat module accessors into logical planes:
8
+ #
9
+ # melaya.trading — market data, account, sim, strategies, backtest, stream, trade
10
+ # melaya.agents — pipelines/runs, hitl, assistant, phone, evals, models
11
+ # melaya.platform — projects, credentials, connectors, billing, team, templates,
12
+ # overview (via pipelines), runner, auth, mfa (via auth),
13
+ # accounts, bugs, events
14
+ #
15
+ # Every attribute on these objects is the *same* instance that is also reachable
16
+ # via the flat accessor on the client, so there is no duplication of state and
17
+ # no double HTTP calls.
18
+ #
19
+ # @example
20
+ # melaya = Melaya::Client.new(api_key: ENV["MELAYA_API_KEY"])
21
+ #
22
+ # # Namespaced (primary API)
23
+ # melaya.trading.market.ticker(exchange: "binance", symbol: "BTC/USDT", market: "spot")
24
+ # melaya.agents.pipelines.list(project: "my-project")
25
+ # melaya.platform.projects.list
26
+ #
27
+ # # Flat aliases still work (backward-compatible)
28
+ # melaya.market.ticker(exchange: "binance", symbol: "BTC/USDT", market: "spot")
29
+
30
+ # Namespace grouping all trading-plane modules.
31
+ #
32
+ # Modules:
33
+ # - +market+ — REST market-data + reference endpoints (public + authenticated).
34
+ # - +account+ — Authenticated account reads: connected keys, tier limits, usage.
35
+ # - +sim+ — Paper trading (sim broker): virtual balance, positions, and orders.
36
+ # - +strategies+ — Launch, control, and inspect trading strategies (paper + live).
37
+ # - +backtest+ — Historical backtests + parameter sweeps on the Rust engine.
38
+ # - +stream+ — WebSocket streaming endpoints (public market data + private feeds).
39
+ # - +trade+ — Live trading — credentialed order placement on a connected exchange.
40
+ TradingNamespace = Struct.new(
41
+ :market,
42
+ :account,
43
+ :sim,
44
+ :strategies,
45
+ :backtest,
46
+ :stream,
47
+ :trade,
48
+ keyword_init: true
49
+ )
50
+
51
+ # Namespace grouping all agent-plane modules.
52
+ #
53
+ # Modules:
54
+ # - +pipelines+ — Pipeline runs, traces, schedules, and overview dashboard.
55
+ # (also aliased as +runs+ for ergonomics)
56
+ # - +hitl+ — Human-in-the-loop approval queue: list pending, approve, reject.
57
+ # - +assistant+ — Assistant onboarding profile (get + set).
58
+ # - +phone+ — Phone device control: pair, list, screen-tree, apps.
59
+ # - +evals+ — Agent evaluation runs and benchmarks.
60
+ # - +models+ — AI model list (reached via credentials#list_models; this is
61
+ # the CredentialsAPI instance filtered by convention — call
62
+ # +models.list_models(provider: "anthropic")+ etc.)
63
+ AgentsNamespace = Struct.new(
64
+ :pipelines,
65
+ :hitl,
66
+ :assistant,
67
+ :phone,
68
+ :evals,
69
+ :models,
70
+ keyword_init: true
71
+ ) do
72
+ # +runs+ is an ergonomic alias for +pipelines+ (agents call them "runs").
73
+ def runs
74
+ pipelines
75
+ end
76
+ end
77
+
78
+ # Namespace grouping all platform-plane modules.
79
+ #
80
+ # Modules:
81
+ # - +projects+ — Create and list agent projects.
82
+ # - +credentials+ — User-scoped credential storage (services, OAuth, env handles, models).
83
+ # - +connectors+ — Project-scoped connector credentials.
84
+ # - +billing+ — Subscription, Stripe checkout/portal, pricing plans, credit balances.
85
+ # - +team+ — Project team management: members, roles, invite links.
86
+ # - +templates+ — Pipeline templates: create, share, assign, and manage visibility.
87
+ # - +overview+ — Pipeline overview dashboard (same object as +agents.pipelines+,
88
+ # exposed here for discoverability on the platform plane).
89
+ # - +runner+ — Runner tokens: mint, list, revoke +mel_run_+ tokens.
90
+ # - +auth+ — Login, MFA, registration, password management, session tokens.
91
+ # - +mfa+ — Alias for +auth+ (MFA operations live on the same AuthAPI object).
92
+ # - +accounts+ — Account management: GDPR export, CEX key removal, profile updates.
93
+ # - +bugs+ — Bug reports: submit, track, and comment.
94
+ # - +events+ — Platform real-time events over Socket.IO.
95
+ PlatformNamespace = Struct.new(
96
+ :projects,
97
+ :credentials,
98
+ :connectors,
99
+ :billing,
100
+ :team,
101
+ :templates,
102
+ :overview,
103
+ :runner,
104
+ :auth,
105
+ :accounts,
106
+ :bugs,
107
+ :events,
108
+ keyword_init: true
109
+ ) do
110
+ # +mfa+ is an ergonomic alias for +auth+ (MFA methods live on AuthAPI).
111
+ def mfa
112
+ auth
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Melaya
4
+ # Phone API — pair and control Android devices connected to the Melaya runner.
5
+ #
6
+ # Agents use these endpoints to drive a paired phone (tap, type, read
7
+ # screen state, launch apps) via the Melaya APK.
8
+ #
9
+ # Maps to /api/v1/private/phone/*.
10
+ #
11
+ # @example
12
+ # result = melaya.phone.pair
13
+ # puts "Pairing code: #{result["code"]}"
14
+ # # User enters code in the Melaya APK on the phone
15
+ #
16
+ # devices = melaya.phone.list_devices
17
+ # tree = melaya.phone.screen_tree
18
+ class PhoneAPI
19
+ def initialize(http)
20
+ @http = http
21
+ end
22
+
23
+ # POST /api/v1/private/phone/pair
24
+ # Start phone device pairing — generates a pairing code for the Melaya APK.
25
+ def pair
26
+ @http.post("/api/v1/private/phone/pair")
27
+ end
28
+
29
+ # GET /api/v1/private/phone/devices
30
+ # List all paired phone devices for the authenticated user.
31
+ def list_devices
32
+ @http.get("/api/v1/private/phone/devices").fetch("devices", [])
33
+ end
34
+
35
+ # DELETE /api/v1/private/phone/devices/:deviceId
36
+ # Revoke a paired phone device by ID.
37
+ # @param device_id [String]
38
+ def revoke_device(device_id)
39
+ @http.delete("/api/v1/private/phone/devices/#{enc(device_id)}")
40
+ end
41
+
42
+ # GET /api/v1/private/phone/screen-tree
43
+ # Get the current accessibility tree from the paired phone's screen.
44
+ def screen_tree
45
+ @http.get("/api/v1/private/phone/screen-tree")
46
+ end
47
+
48
+ # GET /api/v1/private/phone/apps
49
+ # List installed apps on the paired phone.
50
+ def list_apps
51
+ @http.get("/api/v1/private/phone/apps").dig("result", "apps") || []
52
+ end
53
+
54
+ # PUT /api/v1/private/phone/apps/allowed
55
+ # Set the allowlist of apps that agents are permitted to interact with.
56
+ # @param package_names [Array<String>]
57
+ def set_allowed_apps(package_names)
58
+ apps = package_names.map { |package| { "package" => package } }
59
+ @http.put("/api/v1/private/phone/apps/allowed", "apps" => apps)
60
+ end
61
+
62
+ # POST /api/v1/private/phone/active-run
63
+ # Register the currently active pipeline run on the phone (used by agents).
64
+ # @param run_id [String]
65
+ def register_active_run(run_id)
66
+ @http.post("/api/v1/private/phone/active-run", "runId" => run_id)
67
+ end
68
+
69
+ private
70
+
71
+ def enc(s)
72
+ URI.encode_www_form_component(s.to_s)
73
+ end
74
+ end
75
+ end