2026年九月21日 · 工程技术

在回测中途重启策略。它还记得自己持有什么吗?

在回测中途重启策略。它还记得自己持有什么吗?

一个实用的回测测试与寻找更好的参数毫无关系:在进程运行到一半时将其停止,恢复运行,再完成同一段回放。在输入完全相同、执行模拟器受控的情况下,它的决策、订单和权益应与不间断运行的结果一致。

如果两者不一致,你就发现了状态管理问题。策略依赖某些你没有保存、也无法重建的信息。每当研究任务需要续跑、工作进程被替换,或模拟交易服务部署新代码时,这种依赖都会带来影响。

我喜欢这个测试,因为预期结果格外明确。市场有没有变化,不存在争议。两次运行接收的是同一段行情。

下面是 3 种错误的重启方式。数字仅作说明;在其他方面完全确定的系统中,也可能发生这些故障。

1. 重新加载几根 K 线,就认为指标已经预热

假设策略使用 100 周期指数移动平均线。它的更新公式是:

alpha = 2 / 101
ema_next = alpha * close + (1 - alpha) * ema_previous

不间断运行的进程会延续累计的 EMA。重启后的进程获取 100 根 K 线,用第一根收盘价初始化 EMA,并认为 100 周期指标只需要 100 个观测值。

这种想法混淆了指标的平滑参数与有限记忆窗口。EMA 会保留初始状态逐渐衰减的影响。如果两个版本开始时的 EMA 相差 10 个价格单位,那么后续价格相同的情况下,这个差值会按如下方式缩小:

初始化以来的更新次数剩余差值初始误差占比
1001.35313.53%
2500.06740.674%
5000.0004540.00454%

计算公式是 10 * (99 / 101)^k。如果第一条观测值用作初始值,那么获取 100 根 K 线只会产生 99 次更新。

问题通常会在决策边界附近显现。一次运行认为价格高于 EMA,另一次则认为价格低于 EMA。微小的数值差异就会多出一笔交易。之后,冷却期、可用现金和后续决策也可能随之分岔。

保存递归指标状态、初始化状态以及最后处理的事件。或者,从一个已知的初始状态重新回放。延长预热时间可以得到可接受的近似值,但应根据明确的误差容限选择时长,并检查这个容限是否可能改变决策。“周期的 5 倍”只是一种惯例,并非证明。

而且,指标并不代表全部历史状态。滚动百分位数需要保存它的窗口。在线模型可能需要保存优化器状态。亏损后等待 3 根 K 线的规则,需要记住这次亏损和计数器。

2. 保存持仓,却忘了尚未完成的订单

你的目标持仓是 10 单位。一笔买入 10 单位的订单已成交 4 单位,还剩 6 单位待成交。你将持仓 4 单位写入检查点,重启后又提交一笔买入剩余 6 单位的订单。

如果原订单的剩余部分和新订单都成交,你最终会持有 16 单位。

这个问题在回测版本中往往不易察觉,因为重启成交引擎时,未完成订单会被悄悄清除。在模拟交易中,模拟器或外部服务可能会保留它们。这样一来,同一段恢复代码会因存活下来的组件不同而产生不同敞口。

重启时实际状态仅根据持仓恢复时看到的状态
目标持仓1010
已成交持仓44
未完成买入数量60
还需买入数量06

问题表现为恢复后立即出现一批来历不明的订单。有时敞口会翻倍;有时策略会平掉一个保护性订单仍在生效的持仓,导致该订单之后可能反而开出新仓。

检查点除了持仓,还需要记录订单标识和生命周期状态。恢复时,必须先将这些记录与执行系统对账,再生成新的操作。结果未知的订单需要调查;把“没有保存确认信息”当成“从未提交”,就会造成重复下单。

稳定的客户端订单标识有助于查询订单发生了什么。只有当接收系统确实执行了所需的唯一性或幂等规则时,它们才能避免重复。也要持久化已处理的成交标识,避免回放成交时重复增加持仓。

我对那种不起眼的订单状态页面情有独钟。到了重启那天,这些小小的表格行突然成了整栋楼里最吸引人的界面。

3. 恢复持仓,却重新开始一份 P&L 账本

来看一个不使用杠杆、没有手续费的现货示例。初始现金为 $10,000,以 $100 买入 10 单位,并在标记价格达到 $110 时创建检查点。

正确状态是 $9,000 现金加上价值 $1,100 的持仓:权益为 $10,100。如果恢复时保留了 10 单位持仓,却把现金重置为最初的 $10,000,系统就会报告 $11,100。只需重启一个进程,你就凭空变出了 $1,000。

其他情况没那么夸张。恢复时权益保持不变,但入场价被重置为 $110。总权益可能仍然正确,但已实现与未实现损益的归属发生了变化。如果止损或退出条件依赖入场价,这种简化记账就会改变交易行为。

也可能是系统忘了之前的权益高点。假设权益曾达到 $10,600,之后跌至 $10,100。回撤约为 4.72%。如果恢复时重置了历史最高值,策略会突然认为回撤为 0。任何基于回撤的风险控制都因此遭到了未经授权的重置。

因此,问题可能表现为权益突然跳变、回撤看起来异常改善,或部署后风险规则不再触发。应保留账本以及依赖记账的策略状态:现金变动、持仓、适用的成本基础、累计费用和风险控制记忆。在相同估值时间戳下,将恢复后的权益与账本进行对账。

检查点必须对应一致的状态边界。如果保存了成交后的现金,却保存了成交前的持仓数量,得到的状态就从未真实存在过。应将相关状态一起提交,或记录可用于重建状态的持久化事件序列。事件游标也要与该状态一并保存,这样恢复时既不会漏掉成交,也不会重复应用成交。

我会在研究测试框架中保留这样的测试:先运行一遍不间断的参考回放,再在刻意挑选的棘手时点重启第二次运行,包括指标初始化期间、部分成交之后,以及风险限额生效期间。使用相同的事件顺序,并保留模拟器的随机状态。对比恢复后的第一个决策、订单和成交记录,以及权益曲线。只比较最终余额,即使结果相同,也可能掩盖相互抵消的错误。

对于提交订单后、收到确认前发生崩溃的情况,测试框架还需要独立于策略进程保存执行服务的状态。否则,你就抹掉了自己想要测试的不确定性。

策略的规格也包括它会记住什么。要把这些记忆明确记录下来,这样你就能在回放中途终止进程,并准确说明它如何恢复运行。

策略状态回测检查点恢复模拟交易
← 全部文章