踩坑复盘
重启后持仓状态丢失
服务重启后 PaperTrader 仅恢复净值、未恢复持仓明细,误判为空仓并触发全仓买入。#30 事故档案,Delta 状态对齐机制的由来。
层级 ADS · 交易执行 严重度 高 关联 #30 · commit 8a4bf9b · paper_trader.py recorder.py
现象
模拟盘服务重启后,次日成交记录出现一笔近乎全仓的买入,换手率异常飙升。账户实际持有一篮子标的,但系统表现如同首日建仓,重复买入全部标的。
定位
检查持久化逻辑,发现重启后 PaperTrader 恢复了 NAV(净值),但未恢复持仓明细。系统将当前持仓视为空仓,与目标持仓做差后产生全仓买入指令——净值已恢复,但对应的持仓结构未同步恢复。
根因
违反第三条不变量——状态可恢复。任何依赖「进程内存里记着上次干了什么」的执行器,都会在重启 / 宕机 / 漏跑后失忆。状态必须落在数据库里,并且恢复时要恢复完整状态,而不是挑几个字段。
修复
引入 Delta 状态对齐:每天调仓 = 目标持仓 − 实际持仓,而「实际持仓」始终从数据库(或实盘 API 的真实账户)读取,绝不凭内存记忆。
配套修掉了重复执行(幂等)与 PnL 计算逻辑:同一天即便脚本被跑两遍,对账结果也一致。
教训:执行器应基于持久化状态对账,而非依赖进程内存。一个健壮的交易系统在任意时刻被终止并重启后,应能仅凭数据库与账户 API 的真实数据推导出当前应执行的操作,而不依赖历史执行记录的内存缓存。
关联
- 手册:5. 交易执行层 ADS
- 同层教训:碎股与粉尘 · 周频换手把摩擦放大数倍