19 KiB
ScriptStrategy 复杂策略运行时改造实施方案
1. 目标
让脚本策略从“按 K 线生成买卖信号”升级为“可承载复杂状态机和算法执行”的运行时,优先支持以下用户场景:
- 多层分仓,例如 5 个大分仓,每层 3 个子单。
- 层内/层间价格间距可调。
- 每个子单按马丁倍数或自定义序列放大。
- 按实际成交均价止盈,而不是按脚本计划价止盈。
- 只做多、只做空、多空双向 basket 独立运行。
- 合约场景支持按杠杆后的名义价值换算下单量。
- 后端重启、网络失败、部分成交、拒单后不重复补仓。
核心判断:现在的脚本 API 已经有基础,真正要补的是可信状态源、订单生命周期、恢复和风控。先把运行时地基打牢,再开放更复杂模板。
2. 当前代码基础
现有链路已经可复用:
app/services/strategy_script_runtime.pyStrategyScriptContext已支持open_long/add_long/open_short/add_short/close_long/close_short。ScriptPosition已有 long/short 独立腿,并保留旧版 net position 兼容视图。
app/services/trading_executor.py- 脚本产生
ctx._orders后转成执行信号。 _enqueue_pending_order()写入pending_orders。_hydrate_script_ctx_from_positions()会从qd_strategy_positions恢复持仓视图。
- 脚本产生
app/services/pending_order_worker.pyPendingOrderWorker拉取pending_orders,执行 live/signal 模式派单。- 已有 live order context、client order id、成交回填等基础。
app/services/pending_orders/fill_records.pypersist_strategy_fill()按成交更新qd_strategy_trades和qd_strategy_positions。
app/services/live_trading/records.pyapply_fill_to_local_position()已经按成交更新均价和部分减仓。
migrations/init.sql- 已有
pending_orders、qd_strategy_positions、qd_strategy_trades、qd_grid_resting_orders等基础表。
- 已有
现有不足:
pending_orders更像队列表,不是完整 order intent ledger。script_runtime_state存在trading_configJSON 里,适合轻量参数,不适合作为复杂策略状态可信源。- 缺少
strategy_run_id、代码快照、参数快照和运行 epoch。 - 缺少 basket / child order / fill / recovery event 的标准模型。
- 多空 position API 已具备雏形,但 basket 层还没独立。
- 订单幂等当前主要靠
(strategy_id, symbol, signal_type, signal_ts),不够表达层级分仓的layer/order/action。 - 回测还没有完全复用 live 的 order intent、fill、fee、slippage 语义。
3. 总体架构
建议增加一层“策略运行时内核”,位于脚本和下单队列之间:
ScriptStrategy
-> StrategyRuntimeContext
-> BasketRuntime / RuntimeStateStore
-> OrderIntentService
-> ExecutionScheduler
-> PendingOrderWorker / LiveTradingClient
-> FillLedger / PositionLedger
-> RecoveryService / RiskGuard
原则:
- 脚本只表达意图和状态机,不直接操作数据库和交易所。
- 数据库是恢复可信源,内存只做当前循环缓存。
- 所有真实下单先落
order_intent,再提交交易所。 - basket 均价、层级、TP、风险状态由实际 fill 驱动。
- live、paper、backtest 尽量复用同一套 order intent 和 fill 语义。
4. 数据库改造
新增表建议:
strategy_runs
strategy_runtime_state
strategy_baskets
strategy_basket_orders
strategy_order_intents
strategy_order_fills
strategy_runtime_events
strategy_runtime_locks
4.1 strategy_runs
记录每一次启动,不再只靠 strategy_id 表示运行实例。
关键字段:
idstrategy_iduser_idsource_version_idcode_hashparameter_snapshot_jsonexchange_idcredential_idsymbolmarket_typeposition_moderuntime_status:running/recovering/paused/needs_review/stopping/stopped/failedruntime_epochstarted_atstopped_atstop_reason
4.2 strategy_runtime_state
替代把复杂状态塞进 trading_config.script_runtime_state。
关键字段:
strategy_run_idstrategy_idstate_keystate_jsonversionupdated_at
用途:
- 保存轻量脚本状态:冷却计数、上次触发价、用户自定义变量。
- 由运行时托管
ctx.state.get/set/flush()。 - 不保存成交和订单真相。
4.3 strategy_baskets
复杂分仓策略的核心状态。
关键字段:
basket_idstrategy_run_idstrategy_idsymbolside:long/shortstatus:idle/opening/active/closing/closed/failed/needs_reviewcurrent_layercurrent_order_in_layertotal_qtytotal_notionalavg_entry_pricenext_entry_triggertake_profit_pricemax_layermax_orders_per_layerrisk_state_jsoncreated_atupdated_at
4.4 strategy_basket_orders
记录每个子单在 basket 内的归属。
关键字段:
basket_order_idbasket_idsidelayer_indexorder_indexaction:open/add/reduce/closeplanned_priceplanned_qtyplanned_notionalstatus:planned/intent_created/submitted/accepted/partially_filled/filled/rejected/cancelled/expired/unknownorder_intent_idexchange_order_idclient_order_idfilled_qtyavg_fill_pricefeeerror
唯一约束:
strategy_run_id + basket_id + side + layer_index + order_index + action
4.5 strategy_order_intents
统一订单意图。后续 TWAP、BestLimit、Iceberg 都从这里开始。
关键字段:
order_intent_idstrategy_run_idstrategy_idbasket_idbasket_order_ididempotency_keysymbolmarket_typesideposition_sidereduce_onlyorder_typequantitynotionallimit_priceexecution_algo:market/limit/best_limit/twap/stop/icebergstatusclient_order_idexchange_order_idpayload_jsoncreated_atupdated_at
4.6 strategy_order_fills
成交流水是均价、TP 和审计的真实来源。
关键字段:
fill_idorder_intent_idbasket_idexchange_order_idexchange_fill_idsideposition_sidepricequantitynotionalfeefee_ccyfilled_atraw_json
唯一约束:
exchange_id + exchange_fill_id
没有交易所 fill id 的场景,用 order_intent_id + price + quantity + filled_at 做近似去重。
5. 运行时 API 改造
保留现有 ctx.open_long() 等 API,同时新增更适合复杂策略的托管接口。
5.1 ctx.state
ctx.state.get("cooldown_bars", 0)
ctx.state.set("cooldown_bars", 3)
ctx.state.flush()
用途:
- 轻量脚本变量。
- 自动绑定
strategy_run_id。 - 后端定期或关键动作前 checkpoint。
5.2 ctx.basket
long_basket = ctx.basket("long")
if long_basket.is_idle():
long_basket.open_child_order(layer=1, order=1, notional=100)
if long_basket.should_add(current_price):
long_basket.open_child_order(layer=2, order=1, notional=200)
if long_basket.should_take_profit(current_price):
long_basket.close_all(reason="avg_take_profit")
运行时负责:
- 检查幂等键。
- 创建
strategy_basket_orders。 - 创建
strategy_order_intents。 - 将 intent 推进到
pending_orders或算法调度器。 - 成交后刷新 basket 均价、TP、层级和风险状态。
5.3 ctx.long_position / ctx.short_position
ctx.position 保持兼容,新增更明确接口:
ctx.long_position.size
ctx.long_position.avg_entry
ctx.short_position.size
ctx.short_position.avg_entry
多空双开时,脚本不能再依赖单一 net position 判断。
6. LayeredMartingaleBasket 官方模板
先内置一个官方模板,不先开放用户自由组合所有底层能力。
参数:
symbol
direction: long | short | both
base_order_value
leverage
layers = 5
orders_per_layer = 3
martingale_multiplier = 2.0
intra_spacing_pct
inter_spacing_pct
take_profit_pct
max_total_notional
max_margin_pct
hard_stop_pct
cooldown_bars
restart_after_take_profit
运行逻辑:
idle:
创建第 1 层第 1 子单
active:
如果价格逆向达到层内/层间触发价,创建下一个子单
如果实际成交均价达到 TP,close basket
如果达到最大层数,只等 TP 或风险退出
如果触发 hard stop / margin risk,停止补仓并按配置减仓或平仓
第一版限制:
direction=long或direction=short先稳定上线。direction=both内部拆成long basket和short basket,要求交易所能力矩阵确认 hedge mode。- 不支持无限马丁,必须配置最大名义价值或最大保证金占用。
7. 订单生命周期改造
统一状态:
intent_created
submitted
accepted
partially_filled
filled
rejected
cancelled
expired
unknown
reconciled
现有 pending_orders.status 可以继续承载队列阶段,但不要作为专业订单状态的唯一来源。
推荐落地方式:
OrderIntentService.create_intent()先写strategy_order_intents。- 根据
execution_algo:market/limit:直接桥接到pending_orders。twap/best_limit:交给ExecutionScheduler拆 child order。
PendingOrderWorker执行后回写 intent 状态和 fill。persist_strategy_fill()同步升级:除写qd_strategy_trades/positions外,也写strategy_order_fills并刷新 basket。
8. 幂等和单写入者
幂等键格式:
strategy_run_id:basket_id:side:L{layer_index}:O{order_index}:{action}
策略运行锁:
strategy_id + account_id/credential_id + symbol + side
机制:
- 启动策略时创建
strategy_runs,拿到runtime_epoch。 - 每个运行线程写状态和订单时必须带
runtime_epoch。 - 新线程抢锁成功后,旧线程即使还活着,也因为 epoch 不匹配不能继续写库。
- 单机可先用数据库行锁;多实例部署再接 Redis lock + DB fencing token。
9. 重启恢复流程
启动或进程崩溃后恢复时:
- 将 run 标记为
recovering。 - 读取
strategy_runs、strategy_runtime_state、strategy_baskets、未完成strategy_order_intents。 - 拉取交易所当前持仓、未成交订单、近期成交。
- 用交易所事实修正本地:
- open order 状态。
- filled quantity。
- avg fill price。
- basket avg entry。
- pending TP/close 状态。
- 对没有
exchange_order_id的 intent 做幂等判断:- 确认未提交才允许重试。
- 状态未知则进入
needs_review。
- 重建
ctx.state、ctx.basket、ctx.long_position/short_position。 - 写
recovery_completed事件。 - 切回
running。
恢复优先级:
交易所事实 > strategy_order_fills/order_intents > basket checkpoint > 内存状态
10. 风控改造
新增 StrategyRuntimeRiskGuard,在创建 intent 前检查:
- 最大层数。
- 最大子单数。
- 最大名义价值。
- 最大保证金占用。
- 最大账户权益占比。
- 最大浮亏。
- 距离强平价格最小安全距离。
- 单策略最大错误次数。
- 单交易所/全局 kill switch。
触发后写事件:
risk_guard_triggered
并进入以下之一:
paused: 暂停新增开仓。needs_review: 状态不确定,需要人工确认。stopping: 自动减仓或平仓中。failed: 无法安全处理。
11. 算法交易第一版
新增目录:
app/services/algo_trading/
order_intent.py
scheduler.py
child_order.py
execution_algorithms/
twap.py
best_limit.py
stop.py
order_state.py
reconciliation.py
risk.py
P0 支持:
MarketIntent: 现有 market order 的标准化入口。LimitIntent: 限价单标准化入口。BestLimit: 使用 best bid/ask 挂单,超时撤单重挂。TWAP: 按时间切片拆成多个 child order。Stop/StopLimit: 交易所支持则原生,不支持则本地触发。
P1 再做:
- Iceberg。
- Sniper。
- 更细的 order book 驱动。
12. 交易所能力矩阵
现有 app/services/live_trading/capabilities.py 只有市场类型能力,需要扩展为:
supports_hedge_mode
supports_reduce_only
supports_post_only
supports_stop_order
supports_iceberg
supports_client_order_id
supports_order_query_by_client_id
supports_fills_query
min_notional
min_quantity
price_tick
quantity_step
rate_limit
策略启动前做 preflight:
- 交易所是否支持该 market type。
- 是否支持 hedge mode。
- API key 是否有交易、订单查询、成交查询权限。
- 合约杠杆和保证金模式是否可配置。
- symbol 精度和最小下单量是否满足模板参数。
失败时不启动策略,给用户明确错误。
13. 回测一致性
复杂策略不能只靠布尔信号回测。建议新增运行时回测模式:
ScriptStrategy -> RuntimeContext(backtest) -> OrderIntentService(backtest) -> SimulatedFillEngine -> BasketRuntime
模拟能力:
- 最小下单量。
- 数量步进。
- 价格 tick。
- 手续费。
- 滑点。
- 部分成交。
- 下根 K 成交 / 当前 tick 成交差异。
- 子单级成交记录。
验收目标:
- 同一份
LayeredMartingaleBasket模板在 backtest/paper/live 中的子单路径一致。 - 回测报告能展示每层、每个子单、均价、TP、费用和滑点。
14. 前端需要配合的能力
第一版 UI 不要让用户直接编辑底层 runtime 表,而是提供模板化表单:
- 方向:只做多 / 只做空 / 多空双向。
- 分仓层数。
- 每层子单数。
- 层内间距。
- 层间间距。
- 马丁倍数或自定义序列。
- 基础下单金额。
- 杠杆。
- 均价止盈。
- 最大名义价值。
- 最大保证金占用。
- 硬止损。
- 启动前预估最大风险。
策略详情页增加:
- 当前 run id。
- 当前 basket 状态。
- 子单列表。
- 未完成订单。
- 最近成交。
- 恢复事件。
- 风控状态。
needs_review人工处理面板。
15. 分期落地
Phase 0:补齐可追踪运行身份
目标:先让每次运行可追溯。
任务:
- 新增
strategy_runs。 - 启动策略时生成
strategy_run_id。 - 保存代码 hash、参数快照、交易所配置摘要。
pending_orders.payload_json增加strategy_run_id。qd_strategy_trades和qd_strategy_positions增加strategy_run_id可选字段。
验收:
- 任意一笔订单能追到哪次运行、哪版代码、哪组参数。
Phase 1:Basket runtime MVP
目标:支持单向 LayeredMartingaleBasket 稳定运行。
任务:
- 新增 basket 表、basket order 表、runtime event 表。
- 实现
ctx.state和ctx.basket(side)。 - 实现
BasketRuntime.open_child_order/close_all/checkpoint。 - 实现幂等键和 DB 级唯一约束。
- 官方模板先支持
long或short。
验收:
- 5 层、每层 3 单能按配置触发。
- 重启后不会重复开同一个 layer/order。
- 均价止盈基于实际成交均价。
Phase 2:Order intent 与成交驱动
目标:把 pending_orders 从唯一状态源降级为执行队列。
任务:
- 新增
strategy_order_intents和strategy_order_fills。 TradingExecutor._enqueue_pending_order()前置创建 intent。PendingOrderWorker执行后回写 intent 和 fill。persist_strategy_fill()刷新 basket 均价、TP 和状态。- 部分成交不再误判为完整开仓。
验收:
- 部分成交后 basket 数量、均价、TP 正确。
- 拒单不修改 basket 为已开仓。
- 同一幂等键不会重复提交。
Phase 3:恢复与 needs_review
目标:重启、网络失败、未知订单状态可控。
任务:
- 实现
RecoveryService。 - 启动策略前自动恢复 active run。
- 未知状态进入
needs_review。 - UI 提供同步交易所状态、继续观察、关闭 basket、强制停止。
验收:
- 发单后模拟进程崩溃,恢复后不重复下单。
- 交易所和本地不一致时不会继续补仓。
Phase 4:多空双 basket
目标:安全开放 direction=both。
任务:
ctx.long_position/short_position正式化。ctx.basket("long")和ctx.basket("short")独立持久化。- preflight 检查交易所 hedge mode。
- 单写入锁按
strategy_id + credential_id + symbol + side切分。
验收:
- long basket 和 short basket 可同时存在,互不覆盖均价和层级。
- 单向交易所不允许启动 both 模式。
Phase 5:基础 AlgoTrading
目标:把复杂策略的“如何成交”从策略逻辑里抽出来。
任务:
- 实现
BestLimit。 - 实现
TWAP。 - 实现
Stop/StopLimit。 - 子单调度器支持撤单、重挂、超时和最大滑点。
验收:
- 同一个 basket 子单可选择 market、best_limit、twap 执行。
- 执行报告展示计划成交和实际成交差异。
16. 测试清单
必须新增的测试:
test_strategy_run_identity.pytest_basket_runtime_state.pytest_basket_order_idempotency.pytest_basket_partial_fill_avg_price.pytest_basket_rejected_order_state.pytest_basket_restart_recovery.pytest_basket_long_short_independent.pytest_order_intent_lifecycle.pytest_algo_twap_scheduler.pytest_algo_best_limit.pytest_exchange_capability_preflight.pytest_backtest_live_basket_consistency.py
重点场景:
- 同一根 K 线重复触发,不重复开同一子单。
- 写库成功但交易所提交超时,恢复时不盲目重发。
- 部分成交后按成交数量更新均价。
- 拒单后 basket order 为 rejected,不增加层级。
- 交易所最小下单量不足,策略进入
needs_review或拒绝启动。 - 多空双开时 long/short basket 独立恢复。
17. 最小可交付版本
建议最小版本不要一口气做完整算法交易平台,先交付:
strategy_run_id。ctx.state。ctx.basket("long"|"short")。strategy_baskets/strategy_basket_orders。- order intent 幂等键。
- fill 驱动 basket 均价。
LayeredMartingaleBasket单向模板。- 重启后不重复下单。
这一步完成后,已经可以覆盖用户最核心的“多层分仓 + 马丁 + 均价止盈 + 可恢复”诉求。随后再扩展多空双向和 TWAP/BestLimit,会稳很多。