有一个问题,每次回测都绕着走:同一份代码,喂给它真实的实时行情,还会做出同样的动作吗?再多的回测也回答不了它。唯一的答案来自:让策略跑在实时数据上、带着真实的执行语义,然后逐笔核对——两个世界是否一致。这项核对我们每天自动做一次,它是整个平台里最高产的 bug 捕手。
第一天:搭好这面镜子
每个晋级的策略都跑在自己独立的实时节点里,消费真实的交易所数据流:用实时报价撮合、真实价差、真实交易时段。策略之间不共享任何状态(这一课我们是踩坑学会的——共享节点会让一个策略的持仓查询串进另一个策略)。每一笔成交都连同触发它的信号值一起记录下来。
每天早上:重放并比对
一个每日任务会取出各策略的模拟盘时间窗,把完全相同的代码放进回测引擎、在同一区间用 catalog 数据跑一遍,然后把成交一对一匹配:方向一致、时间戳在容差内、数量一致。产出的不是相关系数,也不是"感觉挺像",而是一张表:已匹配、仅模拟盘、仅重放。健康的策略停在 55 笔里匹配 54 笔。不健康的那个,会明明白白告诉你哪一笔、在什么时候分了岔。
坏掉的东西,按它们坏掉的顺序
- 预热期成交。实时节点会喂历史数据给指标做预热——而那些自定义了 bar 回调的策略,就心安理得地拿它下单了。回测从来看不到这些幽灵入场。修复放在下单提交层,而不是逐个改策略,否则每个策略都会继承这个 bug。
- 多腿的顺序预热。一个横截面策略逐个品种做预热;每条腿看到的兄弟腿都是空的,锚点从未初始化。实盘里它一笔没做;重放里它跑得好好的。零匹配成交——但只有比对结果把这件事说出了口。
- tick 精度导致的崩溃循环。实时 aggTrade 逐笔数据的精度,和沙盒 instrument 预期的精度不一样。节点在一个断言上反复崩溃,而回测永远触发不了它,因为 catalog 里的 bar 早已被归一化过。
- 重启失忆。进程重启会对已经存在的持仓重新触发入场逻辑——对再平衡类策略来说,一次重启悄悄变成了一次再平衡。放到实盘,这就不是一致性报告里的一条脚注了;那是一面带着真金白银的红旗。
注意这里的规律:没有一个是信号逻辑的 bug。它们全都是执行边界上的 bug——预热、状态、精度、重启。而这恰恰是回测在结构上就看不见的那一类故障,因为在回测里,回测引擎本身就是那条执行边界。
我们刻意保留的那些不对称
这两个世界是故意不做成一模一样的。回测在 PnL 里计入建模的半价差加平方根冲击;模拟盘则在成交价里付掉真实价差,且不带冲击模型。把建模价差再加到模拟盘上(或者把真实价差加进回测里)都会重复计算。一致性指的是相同的决策、容差内相同的成交——而不是 PnL 一位不差。分得清哪些差异是有道理的、哪些是 bug,这门功夫占了整件事的大半。
回测是假设。模拟盘是实验。重放一致性核对,是那本会当场抓住你自欺的实验记录本。
如果你在跑策略,却没有按固定节奏把实盘行为和重放行为拿来比对,那么有一整类 bug 你从未见过。我们大约每两周就能逮到一个,而每一个,从回测内部看都是隐形的。
← 全部文章
