implement_okx_readonly_order_support_plan.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import json
  2. from pathlib import Path
  3. REPORT_DIR = Path("reports/eth-exploration")
  4. JSON_PATH = REPORT_DIR / "okx-order-support-plan.json"
  5. MD_PATH = REPORT_DIR / "okx-order-support-plan.md"
  6. PLAN = {
  7. "report": "okx-order-support-plan",
  8. "scope": "static implementation plan only; no OKX request, no order, no cancel",
  9. "readiness_gap": "ETH-focused quasi-live portfolio needs a full order lifecycle before any submit-capable runner is valid.",
  10. "source_files_checked": [
  11. {
  12. "path": "okx_codex_trader/okx_client.py",
  13. "facts": [
  14. "Signed _request already supports GET/POST with params/json_body.",
  15. "place_order is a single-order submitter.",
  16. "place_order emits ordType=market or ordType=limit only.",
  17. "No batch-orders, orders-pending, cancel-order, order status, or fills method exists.",
  18. "get_positions reads exchange positions but is not tied to portfolio-owned state.",
  19. ],
  20. },
  21. {
  22. "path": "tests/test_okx_client.py",
  23. "facts": [
  24. "DummySession records request path/body and can support endpoint-level client tests.",
  25. "Existing tests cover request signing, live/demo header, contract sizing, leverage validation, single order payload, and position parsing.",
  26. "No tests cover post_only payloads, batch payloads, order list/cancel/status/fills, or state persistence.",
  27. ],
  28. },
  29. {
  30. "path": "okx_codex_trader/cli.py",
  31. "facts": [
  32. "okx-account is read-only and returns balance plus positions.",
  33. "okx-order is submit-capable and calls place_order.",
  34. "No read-only ETH order-intent/state command exists.",
  35. "No quasi-live state path or append-only lifecycle log is wired.",
  36. ],
  37. },
  38. ],
  39. "official_okx_api_surface": [
  40. {
  41. "capability": "single post-only limit order payload",
  42. "endpoint": "POST /api/v5/trade/order",
  43. "boundary": "submit-capable only after read-only payload rendering is tested",
  44. "required_fields": ["instId", "tdMode", "side", "posSide", "ordType", "px", "sz", "clOrdId"],
  45. "fixed_eth_values": {
  46. "instId": "ETH-USDT-SWAP",
  47. "tdMode": "isolated",
  48. "side": "buy",
  49. "posSide": "long",
  50. "ordType": "post_only",
  51. },
  52. },
  53. {
  54. "capability": "batch entry orders",
  55. "endpoint": "POST /api/v5/trade/batch-orders",
  56. "boundary": "submit-capable; read-only stage renders the exact array only",
  57. "required_shape": "list of three post_only entry payloads with deterministic clOrdId values",
  58. },
  59. {
  60. "capability": "list open orders",
  61. "endpoint": "GET /api/v5/trade/orders-pending",
  62. "boundary": "read-only",
  63. "minimum_params": {"instType": "SWAP", "instId": "ETH-USDT-SWAP"},
  64. },
  65. {
  66. "capability": "cancel open order",
  67. "endpoint": "POST /api/v5/trade/cancel-order",
  68. "boundary": "submit-capable; read-only stage renders cancel intents only",
  69. "required_fields": ["instId", "ordId or clOrdId"],
  70. },
  71. {
  72. "capability": "cancel open orders in batch",
  73. "endpoint": "POST /api/v5/trade/cancel-batch-orders",
  74. "boundary": "submit-capable; read-only stage renders cancel intent array only",
  75. "required_shape": "list of tracked open order ids for ETH strategy-owned orders",
  76. },
  77. {
  78. "capability": "order status",
  79. "endpoint": "GET /api/v5/trade/order",
  80. "boundary": "read-only",
  81. "minimum_params": {"instId": "ETH-USDT-SWAP", "ordId_or_clOrdId": "tracked order identity"},
  82. },
  83. {
  84. "capability": "recent fills",
  85. "endpoint": "GET /api/v5/trade/fills",
  86. "boundary": "read-only",
  87. "minimum_params": {"instType": "SWAP", "instId": "ETH-USDT-SWAP"},
  88. },
  89. ],
  90. "state_persistence": {
  91. "boundary": "local read/write only; not an OKX API call",
  92. "minimum_file": "state/eth_robust_twap_15m_live.json",
  93. "fields": [
  94. "strategy_id",
  95. "symbol",
  96. "bar",
  97. "td_mode",
  98. "pos_side",
  99. "leverage",
  100. "last_confirmed_candle_ts",
  101. "last_signal_candle_ts",
  102. "orders",
  103. "fills",
  104. "position",
  105. "planned_margin_usdt",
  106. "max_margin_usdt",
  107. "updated_at",
  108. ],
  109. "event_log": "append-only JSONL for signal, order-intent, cancel-intent, order-status, fills, and state-transition events",
  110. },
  111. "minimum_patch_plan_by_file": [
  112. {
  113. "file": "okx_codex_trader/okx_client.py",
  114. "patch_plan": [
  115. "Add small dataclasses or typed dicts only if needed by return values: OkxOrder, OkxFill, OkxOrderAck.",
  116. "Add render_post_only_limit_order_payload(...) as a non-submitting function using existing _format_number and build_contract_size.",
  117. "Add render_eth_entry_batch_payloads(...) that returns exactly three post_only payloads for offsets 0.003, 0.006, 0.009.",
  118. "Add list_open_orders(symbol) calling GET /api/v5/trade/orders-pending with instType=SWAP and instId=symbol.",
  119. "Add get_order(symbol, ord_id=None, client_order_id=None) calling GET /api/v5/trade/order.",
  120. "Add list_fills(symbol) calling GET /api/v5/trade/fills with instType=SWAP and instId=symbol.",
  121. "Add cancel_order(symbol, ord_id=None, client_order_id=None) and cancel_batch_orders(symbol, order_ids) only when moving past read-only intent.",
  122. "Do not change existing place_order semantics for the first patch; it is not suitable for the ETH portfolio lifecycle.",
  123. ],
  124. },
  125. {
  126. "file": "okx_codex_trader/cli.py",
  127. "patch_plan": [
  128. "Add read-only eth-robust-twap-order-intent command that prints signal, three entry payloads, expiry, and state transition preview.",
  129. "Add read-only eth-robust-twap-open-orders command that lists current OKX pending orders for ETH-USDT-SWAP.",
  130. "Add read-only eth-robust-twap-reconcile command that reads state, queries open orders/status/fills, and writes a new local state snapshot plus JSONL audit event.",
  131. "Keep okx-order unchanged and do not route portfolio intents through it.",
  132. "Only after read-only commands pass tests, add explicit submit commands for batch entry and cancellation.",
  133. ],
  134. },
  135. {
  136. "file": "tests/test_okx_client.py",
  137. "patch_plan": [
  138. "Add tests for post_only payload shape: ETH-USDT-SWAP, isolated, buy, long, post_only, px, sz, deterministic clOrdId.",
  139. "Add tests for batch payload shape: exactly three orders, offsets 0.003/0.006/0.009, each level sized independently and rounded to lotSz.",
  140. "Add tests for GET /api/v5/trade/orders-pending params and normalized open-order state values.",
  141. "Add tests for POST /api/v5/trade/cancel-order and /cancel-batch-orders request bodies before enabling submit commands.",
  142. "Add tests for GET /api/v5/trade/order by ordId and by clOrdId.",
  143. "Add tests for GET /api/v5/trade/fills parsing fillSz, fillPx, tradeId, ordId, clOrdId, fee, and fillTime.",
  144. "Add tests that malformed OKX lifecycle payloads raise the existing stable invalid-payload error.",
  145. ],
  146. },
  147. {
  148. "file": "new okx_codex_trader/eth_robust_twap_state.py",
  149. "patch_plan": [
  150. "Add only the dedicated state schema and load/save functions needed by the ETH quasi-live lifecycle.",
  151. "Persist exchange order ids, client order ids, fills, position, last confirmed candle, active state, and audit cursor.",
  152. "Reject state whose symbol/bar/strategy_id does not match the command arguments.",
  153. ],
  154. },
  155. {
  156. "file": "new tests/test_eth_robust_twap_state.py",
  157. "patch_plan": [
  158. "Add load empty state test.",
  159. "Add save/read roundtrip for one signal with three open orders and one partial fill.",
  160. "Add state mismatch rejection tests for symbol, bar, and strategy_id.",
  161. ],
  162. },
  163. ],
  164. "readonly_boundary": [
  165. "Render post_only single and batch order payloads without calling POST /api/v5/trade/order or /batch-orders.",
  166. "Read account balance, positions, pending orders, order details, and fills.",
  167. "Write local state snapshots and append-only JSONL audit events.",
  168. "Render cancel intents without calling cancel endpoints.",
  169. ],
  170. "submit_capable_boundary": [
  171. "POST /api/v5/trade/order with ordType=post_only.",
  172. "POST /api/v5/trade/batch-orders.",
  173. "POST /api/v5/trade/cancel-order.",
  174. "POST /api/v5/trade/cancel-batch-orders.",
  175. "Any reduce-only market close path.",
  176. ],
  177. "test_checklist": [
  178. "Existing tests: rtk .venv/bin/pytest tests/test_okx_client.py",
  179. "New client tests for render-only post_only payloads make no DummySession requests.",
  180. "New client tests for list_open_orders/get_order/list_fills assert method/path/params and parsed output.",
  181. "New cancel tests assert request body shape but stay behind commands that are not wired in the read-only phase.",
  182. "New CLI tests assert read-only order-intent/reconcile commands do not call submit/cancel endpoints.",
  183. "New state tests assert deterministic state roundtrip and event append order.",
  184. ],
  185. "sources": [
  186. "https://www.okx.com/docs-v5/en/",
  187. "reports/eth-exploration/eth-focused-portfolio-live-readiness.md",
  188. "reports/eth-exploration/eth-robust-twap-live-plan.md",
  189. ],
  190. }
  191. def _md_table(rows: list[list[str]]) -> str:
  192. header = rows[0]
  193. divider = ["---"] * len(header)
  194. lines = ["| " + " | ".join(header) + " |", "| " + " | ".join(divider) + " |"]
  195. lines.extend("| " + " | ".join(row) + " |" for row in rows[1:])
  196. return "\n".join(lines)
  197. def render_markdown() -> str:
  198. lines = [
  199. "# OKX order support minimum implementation plan",
  200. "",
  201. "Static implementation plan only. No OKX request, order, or cancel was made.",
  202. "",
  203. "## Goal",
  204. "",
  205. PLAN["readiness_gap"],
  206. "",
  207. "Minimum direct path: first add read-only payload/state/reconciliation support, then add submit-capable order and cancel calls behind explicit commands.",
  208. "",
  209. "## Current code facts",
  210. "",
  211. ]
  212. for item in PLAN["source_files_checked"]:
  213. lines.append(f"### `{item['path']}`")
  214. lines.extend(f"- {fact}" for fact in item["facts"])
  215. lines.append("")
  216. lines.extend(
  217. [
  218. "## Required OKX API surface",
  219. "",
  220. _md_table(
  221. [["Capability", "Endpoint", "Boundary"]]
  222. + [[item["capability"], f"`{item['endpoint']}`", item["boundary"]] for item in PLAN["official_okx_api_surface"]]
  223. ),
  224. "",
  225. "State persistence is local state, not an OKX API call. Minimum file: "
  226. f"`{PLAN['state_persistence']['minimum_file']}`.",
  227. "",
  228. "## Read-only boundary",
  229. "",
  230. ]
  231. )
  232. lines.extend(f"- {item}" for item in PLAN["readonly_boundary"])
  233. lines.extend(["", "## Submit-capable boundary", ""])
  234. lines.extend(f"- {item}" for item in PLAN["submit_capable_boundary"])
  235. lines.extend(["", "## Minimum patch plan by file", ""])
  236. for item in PLAN["minimum_patch_plan_by_file"]:
  237. lines.append(f"### `{item['file']}`")
  238. lines.extend(f"- {step}" for step in item["patch_plan"])
  239. lines.append("")
  240. lines.extend(["## Test checklist", ""])
  241. lines.extend(f"- {item}" for item in PLAN["test_checklist"])
  242. lines.extend(
  243. [
  244. "",
  245. "## Sources",
  246. "",
  247. "- OKX API v5 docs: https://www.okx.com/docs-v5/en/",
  248. "- Live readiness report: `reports/eth-exploration/eth-focused-portfolio-live-readiness.md`",
  249. "- ETH live execution plan: `reports/eth-exploration/eth-robust-twap-live-plan.md`",
  250. "",
  251. ]
  252. )
  253. return "\n".join(lines)
  254. def main() -> int:
  255. REPORT_DIR.mkdir(parents=True, exist_ok=True)
  256. JSON_PATH.write_text(json.dumps(PLAN, indent=2) + "\n")
  257. MD_PATH.write_text(render_markdown())
  258. print(json.dumps({"json": str(JSON_PATH), "markdown": str(MD_PATH)}, indent=2))
  259. return 0
  260. if __name__ == "__main__":
  261. raise SystemExit(main())