desic-okx-agent 0.2.1 → 0.3.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.
Files changed (236) hide show
  1. package/README.en.md +90 -10
  2. package/README.md +76 -10
  3. package/dist/account/private-websocket.js +4 -4
  4. package/dist/account/private-websocket.js.map +1 -1
  5. package/dist/account/service.d.ts +12 -1
  6. package/dist/account/service.js +18 -0
  7. package/dist/account/service.js.map +1 -1
  8. package/dist/bars/rate-limiter.d.ts +18 -0
  9. package/dist/bars/rate-limiter.js +84 -0
  10. package/dist/bars/rate-limiter.js.map +1 -0
  11. package/dist/bars/schema.d.ts +36 -0
  12. package/dist/bars/schema.js +134 -0
  13. package/dist/bars/schema.js.map +1 -0
  14. package/dist/bars/service.d.ts +60 -0
  15. package/dist/bars/service.js +120 -0
  16. package/dist/bars/service.js.map +1 -0
  17. package/dist/bars/store.d.ts +105 -0
  18. package/dist/bars/store.js +415 -0
  19. package/dist/bars/store.js.map +1 -0
  20. package/dist/bars/timeframe.d.ts +40 -0
  21. package/dist/bars/timeframe.js +146 -0
  22. package/dist/bars/timeframe.js.map +1 -0
  23. package/dist/bars/types.d.ts +68 -0
  24. package/dist/bars/types.js +13 -0
  25. package/dist/bars/types.js.map +1 -0
  26. package/dist/cli/data-render.d.ts +37 -0
  27. package/dist/cli/data-render.js +143 -0
  28. package/dist/cli/data-render.js.map +1 -0
  29. package/dist/cli/doctor.js +7 -3
  30. package/dist/cli/doctor.js.map +1 -1
  31. package/dist/cli/index.js +757 -26
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/live-render.d.ts +24 -0
  34. package/dist/cli/live-render.js +85 -0
  35. package/dist/cli/live-render.js.map +1 -0
  36. package/dist/cli/range.d.ts +28 -0
  37. package/dist/cli/range.js +63 -0
  38. package/dist/cli/range.js.map +1 -0
  39. package/dist/cli/render.js +3 -0
  40. package/dist/cli/render.js.map +1 -1
  41. package/dist/cli/strategy-render.d.ts +36 -0
  42. package/dist/cli/strategy-render.js +391 -0
  43. package/dist/cli/strategy-render.js.map +1 -0
  44. package/dist/cli/width.d.ts +18 -0
  45. package/dist/cli/width.js +71 -0
  46. package/dist/cli/width.js.map +1 -0
  47. package/dist/config/loader.js +1 -1
  48. package/dist/config/schema.d.ts +7 -0
  49. package/dist/config/schema.js +24 -0
  50. package/dist/config/schema.js.map +1 -1
  51. package/dist/core/okx-client.d.ts +9 -1
  52. package/dist/core/okx-client.js +14 -5
  53. package/dist/core/okx-client.js.map +1 -1
  54. package/dist/i18n/locale.d.ts +24 -0
  55. package/dist/i18n/locale.js +65 -0
  56. package/dist/i18n/locale.js.map +1 -0
  57. package/dist/i18n/messages.d.ts +333 -0
  58. package/dist/i18n/messages.js +660 -0
  59. package/dist/i18n/messages.js.map +1 -0
  60. package/dist/live/account-snapshot.d.ts +30 -0
  61. package/dist/live/account-snapshot.js +130 -0
  62. package/dist/live/account-snapshot.js.map +1 -0
  63. package/dist/live/cutoff-queue.d.ts +42 -0
  64. package/dist/live/cutoff-queue.js +69 -0
  65. package/dist/live/cutoff-queue.js.map +1 -0
  66. package/dist/live/execution-key.d.ts +23 -0
  67. package/dist/live/execution-key.js +31 -0
  68. package/dist/live/execution-key.js.map +1 -0
  69. package/dist/live/failures.d.ts +37 -0
  70. package/dist/live/failures.js +57 -0
  71. package/dist/live/failures.js.map +1 -0
  72. package/dist/live/gates.d.ts +65 -0
  73. package/dist/live/gates.js +136 -0
  74. package/dist/live/gates.js.map +1 -0
  75. package/dist/live/loop.d.ts +56 -0
  76. package/dist/live/loop.js +197 -0
  77. package/dist/live/loop.js.map +1 -0
  78. package/dist/live/preconditions.d.ts +48 -0
  79. package/dist/live/preconditions.js +69 -0
  80. package/dist/live/preconditions.js.map +1 -0
  81. package/dist/live/reconcile.d.ts +46 -0
  82. package/dist/live/reconcile.js +104 -0
  83. package/dist/live/reconcile.js.map +1 -0
  84. package/dist/live/runner.d.ts +57 -0
  85. package/dist/live/runner.js +160 -0
  86. package/dist/live/runner.js.map +1 -0
  87. package/dist/live/schema.d.ts +18 -0
  88. package/dist/live/schema.js +91 -0
  89. package/dist/live/schema.js.map +1 -0
  90. package/dist/live/service.d.ts +144 -0
  91. package/dist/live/service.js +303 -0
  92. package/dist/live/service.js.map +1 -0
  93. package/dist/live/session.d.ts +85 -0
  94. package/dist/live/session.js +234 -0
  95. package/dist/live/session.js.map +1 -0
  96. package/dist/live/sizing.d.ts +62 -0
  97. package/dist/live/sizing.js +79 -0
  98. package/dist/live/sizing.js.map +1 -0
  99. package/dist/live/store.d.ts +123 -0
  100. package/dist/live/store.js +350 -0
  101. package/dist/live/store.js.map +1 -0
  102. package/dist/live/types.d.ts +82 -0
  103. package/dist/live/types.js +2 -0
  104. package/dist/live/types.js.map +1 -0
  105. package/dist/market/websocket.d.ts +16 -1
  106. package/dist/market/websocket.js +60 -5
  107. package/dist/market/websocket.js.map +1 -1
  108. package/dist/mcp/server.d.ts +1 -0
  109. package/dist/mcp/server.js +15 -1
  110. package/dist/mcp/server.js.map +1 -1
  111. package/dist/network/connectivity.d.ts +9 -1
  112. package/dist/network/connectivity.js +28 -1
  113. package/dist/network/connectivity.js.map +1 -1
  114. package/dist/report/chart-script.d.ts +12 -0
  115. package/dist/report/chart-script.js +146 -0
  116. package/dist/report/chart-script.js.map +1 -0
  117. package/dist/report/compare-html.d.ts +8 -0
  118. package/dist/report/compare-html.js +254 -0
  119. package/dist/report/compare-html.js.map +1 -0
  120. package/dist/report/compare-script.d.ts +12 -0
  121. package/dist/report/compare-script.js +109 -0
  122. package/dist/report/compare-script.js.map +1 -0
  123. package/dist/report/compare.d.ts +61 -0
  124. package/dist/report/compare.js +205 -0
  125. package/dist/report/compare.js.map +1 -0
  126. package/dist/report/fetch.d.ts +20 -0
  127. package/dist/report/fetch.js +56 -0
  128. package/dist/report/fetch.js.map +1 -0
  129. package/dist/report/html.d.ts +54 -0
  130. package/dist/report/html.js +641 -0
  131. package/dist/report/html.js.map +1 -0
  132. package/dist/report/open.d.ts +42 -0
  133. package/dist/report/open.js +114 -0
  134. package/dist/report/open.js.map +1 -0
  135. package/dist/runtime/server.d.ts +16 -1
  136. package/dist/runtime/server.js +112 -9
  137. package/dist/runtime/server.js.map +1 -1
  138. package/dist/setup/installer.d.ts +1 -0
  139. package/dist/setup/installer.js +8 -0
  140. package/dist/setup/installer.js.map +1 -1
  141. package/dist/setup/wizard.js +19 -22
  142. package/dist/setup/wizard.js.map +1 -1
  143. package/dist/strategy/constants.d.ts +23 -0
  144. package/dist/strategy/constants.js +24 -0
  145. package/dist/strategy/constants.js.map +1 -0
  146. package/dist/strategy/environment.d.ts +52 -0
  147. package/dist/strategy/environment.js +187 -0
  148. package/dist/strategy/environment.js.map +1 -0
  149. package/dist/strategy/instrument.d.ts +29 -0
  150. package/dist/strategy/instrument.js +39 -0
  151. package/dist/strategy/instrument.js.map +1 -0
  152. package/dist/strategy/optimize.d.ts +73 -0
  153. package/dist/strategy/optimize.js +113 -0
  154. package/dist/strategy/optimize.js.map +1 -0
  155. package/dist/strategy/parameter-space.d.ts +59 -0
  156. package/dist/strategy/parameter-space.js +221 -0
  157. package/dist/strategy/parameter-space.js.map +1 -0
  158. package/dist/strategy/python-bridge.d.ts +24 -0
  159. package/dist/strategy/python-bridge.js +114 -0
  160. package/dist/strategy/python-bridge.js.map +1 -0
  161. package/dist/strategy/schema.d.ts +9 -0
  162. package/dist/strategy/schema.js +91 -0
  163. package/dist/strategy/schema.js.map +1 -0
  164. package/dist/strategy/service.d.ts +138 -0
  165. package/dist/strategy/service.js +745 -0
  166. package/dist/strategy/service.js.map +1 -0
  167. package/dist/strategy/settings.d.ts +162 -0
  168. package/dist/strategy/settings.js +243 -0
  169. package/dist/strategy/settings.js.map +1 -0
  170. package/dist/strategy/store.d.ts +96 -0
  171. package/dist/strategy/store.js +367 -0
  172. package/dist/strategy/store.js.map +1 -0
  173. package/dist/strategy/templates.d.ts +11 -0
  174. package/dist/strategy/templates.js +134 -0
  175. package/dist/strategy/templates.js.map +1 -0
  176. package/dist/strategy/types.d.ts +111 -0
  177. package/dist/strategy/types.js +2 -0
  178. package/dist/strategy/types.js.map +1 -0
  179. package/dist/tools/catalog.d.ts +16 -0
  180. package/dist/tools/catalog.js +118 -17
  181. package/dist/tools/catalog.js.map +1 -1
  182. package/dist/trade/service.d.ts +12 -0
  183. package/dist/trade/service.js +24 -7
  184. package/dist/trade/service.js.map +1 -1
  185. package/dist/tui/app.d.ts +23 -0
  186. package/dist/tui/app.js +322 -0
  187. package/dist/tui/app.js.map +1 -0
  188. package/dist/tui/commands.d.ts +70 -0
  189. package/dist/tui/commands.js +313 -0
  190. package/dist/tui/commands.js.map +1 -0
  191. package/dist/tui/entries.d.ts +17 -0
  192. package/dist/tui/entries.js +24 -0
  193. package/dist/tui/entries.js.map +1 -0
  194. package/dist/tui/execute.d.ts +26 -0
  195. package/dist/tui/execute.js +664 -0
  196. package/dist/tui/execute.js.map +1 -0
  197. package/dist/tui/history.d.ts +17 -0
  198. package/dist/tui/history.js +48 -0
  199. package/dist/tui/history.js.map +1 -0
  200. package/dist/tui/index.d.ts +8 -0
  201. package/dist/tui/index.js +48 -0
  202. package/dist/tui/index.js.map +1 -0
  203. package/dist/tui/line-editor.d.ts +44 -0
  204. package/dist/tui/line-editor.js +98 -0
  205. package/dist/tui/line-editor.js.map +1 -0
  206. package/dist/tui/progress.d.ts +23 -0
  207. package/dist/tui/progress.js +46 -0
  208. package/dist/tui/progress.js.map +1 -0
  209. package/dist/tui/settings-editor.d.ts +18 -0
  210. package/dist/tui/settings-editor.js +115 -0
  211. package/dist/tui/settings-editor.js.map +1 -0
  212. package/docs/live-trading.md +455 -0
  213. package/docs/strategy-research.md +597 -0
  214. package/package.json +10 -1
  215. package/python/desic_strategy/__init__.py +34 -0
  216. package/python/desic_strategy/actions.py +158 -0
  217. package/python/desic_strategy/context.py +164 -0
  218. package/python/desic_strategy/engine.py +614 -0
  219. package/python/desic_strategy/indicators.py +159 -0
  220. package/python/desic_strategy/live.py +253 -0
  221. package/python/desic_strategy/policy.py +193 -0
  222. package/python/desic_strategy/portfolio.py +152 -0
  223. package/python/desic_strategy/report.py +319 -0
  224. package/python/desic_strategy/runner.py +574 -0
  225. package/python/desic_strategy/timeframe.py +150 -0
  226. package/python/main.py +18 -0
  227. package/skills/okx-live-trading/SKILL.md +117 -0
  228. package/skills/okx-live-trading/agents/openai.yaml +9 -0
  229. package/skills/okx-live-trading/references/lifecycle.md +128 -0
  230. package/skills/okx-strategy-research/SKILL.md +113 -0
  231. package/skills/okx-strategy-research/agents/openai.yaml +9 -0
  232. package/skills/okx-strategy-research/references/execution-semantics.md +107 -0
  233. package/skills/okx-strategy-research/references/field-traps.md +142 -0
  234. package/skills/okx-strategy-research/references/python-api.md +121 -0
  235. package/skills/okx-strategy-research/references/tools-and-data.md +192 -0
  236. package/skills/okx-trading/SKILL.md +11 -10
@@ -0,0 +1,597 @@
1
+ # 策略研究使用指南
2
+
3
+ 用 Python 写策略,在本地 OKX 1 分钟历史数据上回测、调参、对比。
4
+
5
+ 三种入口,能力相同:
6
+
7
+ | 入口 | 适合 |
8
+ | --- | --- |
9
+ | 交互界面 `desic-okx` | 人手操作。斜杠命令 + 参数提示,不需要记命令 |
10
+ | CLI `desic-okx strategy ...` | 脚本、CI、批量任务 |
11
+ | MCP 工具 | 交给 AI agent 使用 |
12
+
13
+ 界面语言用 `/lang zh` 或 `desic-okx lang zh` 切换,命令说明和提示都会跟着变。工具名、字段名、错误码保持英文 —— 复制出来的信息仍能对应 API。
14
+
15
+ ---
16
+
17
+ ## 目录
18
+
19
+ - [五分钟跑通第一次回测](#五分钟跑通第一次回测)
20
+ - [准备 Python 环境](#准备-python-环境)
21
+ - [准备数据](#准备数据)
22
+ - [写策略](#写策略)
23
+ - [回测](#回测)
24
+ - [回测参数](#回测参数)
25
+ - [读结果](#读结果)
26
+ - [参数调优](#参数调优)
27
+ - [对比多次回测](#对比多次回测)
28
+ - [下一步:接实盘](#下一步接实盘)
29
+ - [给 AI agent 用](#给-ai-agent-用)
30
+ - [常见问题](#常见问题)
31
+ - [必须知道的限制](#必须知道的限制)
32
+
33
+ ---
34
+
35
+ ## 五分钟跑通第一次回测
36
+
37
+ ```bash
38
+ desic-okx strategy env --setup # 一次性:准备 Python 环境
39
+ desic-okx data download --inst BTC-USDT-SWAP --days 90 # 下载 90 天 1 分钟数据
40
+ desic-okx strategy new --out ema.py --template ema-trend # 生成策略模板
41
+ desic-okx strategy validate --file ema.py # 静态校验
42
+ desic-okx strategy backtest --file ema.py --inst BTC-USDT-SWAP --days 30 --follow
43
+ ```
44
+
45
+ 最后一步会打印运行 ID 和完整指标。想看权益曲线和成交明细:
46
+
47
+ ```bash
48
+ desic-okx strategy report --run bt_xxxx --open
49
+ ```
50
+
51
+ 交互界面里是同一套流程:
52
+
53
+ ```
54
+ $ desic-okx
55
+
56
+ > /env setup
57
+ > /download BTC-USDT-SWAP 90
58
+ > /new ema.py ema-trend
59
+ > /validate ema.py
60
+ > /backtest ema.py BTC-USDT-SWAP 30
61
+ > /report bt_xxxx
62
+ ```
63
+
64
+ ---
65
+
66
+ ## 准备 Python 环境
67
+
68
+ 策略是 Python 代码,需要 Python 3.11 以上,以及 numpy 和 pandas。
69
+
70
+ ```bash
71
+ desic-okx strategy env # 检查
72
+ desic-okx strategy env --setup # 在数据目录下建 venv 并装依赖
73
+ ```
74
+
75
+ `--setup` 的镜像回退链是 **清华 → 阿里 → PyPI**,逐个尝试。单一镜像会因为 403 直接卡死,所以不做单点依赖。
76
+
77
+ `desic-okx doctor` 里也有一节 Python 环境状态。
78
+
79
+ ---
80
+
81
+ ## 准备数据
82
+
83
+ **只存 1 分钟数据。** 3m / 15m / 1H / 1D 等全部由 1 分钟合成,所以只需要下载这一种。TS 侧和 Python 侧各有一份聚合实现,用同一组 fixture 交叉验证,保证回测和图表看到的高周期完全一致。
84
+
85
+ ```bash
86
+ desic-okx data list # 本地全部合约 + 覆盖区间
87
+ desic-okx data coverage --inst BTC-USDT-SWAP # 单个合约的区间与缺口
88
+ desic-okx data download --inst BTC-USDT-SWAP --days 90
89
+ desic-okx data download --inst BTC-USDT-SWAP --from 2026-01-01 --to 2026-06-30
90
+ ```
91
+
92
+ `download` 只请求缺失的分钟,重复执行是安全的。
93
+
94
+ ### 回测前先查缺口
95
+
96
+ ```bash
97
+ desic-okx data coverage --inst BTC-USDT-SWAP
98
+ ```
99
+
100
+ `missingCount` 大于 0 说明区间里有洞。**回测遇到洞会直接失败,不会跳过那一分钟** —— 因为在有缺口的时间线上跑出来的收益,对应的是一段从未发生过的行情。先用 `download` 补齐。
101
+
102
+ `exhaustedBefore` 表示 OKX 再往前已经没有数据了,继续请求更早的历史不会有结果。
103
+
104
+ **注意 `missingCount` 只统计已存区间 `oldestOpen ~ newestOpen` 内部的缺口。** 它为 0 并不代表你要回测的区间是完整的 —— 如果回测窗口延伸到 `newestOpen` 之后(数据是几小时前下的,而你要跑"最近 30 天"),那一段根本还没入库,`coverage` 看不到它,但回测会失败。
105
+
106
+ 所以更可靠的做法是:**回测前先跑一次 `download`。** 它只请求缺失的分钟,已经有的不会重复拉:
107
+
108
+ ```bash
109
+ desic-okx data download --inst BTC-USDT-SWAP --days 30
110
+ desic-okx strategy backtest --file ema.py --inst BTC-USDT-SWAP --days 30 --follow
111
+ ```
112
+
113
+ ### 时间一律按 UTC
114
+
115
+ ```bash
116
+ --from 2026-08-13 # 当天 00:00 UTC
117
+ --from 2026-08-13T07:12:00Z # 精确到分钟,UTC
118
+ --from 2026-08-13T07:12:00+08:00 # 带偏移量,会换算成 UTC
119
+ --from 2026-08-13T07:12 # 报错:缺时区
120
+ ```
121
+
122
+ 最后一种会被拒绝,而不是当成本地时间。带时钟时间却不带时区的话,`Date.parse` 会按机器时区平移 —— 结果是一份看起来合理、但别人无法复现的报告。
123
+
124
+ ---
125
+
126
+ ## 写策略
127
+
128
+ ```bash
129
+ desic-okx strategy new --out ema.py --template ema-trend
130
+ ```
131
+
132
+ 四个模板:`blank`、`ema-trend`、`atr-breakout`、`bollinger-reversion`。
133
+
134
+ ### 最小结构
135
+
136
+ ```python
137
+ def on_bar(ctx):
138
+ fast = ctx.indicators.ema(ctx.instrument_id, "1m", int(ctx.params.get("fastPeriod", 20)))
139
+ slow = ctx.indicators.ema(ctx.instrument_id, "1m", int(ctx.params.get("slowPeriod", 60)))
140
+ if fast is None or slow is None:
141
+ return ctx.no_action("指标预热中")
142
+
143
+ position = ctx.portfolio.position(ctx.instrument_id, "long")
144
+ if position is None and fast > slow:
145
+ return ctx.open_long("快线上穿慢线")
146
+ if position is not None and fast < slow:
147
+ return ctx.close_long("快线下穿慢线")
148
+ return ctx.no_action("持仓不变")
149
+ ```
150
+
151
+ 要点:
152
+
153
+ - `on_bar(ctx)` 必需,每根已确认的 1 分钟收线调用一次
154
+ - **一根 K 线只返回一个动作**
155
+ - 动作是意图,**不含张数** —— 张数由主机按预算和合约规则换算
156
+ - `on_start(ctx)` 可选,只做初始化,必须返回 `no_action`
157
+
158
+ ### 动作集
159
+
160
+ ```python
161
+ ctx.no_action(reason)
162
+ ctx.open_long(reason, protection=None, execution=None)
163
+ ctx.open_short(reason, protection=None, execution=None)
164
+ ctx.close_long(reason, execution=None)
165
+ ctx.close_short(reason, execution=None)
166
+ ctx.cancel_order(order_id, reason)
167
+ ```
168
+
169
+ 附带止损止盈,价格是绝对价:
170
+
171
+ ```python
172
+ return ctx.open_long("突破", protection={"stopLossPrice": 67000.0, "takeProfitPrice": 71000.0})
173
+ ```
174
+
175
+ 指定限价单:
176
+
177
+ ```python
178
+ return ctx.open_long("挂单等回踩", execution=ctx.limit_order(67500.0))
179
+ ```
180
+
181
+ ### 成交时机
182
+
183
+ - **市价动作在下一根 1 分钟开盘成交。** 决策在收线时做出,不可能用收盘价本身成交
184
+ - **限价动作挂单,后续 K 线保守撮合**:买单要求最低价严格穿到限价下方,卖单要求最高价严格穿到上方;单根 K 线最多成交其成交量的 10%
185
+ - 反手必须显式先平后开。持多仓时直接 `open_short` 会报错,不会自动翻仓
186
+
187
+ ### 校验规则
188
+
189
+ ```bash
190
+ desic-okx strategy validate --file ema.py
191
+ ```
192
+
193
+ 毫秒级返回,带行号。**每次改完都跑一次** —— 比排队跑完回测才发现 import 违规划算得多。
194
+
195
+ - 允许的 import:`collections` `dataclasses` `math` `numpy` `pandas` `statistics` `typing`
196
+ - 禁用:`getattr` `setattr` `eval` `exec` `open` `__import__`、dunder 访问、async 处理函数、生成器
197
+
198
+ `getattr` 被禁是刻意的:用它探测字段拼写会把协议不匹配藏起来,而这种问题应该当场报错。**直接用文档里的字段名。**
199
+
200
+ ### 多周期
201
+
202
+ ```bash
203
+ desic-okx strategy backtest --file s.py --inst BTC-USDT-SWAP --intervals 30m,1H
204
+ ```
205
+
206
+ ```python
207
+ buckets = ctx.market.bars(ctx.instrument_id, "30m", lookback=2)
208
+ if not buckets[-1].confirmed:
209
+ return ctx.no_action("等 30m 收线")
210
+ ```
211
+
212
+ 注意:`ctx.indicators` 只接受 `1m`。高周期的当前桶在确认前还可能被修正,用它做增量指标状态,缓存值会悄悄依赖于"第一次算的时间点"。
213
+
214
+ ### 看不到未来
215
+
216
+ `ctx.market.bars()` 只暴露到当前可见长度,物理上取不到之后的 K 线。任何可见 K 线的 `closeTime ≤ ctx.as_of_ms`。测试里专门构造了"能看到未来就会盈利"的策略,断言它无法盈利。
217
+
218
+ ---
219
+
220
+ ## 回测
221
+
222
+ ```bash
223
+ # 最近 30 天
224
+ desic-okx strategy backtest --file ema.py --inst BTC-USDT-SWAP --days 30
225
+
226
+ # 指定区间,前台等待并打印报告
227
+ desic-okx strategy backtest --file ema.py --inst BTC-USDT-SWAP \
228
+ --from 2026-07-01 --to 2026-08-01 --follow
229
+
230
+ # 带参数
231
+ desic-okx strategy backtest --file ema.py --inst BTC-USDT-SWAP \
232
+ --params params.json --intervals 30m
233
+ ```
234
+
235
+ `params.json` 就是普通的 JSON 对象,对应 `ctx.params`:
236
+
237
+ ```json
238
+ { "fastPeriod": 20, "slowPeriod": 60 }
239
+ ```
240
+
241
+ 交互界面:
242
+
243
+ ```
244
+ > /backtest ema.py BTC-USDT-SWAP 30
245
+ > /backtest ema.py BTC-USDT-SWAP 2026-07-01..2026-08-01
246
+ ```
247
+
248
+ 第三个参数同时接受天数和区间。`..` 任一侧可省略:`2026-07-01..` 表示从该日至今。
249
+
250
+ ### 长任务是异步的
251
+
252
+ 不带 `--follow` 时立即返回运行 ID,用 `strategy report` 或 `/report` 查看。**调用返回不等于回测跑完** —— 状态是 `completed` 才有指标可读。
253
+
254
+ ```bash
255
+ desic-okx strategy runs --limit 20
256
+ desic-okx strategy cancel --run bt_xxxx
257
+ desic-okx strategy delete --run bt_xxxx --yes
258
+ ```
259
+
260
+ ### 预热 K 线
261
+
262
+ `--from/--to` 界定的是**评估区间**。`--preload`(默认 120)根 K 线加载在评估起点**之前**,只用于指标预热,**不计入任何统计** —— 不产生权益点、不产生成交、不算收益。
263
+
264
+ 指标需要预热期,如果拿评估区间开头的 K 线去暖机,那段时间的结果就不可信了。
265
+
266
+ ---
267
+
268
+ ## 回测参数
269
+
270
+ 所有回测共用一份保存在磁盘上的参数:
271
+
272
+ ```bash
273
+ desic-okx strategy settings # 查看
274
+ desic-okx strategy settings set leverage=5 budget-pct=0.1 # 修改
275
+ desic-okx strategy settings reset # 恢复默认
276
+ ```
277
+
278
+ 交互界面里 `/settings` 或 `/backtest setting` 打开表单(`↑↓` 移动,`⏎` 编辑,`s` 保存)。
279
+
280
+ | 参数 | 默认 | 范围 | CLI 覆盖 |
281
+ | --- | --- | --- | --- |
282
+ | 初始权益 (USDT) | 10,000 | 1 ~ 1亿 | `--equity` |
283
+ | 杠杆 | 10 | 1 ~ 125 | `--leverage` |
284
+ | 保证金安全系数 | 1 | 1 ~ 20 | `--margin-safety` |
285
+ | 单笔预算比例 | 20% | 0.1% ~ 100% | `--budget-pct` |
286
+ | 单笔固定预算 (USDT) | 0(表示用比例) | 0 ~ 1亿 | `--budget-usdt` |
287
+ | Taker 手续费 | 5 bps | 0 ~ 100 bps | `--taker-fee` |
288
+ | Maker 手续费 | 2 bps | 0 ~ 100 bps | `--maker-fee` |
289
+ | 入场滑点 | 1 bps | 0 ~ 1000 bps | `--entry-slippage` |
290
+ | 出场滑点 | 1 bps | 0 ~ 1000 bps | `--exit-slippage` |
291
+ | 预热 K 线 | 120 | 2 ~ 20000 | `--preload` |
292
+ | 默认评估天数 | 30 | 1 ~ 365 | `--days` |
293
+ | 期末平仓 | 开 | — | `--close-at-end` |
294
+
295
+ 命令行上传的参数只覆盖本次运行,不写入磁盘。超范围的值会被**拒绝而不是截断** —— 悄悄跑一个与你要求不同的回测,等于让错误的假设变得不可见。
296
+
297
+ **每次运行都会记录它实际用的全部参数**,在报告的"本次回测参数"一节里。参数可能在两次运行之间被改过,一份说不清自己假设的报告,跟任何其他报告都不可比。
298
+
299
+ 仓位规模来自交易所报告的合约规格(`ctVal`、`ctMult`、`lotSz`、`minSz`),不做猜测。拿猜出来的合约面值算仓位,会得到一份数字全都合理、但规模差了一个倍数的报告。
300
+
301
+ ---
302
+
303
+ ## 读结果
304
+
305
+ ```bash
306
+ desic-okx strategy report --run bt_xxxx # 终端指标
307
+ desic-okx strategy report --run bt_xxxx --open # 浏览器完整报告
308
+ desic-okx strategy report --run bt_xxxx --equity --trades
309
+ ```
310
+
311
+ HTML 报告完全自包含:所有资源内联,不在打开时请求任何网络,因此可离线查看、可随运行一起归档,也不会把策略内容发到任何地方。内容包括权益曲线(带回撤带、悬停读数)、按月表现、盈亏分布、全部成交明细。
312
+
313
+ ### 几个容易误读的指标
314
+
315
+ - **年化收益 / 卡玛**:评估区间短于 30 天时**不给**。五天的窗口外推出来是五位数百分比,看着像精度其实是噪声,而任何基于它的比率都会继承这份噪声
316
+ - **盈亏比 / 胜率**:按**扣除手续费后**的单笔盈亏计算。用毛利算的话,一个权益实际在下跌的高频策略也能报出大于 1 的盈亏比
317
+ - **手续费占毛利比例**:超过 100% 意味着仅手续费就把一个有效信号吃成了亏损。这是"看起来合理的策略却亏钱"最常见的原因
318
+ - **保证金耗尽**:这是研究用的风险边界,**不是 OKX 强平价格的估算**
319
+
320
+ ### 可复现性
321
+
322
+ 每次运行记录一个 `dataSnapshotId`,其哈希覆盖消费的每一根 K 线。相同请求 + 未变动的数据 = 相同 ID + 相同指标。ID 不同就说明底层数据变了。
323
+
324
+ ---
325
+
326
+ ## 参数调优
327
+
328
+ **不要用"跑一批回测挑最好的"这种做法。** 在任何一段窗口上,最好的参数总有一部分是拟合了这段行情的噪声;用挑出它的那批 K 线去衡量它,不构成证据。
329
+
330
+ `strategy optimize` 把窗口按 **7:3** 切分:在前 70% 搜索,用后 30% 排名 —— 后者是候选从未见过的。
331
+
332
+ ```bash
333
+ desic-okx strategy optimize --file ema.py --inst BTC-USDT-SWAP \
334
+ --space space.json --params params.json \
335
+ --from 2026-05-01 --to 2026-08-01 --budget 100 --follow
336
+ ```
337
+
338
+ `space.json` 声明每个参数的搜索范围:
339
+
340
+ ```json
341
+ {
342
+ "fastPeriod": { "min": 5, "max": 50, "step": 5 },
343
+ "slowPeriod": { "min": 20, "max": 200, "step": 10 }
344
+ }
345
+ ```
346
+
347
+ `--params` 传策略自身的默认参数。**空间里的每个键都会用它校验**:声明一个策略不读的参数会被拒绝 —— 否则所有候选行为完全一致,排第一的只是噪声。
348
+
349
+ ### 采样
350
+
351
+ 组合数超过预算时做确定性采样(拉丁超立方),保证:
352
+
353
+ - **每个参数的每个取值至少被评估一次。** 从没试过 `slowPeriod: 200` 的搜索,没资格对它下任何结论
354
+ - **相同请求 → 相同候选集。** 种子由请求本身派生,重跑可复现,而不是重新抽一次奖
355
+
356
+ 预算小于最宽参数的取值个数时会**拒绝**,因为覆盖保证在算术上不可能成立。
357
+
358
+ ### 限制
359
+
360
+ | 项 | 限制 |
361
+ | --- | --- |
362
+ | 空间组合数 | ≤ 5,000 |
363
+ | 候选预算 | 2 ~ 1,000,默认 100 |
364
+ | 每段最少 K 线 | 1,440(因此窗口至少 7 天) |
365
+
366
+ 组合数上限是刻意的:三个参数各 10 个取值就是 1,000 个候选,而每个都要模拟两次。直接拒绝比启动一个要跑几小时的任务有用。
367
+
368
+ ### 读候选表
369
+
370
+ ```bash
371
+ desic-okx strategy optimize-report --run opt_xxxx --top 20
372
+ ```
373
+
374
+ ```
375
+ # fastPeriod slowPeriod 训练收益 验证收益 训练回撤 验证回撤 成交笔数 结论
376
+ 1 40 150 -28.62% +21.77% 29.81% 8.73% 50 稳健
377
+ 2 40 180 -27.95% +22.33% 29.34% 10.35% 39 稳健
378
+ 20 10 60 -57.30% -4.94% 58.03% 21.92% 150 偏弱
379
+ ```
380
+
381
+ **应当引用的是验证段。** 训练段数字描述的是"在挑选它的那批 K 线上"的表现,不是证据,不能当作结果报出去。
382
+
383
+ 两列都显示是刻意的 —— 差距本身就是结论:
384
+
385
+ | 结论 | 含义 |
386
+ | --- | --- |
387
+ | `稳健` | 两段都盈利,且验证段保住了训练段收益的相当一部分 |
388
+ | `过拟合` | 被挑选时盈利,之后不盈利或衰减严重 |
389
+ | `偏弱` | 两段都不盈利 |
390
+ | `无从解释` | 训练段不盈利,验证段盈利 |
391
+
392
+ `无从解释`需要单独解释一下。它不是"更好的稳健" —— **恰恰相反**:训练段没有优势,也就没有任何东西被带过切分点,验证段的盈利来自那段行情本身的走法。切分要防的正是这种推理。这类候选仍然参与排名(得分就是得分),但它不构成"这组参数有效"的证据。
393
+
394
+ **整张表都是`无从解释`时,问题在窗口,不在策略。** 实测一次 30 天 BTC-USDT-SWAP 搜索,20 个候选全部训练段亏、验证段赚 33~36% —— 因为那 9 天是一段单边上涨,只做多的策略几乎无法错过。此时该做的是换一段包含涨、跌、震荡的窗口重搜,而不是从表里挑第一名。
395
+
396
+ 排名依据会一并说明:验证段足够长时用**卡玛**,不足 30 天时退回**验证收益 / 最大回撤**,若无回撤则用**验证收益**。这一栏不总是同一个量,所以标注出来 —— 一个有时表示别的东西的"卡玛"列,比一个诚实的标签更糟。
397
+
398
+ ### 第一名的数字仍然偏乐观
399
+
400
+ 用验证段得分从 N 个候选里挑第一,本身就是在挑高分。切分消除了大部分偏差,但没有全部消除,剩余部分随预算增大。**要真正干净的估计,需要一段比整个搜索窗口更晚的数据。**
401
+
402
+ ### 采用最佳参数
403
+
404
+ ```bash
405
+ desic-okx strategy optimize-report --run opt_xxxx --adopt ema.params.json
406
+ ```
407
+
408
+ 写到策略**旁边**的 `<策略名>.params.json`,**不改策略源码** —— 源码是你写的,工具不该改写它。
409
+
410
+ 然后用这组参数单独跑一次回测确认:
411
+
412
+ ```bash
413
+ desic-okx strategy backtest --file ema.py --params ema.params.json \
414
+ --inst BTC-USDT-SWAP --from <验证段起点> --to <验证段终点> --follow
415
+ ```
416
+
417
+ 指标应当与候选表里的验证段完全一致。注意这次确认覆盖的仍是搜索用过的同一段时间。
418
+
419
+ 交互界面里,`/optimize` 会自动读取策略旁边的 `<策略名>.params.json` 作为默认参数:
420
+
421
+ ```
422
+ > /optimize ema.py BTC-USDT-SWAP 2026-05-01..2026-08-01 space.json 100
423
+ > /optimize report opt_xxxx
424
+ > /optimize report opt_xxxx --adopt ema.params.json
425
+ ```
426
+
427
+ ---
428
+
429
+ ## 对比多次回测
430
+
431
+ ```bash
432
+ desic-okx strategy compare --runs bt_aaa,bt_bbb # 终端表格
433
+ desic-okx strategy compare --runs bt_aaa,bt_bbb --open # 浏览器叠加图
434
+ ```
435
+
436
+ ```
437
+ > /compare bt_aaa bt_bbb
438
+ > /compare bt_aaa bt_bbb --no-open
439
+ ```
440
+
441
+ 2 到 6 个运行。输出包括指标矩阵、**只显示有差异的参数**(相同的折叠成一行计数)、以及权益曲线叠加。
442
+
443
+ ### 曲线归一化
444
+
445
+ 曲线按各自起点归一化到 100。两个初始权益不同的运行放在同一坐标轴上,余额大的那条单纯画得更高 —— 这跟策略好坏无关。
446
+
447
+ ### 警告要先看
448
+
449
+ 这才是 `/compare` 作为一个命令存在的理由,而不是简单读两份报告。**两次用了不同数据、不同合约、不同区间或不同成本的运行,不是同一个问题的两个答案** —— 而这一点在指标里完全看不出来。
450
+
451
+ | 警告 | 含义 |
452
+ | --- | --- |
453
+ | 数据不同 | 消费的 K 线不同。任何差异都可能来自数据本身 |
454
+ | 合约不同 | 收益反映的是各自市场的涨跌 |
455
+ | 区间不同 | 各项合计覆盖的时间长度不一样 |
456
+ | 参数不同 | 杠杆、手续费、滑点或预算不同 |
457
+ | 未完成 | 有运行没跑完,没有指标 |
458
+
459
+ 出现警告时仍然允许对比 —— 有时那个差异正是你要考察的东西。但结论里要说清是哪一项不同,而不是直接宣布赢家。
460
+
461
+ **只有当所有运行共享同一个 `dataSnapshotId`、同一个合约、同一个区间、同一套成本时**,对比才构成"A 策略优于 B"的证据。
462
+
463
+ 策略参数会展开成 `params.fastPeriod` 这样的单独行。参数是两次运行最常见的差异来源,挤在一个 JSON 里就得靠肉眼比对两串字符串 —— 偏偏是最关键的那个值。
464
+
465
+ ---
466
+
467
+ ## 下一步:接实盘
468
+
469
+ 回测和调参不是终点,是**启用实盘的前置条件**。同一个策略文件可以直接绑到账户上,每根 1 分钟收线自动执行:
470
+
471
+ ```bash
472
+ # 行情先切模拟盘(模拟盘与实盘是两套独立行情,价格不同)
473
+ desic-okx market env demo
474
+ desic-okx data download --inst BTC-USDT-SWAP --days 30
475
+
476
+ # 用模拟盘数据回测一次 —— 启用按"源码 hash + 合约"匹配回测记录
477
+ desic-okx strategy backtest --file ema.py --params ema.params.json \
478
+ --inst BTC-USDT-SWAP --days 30 --follow
479
+
480
+ # 建 Profile(创建绝不会开始交易)
481
+ desic-okx live create --file ema.py --params ema.params.json \
482
+ --inst BTC-USDT-SWAP --account demo --environment demo \
483
+ --entry-budget 100 --side-budget 200 --daily-loss 500
484
+
485
+ desic-okx live readiness --id lp_xxxx
486
+ desic-okx live start --id lp_xxxx
487
+ ```
488
+
489
+ 两个衔接点值得注意:
490
+
491
+ - **回测记录按源码 hash 匹配,不是文件名。** 改一个字符就得重跑,因为旧记录描述的不再是这份策略。
492
+ - **换环境要重跑回测。** 模拟盘和实盘的 K 线来自两个独立市场,实盘 Profile 需要用实盘数据跑过的回测。
493
+
494
+ 回测里的成交是模拟撮合,实盘是真实报单 —— 滑点、拒单、部分成交都会出现在回测看不到的地方。完整说明见 **[实盘执行使用指南](live-trading.md)**。
495
+
496
+ ---
497
+
498
+ ## 给 AI agent 用
499
+
500
+ MCP 工具(`desic-okx setup` 会自动为 Codex 和 Claude Code 装好):
501
+
502
+ | 工具 | 用途 |
503
+ | --- | --- |
504
+ | `data_list_instruments` | 本地全部合约与覆盖区间 |
505
+ | `data_coverage` | 单合约区间、根数、缺口列表 |
506
+ | `data_download` | 下载或补齐指定区间 |
507
+ | `strategy_validate_source` | 静态校验,带行号 |
508
+ | `strategy_run_backtest` | 入队,立即返回 `runId` |
509
+ | `strategy_run_optimize` | 参数搜索,入队 |
510
+ | `strategy_get_optimization` | 排名后的候选表,含训练/验证指标 |
511
+ | `strategy_compare_runs` | 多运行对比,含差异参数与警告 |
512
+ | `strategy_get_run` | 状态、进度、汇总指标 |
513
+ | `strategy_get_run_equity` / `_trades` / `_actions` | 分页读明细 |
514
+ | `strategy_settings` / `_update` | 读写回测参数 |
515
+ | `strategy_cancel_run` / `strategy_delete_run` | 取消 / 删除 |
516
+
517
+ **分层输出是刻意的**:`strategy_get_run` 只给汇总指标,权益曲线和成交明细分页取(单页上限 20,000 行)。一条完整曲线有几万个点,全塞给 agent 只会冲爆上下文。
518
+
519
+ 同时安装的 `okx-strategy-research` Skill 里写明了工作流约束:写完立刻校验、回测前查缺口、入队后轮询而不假设同步完成、先说事实再说解读、说明模拟假设、调参后引用验证段而不是训练段。
520
+
521
+ ### agent 也能走完整条链
522
+
523
+ 上游是行情分析(`market_*`、`news_*`、`smart_money_*` 工具和对应 Skill),下游是实盘(`live_*` 工具和 `okx-live-trading` Skill)。一个 agent 可以:分析行情找到想法 → 写成策略 → 校验 → 回测 → 调参 → 建 Profile → 在模拟盘启用观察 → 请你批准后上实盘。
524
+
525
+ 但下游默认是关的:
526
+
527
+ ```bash
528
+ desic-okx live agent-access on # 需重启 MCP 客户端
529
+ ```
530
+
531
+ 关闭时 `live_create_profile` 和 `live_start` **不出现在** agent 的工具列表里,它只能读 Profile 和停止。开启后能创建和启用,但 Skill 要求它在启用实盘 Profile 前停下来问你并给出数字。详见 **[实盘执行使用指南 · 给 AI agent 用](live-trading.md#给-ai-agent-用)**。
532
+
533
+ ---
534
+
535
+ ## 常见问题
536
+
537
+ **回测失败,提示 "does not fully cover"**
538
+ 区间里的数据不完整。先跑 `data download --inst <合约> --days <天数>` 补齐,用同一个窗口,然后重试。这是 fail-closed 设计,不会静默跳过缺失的分钟。
539
+
540
+ 注意 `data coverage` 显示 `missingCount 0` 时也可能出现这个错误 —— 它只统计已存区间内部的缺口,不包含"窗口延伸到最新一根之后"的部分。数据是几小时前下的、而你要跑"最近 N 天"时就会这样。`download` 能同时解决两种情况。
541
+
542
+ **提示 "has no timezone"**
543
+ 带时钟时间的时间戳必须带时区:`2026-08-13T07:12:00Z`。纯日期 `2026-08-13` 不受影响。
544
+
545
+ **提示 "Unknown tool 'strategy_run_optimize'"**
546
+ 常驻 runtime 是升级前启动的。`desic-okx stop` 后重试即可,下次调用会自动拉起新版本。
547
+
548
+ **调优提示 "cannot cover 'slowPeriod', which has 19 values"**
549
+ 预算比该参数的取值个数还小,覆盖保证不可能成立。把预算提到至少 19,或者把 step 调大。
550
+
551
+ **调优提示 "declares no parameter 'xxx'"**
552
+ `space.json` 里的键在策略参数里不存在。用 `--params` 传上策略的默认参数,并检查拼写。
553
+
554
+ **调优提示 "below the 1440-bar minimum"**
555
+ 窗口太短,7:3 切分后有一段不足 1,440 根。用至少 7 天的窗口。
556
+
557
+ **空间提示 "above the 5000 limit"**
558
+ 组合数太多。缩小范围或调大 step。
559
+
560
+ **年化和卡玛显示 `-`**
561
+ 评估区间短于 30 天,这两个值被刻意隐去。用更长的窗口。
562
+
563
+ **收益率为正但盈亏比小于 1**
564
+ 先看"手续费占毛利比例"。超过 100% 说明手续费吃掉了全部信号优势,问题在换手率而不一定在逻辑。
565
+
566
+ ---
567
+
568
+ ## 必须知道的限制
569
+
570
+ **回测是模拟,不是预期收益。** 具体地说:
571
+
572
+ - 手续费和滑点按你配置的固定值计算,不随行情变化
573
+ - 市价单在下一根 1 分钟开盘成交,不含真实的交易所延迟
574
+ - 限价单按 K 线保守估计撮合,**没有订单簿队列模型**,单根 K 线成交量上限 10%
575
+ - 保证金耗尽是研究用的风险边界,不是 OKX 强平估算
576
+ - 只有 1 分钟粒度,K 线内部的价格路径是不可知的(同一根内止损和止盈都可能触及时,按不利的那个算)
577
+
578
+ **过拟合是默认结果,不是例外。** 单一区间上的亮眼成绩要按可疑对待。优先选择在相邻参数值上都稳定的那一组,而不是一个孤立的尖峰 —— 被一片糟糕结果包围的单个高点,是数据里的巧合,不是一个设定。
579
+
580
+ 负面结果是有效发现。一个亏钱的策略,值得如实报出来。
581
+
582
+ ---
583
+
584
+ ## 相关文件位置
585
+
586
+ | 内容 | 路径 |
587
+ | --- | --- |
588
+ | 配置与回测参数 | 配置目录下 `backtest-settings.json` |
589
+ | 数据库(K 线、运行记录) | 数据目录下 SQLite 文件 |
590
+ | HTML 报告 | 数据目录下 `reports/`,保留最近 20 份 |
591
+ | Python venv | 数据目录下 `python/venv` |
592
+
593
+ `desic-okx status` 和 `desic-okx doctor` 会打印实际路径。
594
+
595
+ ---
596
+
597
+ 策略验证完成后如需接到真实账户,见 **[实盘执行使用指南](live-trading.md)**。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "desic-okx-agent",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Independent Desic runtime, MCP server, CLI, and agent skills for OKX",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,8 +28,11 @@
28
28
  },
29
29
  "files": [
30
30
  "dist",
31
+ "python/main.py",
32
+ "python/desic_strategy/*.py",
31
33
  "skills",
32
34
  "examples",
35
+ "docs",
33
36
  "README.md",
34
37
  "README.en.md",
35
38
  "LICENSE"
@@ -66,8 +69,14 @@
66
69
  "@types/better-sqlite3": "^7.6.13",
67
70
  "@types/node": "^22.19.15",
68
71
  "@types/ws": "^8.18.1",
72
+ "ink-testing-library": "^4.0.0",
69
73
  "tsx": "^4.21.0",
70
74
  "typescript": "^5.9.3",
71
75
  "vitest": "^3.2.4"
76
+ },
77
+ "optionalDependencies": {
78
+ "@types/react": "^19.2.18",
79
+ "ink": "^6.8.0",
80
+ "react": "^19.2.8"
72
81
  }
73
82
  }
@@ -0,0 +1,34 @@
1
+ """Desic strategy research: policy, timeframe aggregation, and the backtest engine.
2
+
3
+ The host owns the clock, the market window, matching, risk checks, and the audit
4
+ trail. A strategy receives one immutable point-in-time context and returns one
5
+ high-level decision. It never receives an exchange client, credentials, a system
6
+ clock, a database handle, or an order API.
7
+ """
8
+
9
+ from .actions import Decision, StrategyError
10
+ from .engine import Costs, Engine, Instrument, RunConfig, Sizing
11
+ from .policy import PolicyViolation, validate_source
12
+ from .report import Metrics, build_report, calculate_metrics
13
+ from .timeframe import Bar, DataContractError, TimeframeAggregator, aggregate
14
+
15
+ __all__ = [
16
+ "Bar",
17
+ "Costs",
18
+ "DataContractError",
19
+ "Decision",
20
+ "Engine",
21
+ "Instrument",
22
+ "Metrics",
23
+ "PolicyViolation",
24
+ "RunConfig",
25
+ "Sizing",
26
+ "StrategyError",
27
+ "TimeframeAggregator",
28
+ "aggregate",
29
+ "build_report",
30
+ "calculate_metrics",
31
+ "validate_source",
32
+ ]
33
+
34
+ PROTOCOL_VERSION = "desic.strategy/v1"