NOTE · 技术笔记

滚动回测的口径:为什么我坚持「全期切片」而不是「窗口重跑」

先给你看我现在系统里两个"滚动表现"产物的真实数字(同一条策略,2026-10-08 生成):

  • 近 20 个交易日(2026-09-03 ~ 10-08):收益 −5.502788%、最大回撤 −11.211599%、40 笔、胜率 45.0%;
  • 近 60 个交易日(2026-07-09 ~ 10-08):收益 −3.873406%、最大回撤 −25.930672%、108 笔、胜率 45.37%。

注意 60 日回撤 比 20 日还深一倍多(−25.93% vs −11.21%)——这说明回撤是"从历史水位上刻下来的",不是本窗口自己长出来的。这篇就讲这个被大多数人忽略的细节,以及我为此栽过的跟头。

事件的起点:和官方数据对不上

我的系统有一套"滚动表现"统计(给盯盘和复盘页用)。有天我把它的数字与一套官方回放数据对账:同一策略、同一 20 日窗口,收益和回撤都对不上,回撤差距尤其大。

第一反应是查 bug。两边各自重算、逐日核对、逐笔核对——单独看每一方都自洽。最后发现:根本不是 bug,是口径定义不同。

两种口径,差在"基期"

把两种算法摊开(伪代码级):

# 口径 A:窗口独立重跑(错误示范)
window = bars[start:end]                    # 只取窗口内的数据
nav = run_backtest(window, initial_cash=1)  # 资金重置、持仓清零,从零开始
ret_20d = nav[-1] / nav[0] - 1              # 以窗口第一天为基期

# 口径 B:全期序列切片(正确口径)
nav_full = run_backtest(bars[:end], ...)    # 从更早(我的实现是全历史)连续跑完整序列
segment = nav_full[start_idx: end_idx + 1]  # 切片取窗口段
ret_20d = nav_full[end_idx] / nav_full[start_idx - 1] - 1  # 基期 = 窗口首日的前一交易日

差别一句话:口径 A 假设"20 天前我干干净净空仓开始";口径 B 承认"20 天前我手里就拿着仓位和浮盈浮亏"。真实账户当然不会每 20 天清零一次——所以官方用的是口径 B,我的系统最初是口径 A。

为什么回撤差异最大

收益的差异还可能温和,回撤会直接夸张化:

  • 口径 B 的"历史最高水位"可能是窗口之前就形成的——如果之前涨上去过,窗口内的回撤要从那个历史高点算起;
  • 口径 A 的窗口从零开始,最高水位只能是窗口内的——"忘记"了历史。

到我这条数据上:60 日口径 B 的回撤 −25.93%,本质是"从更早的高点刻下来的"——这个数字对持仓者才有意义(他真的经历过那一段)。而口径 A 算出来的会小得多,给人"最近还行"的错觉。

修复与验收(可复现)

1. 对齐生产口径:滚动统计全部改为"全期序列尾部切片",基期定义为窗口首日的前一交易日的收盘状态;

2. 让口径随身走——每个滚动产物带上锚点字段(这是我的产物真实结构):

{
  "window_days": 20,
  "start": "2026-09-03", "end": "2026-10-08",
  "base_date": "2026-09-02",
  "anchors": {"method": "full_series_slice", "full_start": "2026-01-05"}
}

以后任何人(包括半年后的我)看到数字,都能当场知道它怎么算的、基期在哪、全序列从哪开始。

3. 验收用逐值对账,不用"差不多":修完后与官方回放数据比对——20 日项命中到 −5.5028% / −11.2116%、60 日项命中到 −3.8734% / −25.9307%(小数点后四位全等),才算结案。对账断言只有一条:逐值相等;不搞"误差小于 x% 就通过"。

可复制的检查清单

1. 任何"近 N 日"指标,先把基期写下来(窗口首日 还是 首日前一交易日?);

2. 回撤是高水位刻出来的——先定义"高水位从哪开始追溯";

3. 自研指标与外部对不上时,先怀疑口径、再怀疑 bug——口径差异是静默的,bug 通常会喊两声;

4. 每个统计产物带上计算口径锚点字段(方法名 + 基期日期),这是给未来的自己留的说明书;

5. 验收方式用逐值对账(能到几位就到几位),不用"看起来接近"。


本站内容为个人研究记录与方法分享,模拟,不构成投资建议;据此操作风险自负。


Joyous 的技术笔记 · 沪ICP备2026053770号


隐私最小化:不设 cookie、无表单、无第三方脚本;访问统计仅取服务端日志(≤30 天,仅聚合),不导出 IP。