melaya 0.1.0__tar.gz

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.
@@ -0,0 +1,32 @@
1
+ # Build artifacts
2
+ **/node_modules/
3
+ **/dist/
4
+ **/target/
5
+ **/build/
6
+ **/.gradle/
7
+ **/bin/
8
+ **/obj/
9
+ **/vendor/
10
+ **/__pycache__/
11
+ *.egg-info/
12
+ .pytest_cache/
13
+ # Gradle/JVM
14
+ *.class
15
+ .gradle/
16
+ # logs / tmp
17
+ *.log
18
+ *.tmp
19
+ # IDE
20
+ .idea/
21
+ .vscode/
22
+ # package locks we don't ship from staging
23
+ **/go.sum
24
+ **/*.exe
25
+
26
+ # secrets (belt-and-suspenders)
27
+ .env
28
+ .env.*
29
+ **/.env
30
+ *.pem
31
+ *.key
32
+ id_rsa*
melaya-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: melaya
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Melaya unified market-data & streaming API across 70+ venues.
5
+ Project-URL: Homepage, https://melaya.org
6
+ Project-URL: Documentation, https://melaya.org/docs
7
+ Project-URL: Repository, https://github.com/melaya-labs/melaya
8
+ Project-URL: Issues, https://github.com/melaya-labs/melaya/issues
9
+ Author: Melaya
10
+ License-Expression: Apache-2.0
11
+ Keywords: agentic-ai,crypto,exchange,market-data,melaya,ohlcv,orderbook,sdk,ticker,trading,websocket
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Financial and Insurance Industry
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Office/Business :: Financial :: Investment
18
+ Requires-Python: >=3.9
19
+ Requires-Dist: httpx>=0.27
20
+ Provides-Extra: stream
21
+ Requires-Dist: websockets>=12; extra == 'stream'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # melaya (Python SDK)
25
+
26
+ Official Python SDK for the **[Melaya](https://melaya.org)** trading platform — normalized market data, paper + live trading, backtesting, and an AI agentic trading crew across **70+ venues**, powered by an in-house Rust engine.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install melaya # REST
32
+ pip install "melaya[stream]" # REST + WebSocket streaming
33
+ ```
34
+
35
+ ## Quick start
36
+
37
+ ```python
38
+ from melaya import Melaya
39
+
40
+ m = Melaya(api_key="mk_...") # keys are prefixed `mk_`
41
+
42
+ # Normalized ticker from any of 70+ venues
43
+ t = m.market.ticker(exchange="binance", symbol="BTC/USDT", market="spot")
44
+ print(t["last"], t["bid"], t["ask"])
45
+
46
+ # Order book + candles
47
+ book = m.market.orderbook(exchange="bybit", symbol="BTC/USDT", market="spot", limit=20)
48
+ candles = m.market.ohlcv(exchange="okx", symbol="ETH/USDT", timeframe="1h", limit=200)
49
+ ```
50
+
51
+ ## Streaming (async)
52
+
53
+ ```python
54
+ import asyncio
55
+ from melaya import Melaya
56
+
57
+ async def main():
58
+ m = Melaya(api_key="mk_...")
59
+ async for t in m.stream.ticker(exchange="binance", symbol="BTC/USDT", market="spot"):
60
+ print(t["last"])
61
+
62
+ asyncio.run(main())
63
+ ```
64
+
65
+ ## Trading
66
+
67
+ The same client covers your account, paper trading, live strategies, and backtests. Reads need only your `mk_` key; live order placement needs a connected exchange key (`m.account.keys()`).
68
+
69
+ ```python
70
+ # Account
71
+ keys = m.account.keys() # [{"apiKeyId": "BINANCEUSDM_0", "exchange": ..., "market": ...}]
72
+ usage = m.account.usage()
73
+
74
+ # Strategies — create() launches immediately. Paper (dry_run) needs no exchange key.
75
+ # SDK-launchable strategies are `custom` Rhai definitions (an `evaluate()` that
76
+ # emits signals: emit_long / emit_short / emit_close).
77
+ res = m.strategies.create(
78
+ name="My first bot", strategy_type="custom",
79
+ exchange="binanceusdm", symbol="BTC/USDT:USDT", market="FUTURES", dry_run=True,
80
+ params={"language": "rhai",
81
+ "definition": 'fn evaluate() { emit_long(param("qty")); }',
82
+ "qty": 0.001}, # dry_run=False + api_key_id places real orders
83
+ )
84
+ sid = res["strategyId"]
85
+ m.strategies.pause(sid)
86
+ m.strategies.resume(sid)
87
+ trades = m.strategies.trades(sid)
88
+
89
+ # Paper trading (sim broker) — synthetic fills, no venue state
90
+ bal = m.sim.balance(strategy_id=sid)
91
+ fill = m.sim.create_order(strategy_id=sid, exchange="binanceusdm",
92
+ symbol="BTC/USDT:USDT", side="buy", type="market",
93
+ amount=0.001, market="FUTURES")
94
+
95
+ # Backtest on the Rust engine
96
+ import time
97
+ start = m.backtest.start({"strategyType": "custom", "exchange": "binance",
98
+ "symbol": "BTC/USDT", "timeframe": "1h", "language": "rhai",
99
+ "definition": 'fn evaluate() { emit_long(param("qty")); }',
100
+ "params": {"qty": 0.001}})
101
+ job_id = start["job_id"]
102
+ while m.backtest.job(job_id)["status"] not in ("done", "error"):
103
+ time.sleep(2)
104
+ result = m.backtest.results(job_id) # metrics, equity_curve, ohlcv
105
+
106
+ # Live private strategy feed (async; ticket minted automatically)
107
+ async for ev in m.stream.strategies():
108
+ print(ev["type"], ev.get("strategyId"))
109
+ ```
110
+
111
+ ## Authentication
112
+
113
+ Create an API key in the dashboard (**melaya.org → Settings → API Keys**). Keys are prefixed `mk_`; the SDK sends it on every REST call and WebSocket connection. Public market-data and account/strategy reads work with the key alone. **Live** order placement and live strategy launches additionally require a connected exchange key — connect one in **Settings → Connectors**, then reference it by `api_key_id`. Paper trading and backtesting never touch a venue and need no exchange credentials.
114
+
115
+ ## API surface
116
+
117
+ | Area | Methods |
118
+ |---|---|
119
+ | Reference | `market.list_exchanges()`, `catalog_counts()` |
120
+ | Market data | `market.ticker`, `orderbook`, `ohlcv`, `ohlcv_multi`, `trades`, `markets`, `currencies`, `market_constraints`, `status`, `time` |
121
+ | Batch / derivatives | `market.tickers`, `funding_rates`, `funding_rate_history`, `funding_rate_history_multi`, `open_interest`, `open_interest_history`, `open_interest_history_multi`, `instruments`, `liquidation_events` |
122
+ | Prediction markets | `market.prediction_markets` (polymarket, kalshi, drift_pm, sxbet, azuro, overtime) |
123
+ | Account | `account.keys`, `usage`, `api_key_status` |
124
+ | Strategies | `strategies.create`, `list`, `get`, `pause`, `resume`, `stop`, `delete`, `update_params`, `status`, `performance`, `executions`, `trades`, `logs` |
125
+ | AI optimizer | `strategies.ai_opt_start`, `ai_opt_status`, `ai_opt_approve`, `ai_opt_stop`, `ai_opt_runs` |
126
+ | Paper trading | `sim.balance`, `positions`, `open_orders`, `my_trades`, `create_order`, `cancel_order`, `list_accounts` |
127
+ | Backtesting | `backtest.start`, `job`, `results`, `trades`, `sweep`, `list`, `favorites`, `funding_range`, `cancel`, `delete`, `delete_all` |
128
+ | Public streaming | `stream.ticker`, `orderbook`, `ohlcv`, `trades`, `liquidations` |
129
+ | Private streaming | `stream.strategies`, `stream.private` |
130
+ | Live trading | `trade.balance`, `positions`, `open_orders`, `orders`, `closed_orders`, `my_trades`, `my_trades_history`, `plan_orders`, `positions_history`, `leverage`, `leverage_tiers`, `create_order`, `cancel_order`, `amend_order`, `cancel_all_orders`, `cancel_plan_orders`, `close_position`, `set_leverage`, `set_margin_mode`, `set_position_mode` |
131
+
132
+ Full docs: **[melaya.org/docs](https://melaya.org/docs)**.
133
+
134
+ ## License
135
+
136
+ [Apache-2.0](../../LICENSE)
melaya-0.1.0/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # melaya (Python SDK)
2
+
3
+ Official Python SDK for the **[Melaya](https://melaya.org)** trading platform — normalized market data, paper + live trading, backtesting, and an AI agentic trading crew across **70+ venues**, powered by an in-house Rust engine.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install melaya # REST
9
+ pip install "melaya[stream]" # REST + WebSocket streaming
10
+ ```
11
+
12
+ ## Quick start
13
+
14
+ ```python
15
+ from melaya import Melaya
16
+
17
+ m = Melaya(api_key="mk_...") # keys are prefixed `mk_`
18
+
19
+ # Normalized ticker from any of 70+ venues
20
+ t = m.market.ticker(exchange="binance", symbol="BTC/USDT", market="spot")
21
+ print(t["last"], t["bid"], t["ask"])
22
+
23
+ # Order book + candles
24
+ book = m.market.orderbook(exchange="bybit", symbol="BTC/USDT", market="spot", limit=20)
25
+ candles = m.market.ohlcv(exchange="okx", symbol="ETH/USDT", timeframe="1h", limit=200)
26
+ ```
27
+
28
+ ## Streaming (async)
29
+
30
+ ```python
31
+ import asyncio
32
+ from melaya import Melaya
33
+
34
+ async def main():
35
+ m = Melaya(api_key="mk_...")
36
+ async for t in m.stream.ticker(exchange="binance", symbol="BTC/USDT", market="spot"):
37
+ print(t["last"])
38
+
39
+ asyncio.run(main())
40
+ ```
41
+
42
+ ## Trading
43
+
44
+ The same client covers your account, paper trading, live strategies, and backtests. Reads need only your `mk_` key; live order placement needs a connected exchange key (`m.account.keys()`).
45
+
46
+ ```python
47
+ # Account
48
+ keys = m.account.keys() # [{"apiKeyId": "BINANCEUSDM_0", "exchange": ..., "market": ...}]
49
+ usage = m.account.usage()
50
+
51
+ # Strategies — create() launches immediately. Paper (dry_run) needs no exchange key.
52
+ # SDK-launchable strategies are `custom` Rhai definitions (an `evaluate()` that
53
+ # emits signals: emit_long / emit_short / emit_close).
54
+ res = m.strategies.create(
55
+ name="My first bot", strategy_type="custom",
56
+ exchange="binanceusdm", symbol="BTC/USDT:USDT", market="FUTURES", dry_run=True,
57
+ params={"language": "rhai",
58
+ "definition": 'fn evaluate() { emit_long(param("qty")); }',
59
+ "qty": 0.001}, # dry_run=False + api_key_id places real orders
60
+ )
61
+ sid = res["strategyId"]
62
+ m.strategies.pause(sid)
63
+ m.strategies.resume(sid)
64
+ trades = m.strategies.trades(sid)
65
+
66
+ # Paper trading (sim broker) — synthetic fills, no venue state
67
+ bal = m.sim.balance(strategy_id=sid)
68
+ fill = m.sim.create_order(strategy_id=sid, exchange="binanceusdm",
69
+ symbol="BTC/USDT:USDT", side="buy", type="market",
70
+ amount=0.001, market="FUTURES")
71
+
72
+ # Backtest on the Rust engine
73
+ import time
74
+ start = m.backtest.start({"strategyType": "custom", "exchange": "binance",
75
+ "symbol": "BTC/USDT", "timeframe": "1h", "language": "rhai",
76
+ "definition": 'fn evaluate() { emit_long(param("qty")); }',
77
+ "params": {"qty": 0.001}})
78
+ job_id = start["job_id"]
79
+ while m.backtest.job(job_id)["status"] not in ("done", "error"):
80
+ time.sleep(2)
81
+ result = m.backtest.results(job_id) # metrics, equity_curve, ohlcv
82
+
83
+ # Live private strategy feed (async; ticket minted automatically)
84
+ async for ev in m.stream.strategies():
85
+ print(ev["type"], ev.get("strategyId"))
86
+ ```
87
+
88
+ ## Authentication
89
+
90
+ Create an API key in the dashboard (**melaya.org → Settings → API Keys**). Keys are prefixed `mk_`; the SDK sends it on every REST call and WebSocket connection. Public market-data and account/strategy reads work with the key alone. **Live** order placement and live strategy launches additionally require a connected exchange key — connect one in **Settings → Connectors**, then reference it by `api_key_id`. Paper trading and backtesting never touch a venue and need no exchange credentials.
91
+
92
+ ## API surface
93
+
94
+ | Area | Methods |
95
+ |---|---|
96
+ | Reference | `market.list_exchanges()`, `catalog_counts()` |
97
+ | Market data | `market.ticker`, `orderbook`, `ohlcv`, `ohlcv_multi`, `trades`, `markets`, `currencies`, `market_constraints`, `status`, `time` |
98
+ | Batch / derivatives | `market.tickers`, `funding_rates`, `funding_rate_history`, `funding_rate_history_multi`, `open_interest`, `open_interest_history`, `open_interest_history_multi`, `instruments`, `liquidation_events` |
99
+ | Prediction markets | `market.prediction_markets` (polymarket, kalshi, drift_pm, sxbet, azuro, overtime) |
100
+ | Account | `account.keys`, `usage`, `api_key_status` |
101
+ | Strategies | `strategies.create`, `list`, `get`, `pause`, `resume`, `stop`, `delete`, `update_params`, `status`, `performance`, `executions`, `trades`, `logs` |
102
+ | AI optimizer | `strategies.ai_opt_start`, `ai_opt_status`, `ai_opt_approve`, `ai_opt_stop`, `ai_opt_runs` |
103
+ | Paper trading | `sim.balance`, `positions`, `open_orders`, `my_trades`, `create_order`, `cancel_order`, `list_accounts` |
104
+ | Backtesting | `backtest.start`, `job`, `results`, `trades`, `sweep`, `list`, `favorites`, `funding_range`, `cancel`, `delete`, `delete_all` |
105
+ | Public streaming | `stream.ticker`, `orderbook`, `ohlcv`, `trades`, `liquidations` |
106
+ | Private streaming | `stream.strategies`, `stream.private` |
107
+ | Live trading | `trade.balance`, `positions`, `open_orders`, `orders`, `closed_orders`, `my_trades`, `my_trades_history`, `plan_orders`, `positions_history`, `leverage`, `leverage_tiers`, `create_order`, `cancel_order`, `amend_order`, `cancel_all_orders`, `cancel_plan_orders`, `close_position`, `set_leverage`, `set_margin_mode`, `set_position_mode` |
108
+
109
+ Full docs: **[melaya.org/docs](https://melaya.org/docs)**.
110
+
111
+ ## License
112
+
113
+ [Apache-2.0](../../LICENSE)
@@ -0,0 +1,270 @@
1
+ """FULL endpoint validation for the `melaya` Python SDK — every method, live.
2
+ Safety: paper/sim only. NEVER places a live order or launches a live strategy.
3
+ Destructive/billable endpoints (backtest.delete_all, strategies.ai_opt_start /
4
+ ai_opt_approve) are WIRED-checked, not invoked.
5
+ Run: MK=mk_... python smoke.py (SDK src on sys.path)
6
+ """
7
+ import asyncio
8
+ import os
9
+ import sys
10
+ import time
11
+
12
+ import os as _os; sys.path.insert(0, _os.path.join(_os.path.dirname(__file__), "..", "src"))
13
+ from melaya import Melaya, MelayaError # noqa: E402
14
+
15
+ KEY = os.environ.get("MK")
16
+ if not KEY:
17
+ print("set MK=mk_..."); sys.exit(2)
18
+ m = Melaya(api_key=KEY)
19
+ # Local dev box does TLS interception; disable cert verification for the test
20
+ # (mirrors NODE_TLS_REJECT_UNAUTHORIZED=0 used by the Node smoke). Not an SDK concern.
21
+ import warnings as _w # noqa: E402
22
+ import httpx as _httpx # noqa: E402
23
+ _w.filterwarnings("ignore")
24
+ m._http = _httpx.Client(base_url="https://api.melaya.org", timeout=30.0,
25
+ headers={"Authorization": f"Bearer {KEY}"}, verify=False)
26
+ import ssl as _ssl # noqa: E402
27
+ _NOVERIFY = _ssl.create_default_context()
28
+ _NOVERIFY.check_hostname = False
29
+ _NOVERIFY.verify_mode = _ssl.CERT_NONE
30
+ SPOT = dict(exchange="binance", symbol="BTC/USDT", market="spot")
31
+ PERP = dict(exchange="binanceusdm", symbol="BTC/USDT:USDT")
32
+ R = []
33
+
34
+
35
+ def rec(cat, name, st, d=""):
36
+ R.append((cat, name, st, str(d)[:80]))
37
+
38
+
39
+ def chk(cat, name, fn, validate=None, retry=False):
40
+ for i in range(2 if retry else 1):
41
+ try:
42
+ r = fn()
43
+ if validate is None or validate(r):
44
+ rec(cat, name, "PASS", _short(r)); return r
45
+ if i == (1 if retry else 0):
46
+ rec(cat, name, "FAIL", "invalid shape: " + _short(r)); return r
47
+ except MelayaError as e:
48
+ if i == (1 if retry else 0):
49
+ rec(cat, name, "FAIL", f"{e.status or ''} {e.code or ''} {str(e)[:70]}"); return None
50
+ except Exception as e: # noqa: BLE001
51
+ if i == (1 if retry else 0):
52
+ rec(cat, name, "FAIL", str(e)[:80]); return None
53
+ time.sleep(1.6)
54
+
55
+
56
+ def _short(r):
57
+ import json
58
+ try:
59
+ return json.dumps(r)[:80]
60
+ except Exception: # noqa: BLE001
61
+ return str(r)[:80]
62
+
63
+
64
+ def is_list(n=0):
65
+ return lambda r: isinstance(r, list) and len(r) >= n
66
+
67
+
68
+ def is_obj(r):
69
+ return isinstance(r, dict) and len(r) > 0
70
+
71
+
72
+ # ════ MARKET (22) ════
73
+ chk("market", "list_exchanges", lambda: m.market.list_exchanges(), is_list(60))
74
+ chk("market", "ticker", lambda: m.market.ticker(**SPOT), lambda r: is_obj(r) and (r.get("last") is not None or r.get("bid") is not None), retry=True)
75
+ chk("market", "orderbook", lambda: m.market.orderbook(**SPOT, limit=5), lambda r: r.get("bids"), retry=True)
76
+ chk("market", "ohlcv", lambda: m.market.ohlcv(**SPOT, timeframe="1h", limit=10), is_list(1), retry=True)
77
+ chk("market", "trades", lambda: m.market.trades(**SPOT), is_list(1), retry=True)
78
+ chk("market", "markets", lambda: m.market.markets(exchange="binance"), is_list(1))
79
+ chk("market", "currencies", lambda: m.market.currencies(exchange="kraken"), is_list(1), retry=True)
80
+ chk("market", "status", lambda: m.market.status(exchange="binance"), is_obj)
81
+ chk("market", "time", lambda: m.market.time(exchange="binance"), lambda r: r is not None)
82
+ chk("market", "tickers", lambda: m.market.tickers(exchange="binance", symbols=["BTC/USDT", "ETH/USDT"]), is_obj, retry=True)
83
+ chk("market", "funding_rates", lambda: m.market.funding_rates(exchange="binanceusdm", symbols=[PERP["symbol"]]), is_obj, retry=True)
84
+ chk("market", "funding_rate_history", lambda: m.market.funding_rate_history(exchange="binanceusdm", symbol=PERP["symbol"], hours=24), is_list(1), retry=True)
85
+ chk("market", "open_interest", lambda: m.market.open_interest(exchange="binanceusdm", symbols=[PERP["symbol"]]), is_obj, retry=True)
86
+ chk("market", "open_interest_history", lambda: m.market.open_interest_history(exchange="binanceusdm", symbol=PERP["symbol"], hours=24), is_list(1), retry=True)
87
+ chk("market", "instruments", lambda: m.market.instruments(exchange="binanceusdm"), is_obj)
88
+ chk("market", "liquidation_events", lambda: m.market.liquidation_events(exchange="binanceusdm", limit=10), lambda r: isinstance(r, list))
89
+ chk("market", "ohlcv_multi", lambda: m.market.ohlcv_multi(exchange="binance", symbols=["BTC/USDT", "ETH/USDT"], timeframe="1h", limit=5, market="spot"), is_obj, retry=True)
90
+ chk("market", "market_constraints", lambda: m.market.market_constraints(exchange="binanceusdm", symbol=PERP["symbol"]), lambda r: r is not None)
91
+ chk("market", "funding_rate_history_multi", lambda: m.market.funding_rate_history_multi(exchanges=["binanceusdm", "bybitlinear"], symbol=PERP["symbol"], hours=24), is_obj, retry=True)
92
+ chk("market", "open_interest_history_multi", lambda: m.market.open_interest_history_multi(exchanges=["binanceusdm", "bybitlinear"], symbol=PERP["symbol"], hours=24), is_obj, retry=True)
93
+ chk("market", "prediction_markets", lambda: m.market.prediction_markets(venue="polymarket"), is_list(1), retry=True)
94
+ chk("market", "catalog_counts", lambda: m.market.catalog_counts(), lambda r: r.get("tools", 0) > 0)
95
+
96
+ # ════ ACCOUNT (3) ════
97
+ chk("account", "keys", lambda: m.account.keys(), lambda r: isinstance(r, list))
98
+ chk("account", "usage", lambda: m.account.usage(), lambda r: r.get("tier") is not None)
99
+ chk("account", "api_key_status", lambda: m.account.api_key_status(), is_obj)
100
+
101
+ # ════ STRATEGIES — reads on an existing one; lifecycle on a fresh custom paper one ════
102
+ lst = chk("strategies", "list", lambda: m.strategies.list(), is_list(1))
103
+ read_sid = lst[0]["strategyId"] if lst else None
104
+ if read_sid:
105
+ chk("strategies", "get", lambda: m.strategies.get(read_sid), lambda r: r.get("strategyId") == read_sid)
106
+ chk("strategies", "status", lambda: m.strategies.status(read_sid), is_obj)
107
+ chk("strategies", "executions", lambda: m.strategies.executions(read_sid), lambda r: isinstance(r, list))
108
+ chk("strategies", "trades", lambda: m.strategies.trades(read_sid), lambda r: isinstance(r, list))
109
+ chk("strategies", "performance", lambda: m.strategies.performance(read_sid), lambda r: isinstance(r, list))
110
+ chk("strategies", "logs", lambda: m.strategies.logs(read_sid), lambda r: isinstance(r, list))
111
+ chk("strategies", "ai_opt_status", lambda: m.strategies.ai_opt_status(read_sid), is_obj)
112
+ chk("strategies", "ai_opt_runs", lambda: m.strategies.ai_opt_runs(read_sid), lambda r: r is not None)
113
+
114
+ RHAI = 'fn evaluate() {\n let qty = param("qty");\n if qty == () { qty = 0.001; }\n emit_long(qty);\n}'
115
+ created = chk("strategies", "create(custom,paper)", lambda: m.strategies.create(
116
+ name="SDK full-smoke py (custom)", strategy_type="custom",
117
+ exchange="binanceusdm", symbol="BTC/USDT:USDT", market="FUTURES", dry_run=True,
118
+ params={"language": "rhai", "definition": RHAI, "qty": 0.001},
119
+ ), lambda r: r.get("ok") and r.get("strategyId"))
120
+ paper_sid = created.get("strategyId") if created else None
121
+
122
+ if paper_sid:
123
+ chk("strategies", "pause", lambda: m.strategies.pause(paper_sid), lambda r: r.get("ok"))
124
+ chk("strategies", "resume", lambda: m.strategies.resume(paper_sid), lambda r: r.get("ok"))
125
+ chk("strategies", "update_params", lambda: m.strategies.update_params(paper_sid, {"qty": 0.002}), lambda r: r.get("ok"))
126
+ chk("strategies", "ai_opt_stop", lambda: m.strategies.ai_opt_stop(paper_sid), lambda r: r.get("ok"))
127
+
128
+ # ════ SIM (7) ════
129
+ chk("sim", "balance", lambda: m.sim.balance(strategy_id=paper_sid), lambda r: r.get("total") is not None)
130
+ chk("sim", "positions", lambda: m.sim.positions(strategy_id=paper_sid), lambda r: isinstance(r, list))
131
+ chk("sim", "list_accounts", lambda: m.sim.list_accounts(), lambda r: isinstance(r, list))
132
+ chk("sim", "my_trades", lambda: m.sim.my_trades(strategy_id=paper_sid), lambda r: isinstance(r, list))
133
+ px = 60000
134
+ try:
135
+ t = m.market.ticker(**PERP); px = float(t.get("last") or t.get("bid") or 60000)
136
+ except Exception: # noqa: BLE001
137
+ pass
138
+ ordr = chk("sim", "create_order(limit,resting)", lambda: m.sim.create_order(
139
+ strategy_id=paper_sid, exchange="binanceusdm", symbol="BTC/USDT:USDT",
140
+ side="buy", type="limit", price=round(px * 0.5), amount=0.001, market="FUTURES",
141
+ ), lambda r: r.get("order_id"))
142
+ chk("sim", "open_orders", lambda: m.sim.open_orders(strategy_id=paper_sid), lambda r: isinstance(r, list))
143
+ if ordr and ordr.get("order_id"):
144
+ oid = ordr["order_id"]
145
+ chk("sim", "cancel_order", lambda: m.sim.cancel_order(strategy_id=paper_sid, order_id=oid, symbol="BTC/USDT:USDT", exchange="binanceusdm"), is_obj)
146
+ else:
147
+ rec("sim", "cancel_order", "SKIP", "no resting order id")
148
+ else:
149
+ for n in ["pause", "resume", "update_params", "ai_opt_stop"]:
150
+ rec("strategies", n, "SKIP", "create failed")
151
+ for n in ["balance", "positions", "list_accounts", "my_trades", "create_order(limit,resting)", "open_orders", "cancel_order"]:
152
+ rec("sim", n, "SKIP", "no paper sid")
153
+
154
+ rec("strategies", "ai_opt_start", "WIRED", "not invoked (billed optimization)")
155
+ rec("strategies", "ai_opt_approve", "WIRED", "not invoked (applies optimizer output)")
156
+
157
+ # ════ BACKTEST (custom strategy, end-to-end; deleteAll skipped) ════
158
+ now = int(time.time() * 1000)
159
+ bt = chk("backtest", "start(custom)", lambda: m.backtest.start({
160
+ "strategyType": "custom", "exchange": "binance", "symbol": "BTC/USDT", "timeframe": "1h",
161
+ "since_ms": now - 60 * 86400000, "until_ms": now, "initial_equity": 10000,
162
+ "language": "rhai", "definition": RHAI, "custom_code": RHAI, "params": {"qty": 0.001},
163
+ }), lambda r: r.get("job_id"))
164
+ job_id = bt.get("job_id") if bt else None
165
+ if job_id:
166
+ status = "pending"
167
+ for _ in range(20):
168
+ if status in ("done", "error", "halted", "cancelled"):
169
+ break
170
+ time.sleep(2)
171
+ try:
172
+ status = str(m.backtest.job(job_id).get("status", "")).lower()
173
+ except Exception: # noqa: BLE001
174
+ pass
175
+ chk("backtest", "job(poll)", lambda: m.backtest.job(job_id), lambda r: r.get("job_id") == job_id)
176
+ if status == "done":
177
+ chk("backtest", "results", lambda: m.backtest.results(job_id), is_obj)
178
+ chk("backtest", "trades", lambda: m.backtest.trades(job_id, limit=10), lambda r: isinstance(r, list))
179
+ else:
180
+ rec("backtest", "results", "SKIP", f"job {status}")
181
+ rec("backtest", "trades", "SKIP", f"job {status}")
182
+ chk("backtest", "list", lambda: m.backtest.list(limit=5), lambda r: isinstance(r, list))
183
+ chk("backtest", "favorites", lambda: m.backtest.favorites(limit=5), lambda r: isinstance(r, list))
184
+ chk("backtest", "funding_range", lambda: m.backtest.funding_range(exchange="binanceusdm", symbol=PERP["symbol"]), lambda r: r is None or isinstance(r, (int, float)))
185
+ sweep = chk("backtest", "start(grid_sweep)", lambda: m.backtest.start({
186
+ "mode": "grid_sweep", "strategyType": "custom", "exchange": "binance", "symbol": "BTC/USDT", "timeframe": "1h",
187
+ "since_ms": now - 30 * 86400000, "until_ms": now, "language": "rhai", "definition": RHAI, "custom_code": RHAI,
188
+ "paramRanges": {"qty": [0.001, 0.002]},
189
+ }), lambda r: r.get("job_id"))
190
+ if sweep and sweep.get("job_id"):
191
+ chk("backtest", "sweep", lambda: m.backtest.sweep(sweep["job_id"], limit=10), is_obj)
192
+ else:
193
+ rec("backtest", "sweep", "SKIP", "no sweep parent")
194
+ cj = chk("backtest", "start(for-cancel)", lambda: m.backtest.start({
195
+ "strategyType": "custom", "exchange": "binance", "symbol": "ETH/USDT", "timeframe": "1h",
196
+ "since_ms": now - 365 * 86400000, "until_ms": now, "language": "rhai", "definition": RHAI, "custom_code": RHAI, "params": {"qty": 0.001},
197
+ }), lambda r: r.get("job_id"))
198
+ if cj and cj.get("job_id"):
199
+ chk("backtest", "cancel", lambda: m.backtest.cancel(cj["job_id"]), is_obj)
200
+ chk("backtest", "delete", lambda: m.backtest.delete(cj["job_id"]), lambda r: r.get("ok"))
201
+ else:
202
+ rec("backtest", "cancel", "SKIP", "no job"); rec("backtest", "delete", "SKIP", "no job")
203
+ rec("backtest", "delete_all", "WIRED", "not invoked (soft-deletes ALL non-favorited jobs)")
204
+
205
+
206
+ # ════ STREAMS — public (5) + private (2) ════
207
+ async def stream_chk(cat, name, factory):
208
+ opened = False
209
+ try:
210
+ agen = factory().__aiter__()
211
+ frame = await asyncio.wait_for(agen.__anext__(), timeout=10)
212
+ rec(cat, name, "PASS", "frame " + _short(frame)[:45])
213
+ except asyncio.TimeoutError:
214
+ rec(cat, name, "PASS" if opened else "FAIL", "no frame in 10s")
215
+ except Exception as e: # noqa: BLE001
216
+ rec(cat, name, "FAIL", str(e)[:60])
217
+
218
+
219
+ async def run_streams():
220
+ await stream_chk("stream", "ticker", lambda: m.stream.ticker(**SPOT))
221
+ await stream_chk("stream", "orderbook", lambda: m.stream.orderbook(**SPOT, limit=10))
222
+ await stream_chk("stream", "ohlcv", lambda: m.stream.ohlcv(**SPOT, timeframe="1m"))
223
+ await stream_chk("stream", "trades", lambda: m.stream.trades(**SPOT))
224
+ await stream_chk("stream", "liquidations", lambda: m.stream.liquidations(exchange="binanceusdm"))
225
+ await stream_chk("stream", "strategies(private)", lambda: m.stream.strategies())
226
+ try:
227
+ keys = m.account.keys()
228
+ except Exception: # noqa: BLE001
229
+ keys = []
230
+ if keys:
231
+ k = keys[0]
232
+ await stream_chk("stream", "private(account)", lambda: m.stream.private(exchange=k.get("exchange"), market=k.get("market"), api_key_id=k.get("apiKeyId")))
233
+ else:
234
+ rec("stream", "private(account)", "SKIP", "no connected key")
235
+
236
+ asyncio.run(run_streams())
237
+
238
+ # ════ TRADE — live credentialed reads (5); write ops WIRED — real funds ════
239
+ _VEN = dict(exchange="bitgetfutures", api_key_id="BITGETFUTURES_0", market_type="futures")
240
+ chk("trade", "balance", lambda: m.trade.balance(**_VEN), lambda r: r.get("ok") is True)
241
+ chk("trade", "positions", lambda: m.trade.positions(**_VEN), lambda r: r.get("ok") is True)
242
+ chk("trade", "open_orders", lambda: m.trade.open_orders(**_VEN), lambda r: r.get("ok") is True)
243
+ chk("trade", "orders", lambda: m.trade.orders(**_VEN), lambda r: r.get("ok") is True)
244
+ chk("trade", "my_trades", lambda: m.trade.my_trades(**_VEN, symbol="BTC/USDT:USDT"), lambda r: r.get("ok") is True)
245
+ for _w in ["create_order","cancel_order","amend_order","cancel_all_orders","cancel_plan_orders","close_position","set_leverage","set_margin_mode","set_position_mode"]:
246
+ rec("trade", _w, "WIRED", "not invoked — LIVE write, real funds")
247
+
248
+ # ════ TEARDOWN ════
249
+ if paper_sid:
250
+ chk("teardown", "strategies.stop", lambda: m.strategies.stop(paper_sid), lambda r: r.get("ok"))
251
+ chk("teardown", "strategies.delete", lambda: m.strategies.delete(paper_sid), lambda r: r.get("ok"))
252
+
253
+ m.close()
254
+
255
+ # ════ REPORT ════
256
+ print("\n============== MELAYA SDK — FULL ENDPOINT VALIDATION (Python) ==============")
257
+ cats = []
258
+ for c, *_ in R:
259
+ if c not in cats:
260
+ cats.append(c)
261
+ nP = nF = nW = nS = 0
262
+ for cat in cats:
263
+ print(f"\n-- {cat} --")
264
+ for c, name, st, d in [x for x in R if x[0] == cat]:
265
+ print(f" {st:<5} {name:<28} {d}")
266
+ nP += st == "PASS"; nF += st == "FAIL"; nW += st == "WIRED"; nS += st == "SKIP"
267
+ print("\n===========================================================================")
268
+ print(f"PASS {nP} FAIL {nF} WIRED(not-invoked) {nW} SKIP {nS} | total {nP + nF + nW + nS}")
269
+ print("RESULT: GO — every invoked endpoint validated." if nF == 0 else f"RESULT: NO-GO — {nF} failing.")
270
+ sys.exit(0 if nF == 0 else 1)
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "melaya"
7
+ version = "0.1.0"
8
+ description = "Official Python SDK for the Melaya unified market-data & streaming API across 70+ venues."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "Apache-2.0"
12
+ authors = [{ name = "Melaya" }]
13
+ keywords = [
14
+ "melaya",
15
+ "trading",
16
+ "crypto",
17
+ "market-data",
18
+ "exchange",
19
+ "websocket",
20
+ "orderbook",
21
+ "ohlcv",
22
+ "ticker",
23
+ "agentic-ai",
24
+ "sdk",
25
+ ]
26
+ classifiers = [
27
+ "Development Status :: 4 - Beta",
28
+ "Intended Audience :: Developers",
29
+ "Intended Audience :: Financial and Insurance Industry",
30
+ "License :: OSI Approved :: Apache Software License",
31
+ "Programming Language :: Python :: 3",
32
+ "Topic :: Office/Business :: Financial :: Investment",
33
+ ]
34
+ dependencies = ["httpx>=0.27"]
35
+
36
+ [project.optional-dependencies]
37
+ stream = ["websockets>=12"]
38
+
39
+ [project.urls]
40
+ Homepage = "https://melaya.org"
41
+ Documentation = "https://melaya.org/docs"
42
+ Repository = "https://github.com/melaya-labs/melaya"
43
+ Issues = "https://github.com/melaya-labs/melaya/issues"
44
+
45
+ [tool.hatch.build.targets.wheel]
46
+ packages = ["src/melaya"]
@@ -0,0 +1,13 @@
1
+ """Official Python SDK for the Melaya unified market-data & streaming API.
2
+
3
+ >>> from melaya import Melaya
4
+ >>> m = Melaya(api_key="mk_...")
5
+ >>> m.market.ticker(exchange="binance", symbol="BTC/USDT", market="spot")
6
+
7
+ See https://melaya.org/docs
8
+ """
9
+ from .client import Melaya, DEFAULT_BASE_URL, DEFAULT_WS_URL
10
+ from .errors import MelayaError
11
+
12
+ __all__ = ["Melaya", "MelayaError", "DEFAULT_BASE_URL", "DEFAULT_WS_URL"]
13
+ __version__ = "0.1.0"
@@ -0,0 +1,27 @@
1
+ """Account API — authenticated reads about your Melaya account.
2
+
3
+ Connected-exchange key references (masked), tier limits, and live usage
4
+ counters. Requires an ``mk_`` key on the private plane.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, Callable, Dict, List
9
+
10
+ _Request = Callable[..., Any]
11
+
12
+
13
+ class AccountAPI:
14
+ def __init__(self, request: _Request) -> None:
15
+ self._request = request
16
+
17
+ def keys(self) -> List[Dict[str, Any]]:
18
+ """Connected exchange keys. ``apiKey`` is masked; use ``apiKeyId`` as the reference."""
19
+ return self._request("GET", "/api/v1/private/keys")["keys"]
20
+
21
+ def usage(self) -> Dict[str, Any]:
22
+ """Tier, plan limits, and live usage counters."""
23
+ return self._request("GET", "/api/v1/private/usage")
24
+
25
+ def api_key_status(self) -> Dict[str, Any]:
26
+ """Status of your platform API key (tier, max concurrent connections)."""
27
+ return self._request("GET", "/api/v1/private/api-key")