2026年十一月1日 · 研究

如何给回测预热,才不会把回看窗口算进业绩?

如何给回测预热,才不会把回看窗口算进业绩?

规格里的 20 日 Donchian 看起来无害。智能体写了一个 20 根 K 线的滚动最高,回测从 parquet 文件的第一行开始,前两周所谓的「20 日高点」其实只是已有 K 线的累计最高。到第 4 天,它就是一个 4 日高点。你以为自己在测的策略,并不是打出前十几笔成交的那个。

我有一份 ETHUSDT 日线 notebook,全样本 Sharpe 是 1.4。切掉前 20 个交易日,掉到 0.6。只留这 20 个、丢掉第一年剩下的部分,打出 3.1。没有人泄露明天。策略用了过去,只是用得不够,报告却把这些半成品交易当成了证据。

1.4全样本 Sharpe,含未完成窗口
0.6同样的代码,过完 20 个完整交易日之后
3.1只保留预热期交易时的 Sharpe

让窗口还在填充时就开始交易

常见翻车是 min_periods=1。只要你允许,Pandas 会在单根 K 线上就算滚动均值。智能体经常这么干,因为数据框顶部的 NaN 看起来像 bug,填上它,单元测试就变绿。

20 日已实现波动率目标如果只有两个收益观测,就是仓位爆炸。两个数的方差是抛硬币,你再取倒数,第一笔仓位就是目标风险的好几倍。用接近零的标准差做 z-score 是同一套把戏,只是带了符号:策略看起来有一把锋利入场,窗口一旦诚实,就再也没出现过。

突破更糟,因为看起来像真规则。第 6 根 K 线上的 20 根通道其实是 6 根通道。一开始就会扎堆成交,常常发生在一段平静行情里,完整指标根本不会触发。平静市场会慢慢漂移。收紧的通道会开火。权益曲线拿到一段后面任何一年都不会再重复的抢跑优势。

用 0 填充和向前填充是另一种谎言。RSI 为 0 会被读成超卖。缺失的 ATR 变成 0 会把单位仓位炸掉,回测会尽职尽责地买下一整座仓库。我宁愿让 NaN 杀掉订单,也不愿让 0 凭空造出一笔。

我还是会不自觉地从第 0 行开始画图,因为图表左侧空出一个月看起来像没画完。那片空白才是诚实的部分。

裁掉权益曲线,留下仓位

这一条能过代码审查。你正确地计算了 20 日特征,从绘制的曲线里丢掉前 20 行,从 1 月 21 日开始发表。1 月 21 日的仓位是 1 月 14 日开的。移动止损的高水位、金字塔加仓计数、那些预热烧掉之后的现金:全都走进了「干净」样本。

后果是一份没法在文中那个日期启动的模拟策略。从空仓在报告的起始日重启,第一周的订单就对不上。有时回测已经在一笔交易里,而模拟系统没有理由入场。有时模拟系统入场是因为回看窗口是冷的,回测在空仓观望,因为隐藏的那一个月已经吃过这次突破。两本开仓账,一个公开发表的起始日。

Walk-forward 的每一折会悄悄这么做。每个测试窗口都有一段你不画出来的回看尾巴。跳过模拟那段尾巴,测试窗口的第一批交易又是未完成窗口的交易。模拟它、只报告测试日,则测试日的仓位取决于你没展示的成交。第二种做法可以是正当的研究选择。把这一折的第一个早晨叫做冷启动,而移动止损已经从尾巴里活着进来,这就是两个研究员从同一套配置算出两个 Sharpe 的方式。

如果在公开发表的起始日从空仓重启会改变 P&L,预热就进了业绩记录。回看数据可以在。回看交易不能在。

特征不是「日」的时候,却按日历日预热

「给它 30 天」让特征定义不足。5 分钟 Donchian 上的 30 个日历日,比 20 周期回看多出几千根 K 线。需要 20 个交易日的日线特征,给 30 天,是肥是瘦取决于周末和空洞。

我们某家永续合约交易所上的资金费率 z-score 被给了 30 天预热,因为规格写的是 30 个周期,有人把周期翻译成了日历。那边的资金费每 8 小时打印一次。30 天大约是 90 次结算。30 个周期是 10 天。多出来的两个月预热恰好包含了一周脱锚。丢掉这段让实盘窗口看起来比策略的真实历史更平静。人们怪起始日敏感性。预热把那个市场状态吃掉了。

而加密货币对比股票,会让同一个数字在没人改参数的情况下换掉含义。ETH 的 20 个「日」包含周六。股票的 20 个交易日几乎是一个日历月。把股票 notebook 里的 20 日回看复制到小时永续 K 线上,你得到的是名叫 20 日、实为 20 小时的通道。第一根有效 K 线于是要么是 20 小时之后,要么是 20 天之后(如果后面某个函数做了重采样),我在同一次智能体 diff 里两种都见过。

钉死首个可交易时间戳

预热是数据要求,不是额外样本。每个特征都要写明自己需要多少个已完成观测,单位就是它的计算单位。首个可交易时间戳,是所有这些条件都成立、且空仓账户会被允许发单的第一根 K 线。

特征规格通常怎么写实际必须存在什么静默替代
20 日 Donchian20 个日高点20 个完整交易日min_periods=1 的滚动最高
30 日已实现波动率30 日窗口30 个收盘到收盘收益2 日方差,取倒数变成仓位
20 次打印的资金费率 z-score20 天20 次资金费结算20 个日历日(~90 次打印),或错误时间框架上的 20 根 K 线
5 分钟 14 周期 RSI14 个周期14 根已收盘的 5 分钟 K 线14 天,或把缺口向前填充后的 14 根 K 线

该时间戳之前的 P&L 不进业绩记录。该时间戳之前的状态可能仍需要模拟,这样时钟走到首根可交易 K 线时,移动止损或冷却期是真实的,但那些成交不进 Sharpe、不进成交笔数、也不进容量计算。然后跑那个难看的检查:在该时间戳从空仓重启,回看只作为数据可用。如果订单变了,你就是在交付隐藏交易。

模拟交易按同一份约定启动。每个 walk-forward 测试窗口也一样。如果图表看起来缺了一个月,就让它缺着。

回测数据工程walk-forwardAI 智能体模拟交易
← 全部文章