你跑了一次回测,夏普 1.8,最大回撤 12%,曲线漂亮。

你上手做,三个月后曲线不长那样。

问题不在于"回测会失真"这句老生常谈——问题在于你把回测的输出当成了一次测量。它不是。它是一次抽样,而你只抽了一次,还是从你自己挑出来的那段历史里抽的。

前面十几篇反复出现同一句话:这个需要检验。趋势系统需要多少样本(第 30 篇)、加一个过滤器要付出什么代价(第 32 篇)、缠论规格化之后该怎么测(第 37 篇)——但「检验」这件事本身,手册一直没有正面写过。

这一篇补上它。

本系列仅用于记录与普及基础知识,不构成投资建议。这一篇全部是统计与工程方法,不涉及任何具体策略、标的或买卖规则。

零、和第 7 篇的分工

第 7 篇第九、十节已经给了一份很实在的清单:五个步骤 + 六个陷阱。那一份不重复。

两者的区别是:

第 7 篇 这一篇
形式 清单——别做什么 流水线——按什么顺序做
判据 定性为主 给数字和停止规则
覆盖 点名了 Walk-Forward、紧缩夏普 把它们展开成可执行规格

打个比方

清单像一份体检项目表,流水线像急诊分诊

体检表告诉你有哪些指标要查,但不排顺序——反正都要查一遍。分诊不一样:它决定先看什么,因为有些结果一出来就该停,后面的都不用做了。

⚠️ 清单和流水线不是同一个东西。 清单告诉你有六个坑,但不告诉你先跳哪个——而顺序恰恰决定了你会不会在还有救的时候就停下来

这一篇的价值大半在顺序上:把最便宜、淘汰率最高的检查放在最前面,可以省掉后面全部的工作量,也省掉你在一个注定不成立的想法上继续投入感情。

一、⭐ 先接受一件事:回测的输出不是一个数

一次回测给你一条净值曲线、一个夏普、一个最大回撤。这些数字看起来是测量结果,但它们不是。

它们是一次抽样。

同一个策略、同一套规则,如果历史沿着另一条同样合理的路径走过一遍,你会得到一组不同的数字。你看到的那一组,是从一个分布里抽出来的一个点。

这件事的后果是具体的:

  • 「夏普 1.2」这句话没有信息量——你需要的是「夏普 1.2,95% 区间 [0.3, 2.1]」
  • 「最大回撤 18%」是这条路径上的最大回撤,不是这个策略的最大回撤
  • ⚠️ 而且你的参数正是在这条路径上挑的,所以这个 18% 系统性偏乐观

整篇的方法论都建立在这一句上:把点估计换成分布。 第五节给具体做法。

二、⭐ 检验的顺序,以及顺序为什么不能换

⭐ 这就是分诊:排在前面的不是最重要的检查,是"一旦结果不对,后面全都不用做了"的那些。 顺序换了不会得出错误结论,只会让你在一个注定不成立的想法上多花几周。

九道关卡,按顺序过,不通过就停

# 关卡 消耗样本吗 淘汰率
0 可实现性:数据当时存在吗、这个价位成交得了吗 ❌ 不消耗
1 成本关:毛收益够不够付往返成本 ❌ 不消耗 最高
2 样本量关:数据量够不够检验这个量级的边缘 ❌ 不消耗
3 样本内表现 ✅ 消耗
4 参数稳健性 ✅ 消耗
5 步进 / 样本外 一次性
6 多重比较修正
7 相关性去重 常被跳过
8 小规模实盘 真金白银

排序原理和第 32 篇的七道关卡完全一样:先跑便宜的、淘汰率高的、且不消耗数据的。

前三关全都是纯算术:不需要跑回测,不消耗样本外数据,几分钟就能算完,而且它们合起来能淘汰掉大部分想法。在这三关之前就去写回测代码,是把最贵的资源花在最先该被否决的东西上。

⚠️ 必须是布尔判据,不能打分加权。 「成本关差一点,但样本内表现特别好,综合看还行」——这句话在数学上没有意义:成本是确定要付的,样本内表现是估计出来的,用一个估计量去抵消一个确定量,方向就是错的。(同第 26 篇第 3 层的理由。)

三、成本关为什么必须排第一

三个理由,一个比一个硬:

  1. 它不消耗任何样本——纯算术,算错了也不污染数据
  2. 它的淘汰率最高——第 31 篇那张费率表,短周期上大部分格子是 ❌
  3. 它是确定量——成本是你一定会付的,而收益是估计的

一个可以直接算的判据

先用零成本跑一遍回测,得到每笔平均毛收益 g(以标的价格的百分比计),再取往返成本 c

g < c        ❌ 直接死,不用继续
c ≤ g < 2c   ⚠️ 净期望不到毛期望的一半
             ——此时结论由「成本估得准不准」主导,而不是由策略主导
g ≥ 3c       🟢 可以进入下一关

⚠️ 中间那档是最危险的,因为它看起来「还能做」。但成本估计本身的误差(滑点分布、极端时段的价差扩大、部分成交)在那一档足以让结论翻转——你测的其实是你的成本模型,不是你的策略。

⭐ 一个第 7 篇没有的推论:成本会放大样本量需求

第 7 篇给的样本量公式是:

t = (μ ÷ σ) × √N        要 t = 2  ⇒  N = 4σ² ÷ μ²

关键在于 N 和 μ 是平方反比关系。而你真正要检验的不是「毛收益 > 0」,是**「扣完成本还 > 0」**:

μ_净 = μ_毛 − 成本

于是:

成本吃掉一半毛收益 ⇒ 需要的样本量变成 4 倍。 成本吃掉三分之二 ⇒ 变成 9 倍。

成本占毛收益 μ 剩下 所需样本量
0% 100% ×1(基准)
25% 75% ×1.8
50% 50% ×4
67% 33% ×9
75% 25% ×16

⚠️ 这条推论和第 31 篇的结论叠加起来很难受:短周期成本占比高,所以既更难赚,又更难证明自己赚了。 它唯一的补偿是笔数积累得快——但那个优势的一部分,正好被这里的倍数吃掉。

四、样本量关:不够就别测

第 7 篇的两张表(每笔 R × 标准差、年化夏普 × 年数)已经很完整,不重复。这里只补一条常被跳过的检查:

先算你手上的数据在这个周期上到底有多少笔,再决定要不要测。

趋势系统,单品种日线,一年 10–20 笔
→ 10 年数据 = 100–200 笔
→ 按第 7 篇的表,平均 +0.20R / 标准差 1.0R 需要 100 笔

结论:勉强够,且只够检验「相当强」的边缘

⚠️ 如果算出来「需要 900 笔,我有 150 笔」,正确的动作是不要测——不是「先测了看看」。因为一旦你看过这份数据,它就不再是干净的样本外数据了(第六节)。

这一关是免费的,而且它能在你写任何代码之前就否决掉一个想法。

五、⭐ Bootstrap:把单条曲线变成分布

这是第 7 篇没有的部分,也是第一节那句话的具体做法。

做法

原始数据:策略的 N 笔交易收益序列(或每日收益序列)

重复 B 次(B = 1000 或 10000):
    从原序列中有放回地抽样,抽出同样长度的一条新序列
    在这条新序列上算:夏普、总收益、最大回撤

输出:这三个指标各自的分布
      取 2.5% 和 97.5% 分位 → 95% 区间

⚠️ 但直接这样做是错的,因为它打乱了顺序,而金融收益序列有自相关(波动率聚集,见第 35 篇)。

⭐ 必须用分块 bootstrap(Block Bootstrap)

不抽单个点,抽连续的块

块长 L:取能覆盖你关心的自相关尺度
        经验起点 L ≈ N^(1/3),日频数据常用 5–20 天
抽样:随机选起点,取连续 L 个点,拼接到长度 N

块长的作用很直接:L = 1 退化成普通 bootstrap(毁掉所有时间结构);L = N 就是原序列(什么也没做)。 中间才有意义。

⚠️ 块长本身是一个可调参数,所以要报告 L 在合理范围内变动时区间是否稳定——如果换个块长结论就变,说明结论依赖的是块长,不是策略。(这是第 37 篇跨定义检验的同一个思路。)

它能给你三样东西

输出 用途
夏普的 95% 区间 ⭐ 区间下界跨过 0 ⇒ 你没有证据
最大回撤的分布 见下,这个最有用
总收益的区间 对预期做校准

最大回撤那一项值得单独说。 回测给你的是一条路径上的 MDD。跑 bootstrap 会发现分布的中位数通常明显大于它——因为参数就是在那条路径上选的。

仓位应该按 MDD 分布的高分位(比如 90%)来定,不是按回测里看到的那一个。

这条直接接到第 6 篇用回测 MDD 反推杠杆,是在用一个系统性偏小的数当分母。

Bootstrap 不能修的东西

  • 它假设未来和过去同分布——市场结构变了它不知道
  • 不能修复过拟合:如果你的策略是拟合出来的,bootstrap 会忠实地给出一个漂亮的区间
  • 不能修复前视偏差——垃圾进,精确的垃圾出

Bootstrap 量化的是「运气的宽度」,不是「逻辑的对错」。

六、步进检验的具体规格

第 7 篇提了一句「前进式检验(Walk-Forward)」,这里给规格。

两种模式

滚动(Rolling):训练窗固定长度,整体向前滑
    [训练 24m][测试 6m]
              [训练 24m][测试 6m]
                        [训练 24m][测试 6m]

锚定(Anchored):训练窗起点固定,只往右扩
    [训练 24m][测试 6m]
    [训练 30m       ][测试 6m]
    [训练 36m             ][测试 6m]

滚动假设市场结构会变(近期数据更相关);锚定假设结构稳定(数据越多越好)。⭐ 这个选择本身是一个假设,应该写下来,而不是默认。

三个参数与一个约束

参数 怎么定
训练窗 至少要装得下第四节算出的最小样本量
测试窗 短到能捕捉结构变化,长到有统计意义
步长 通常 = 测试窗(不重叠)

⚠️ 约束:段数太少,步进本身就没有统计意义。 3 段测试窗给你 3 个数——从 3 个数里看不出一致性。 起码要 10 段以上才值得读。

⭐ 真正该看的不是总收益

把所有测试段拼起来算一条总净值,是最常见的用法,也是浪费。步进真正的产出是段与段之间的一致性

现象 含义
各段都小赚 🟢 最好的结果
少数几段贡献了全部收益 ⚠️ 可能是几次运气,不是系统
每段选出的最优参数差异巨大 参数没有稳定含义 ⇒ 拟合噪声

最后一行是步进最有价值的输出,而它在「总收益」那条曲线上完全看不见。

随机 K 折在时序数据上是错的

顺带说明一个在机器学习里极常见的错误:随机 K 折交叉验证是几乎所有教程的默认选项,但在时间序列上——

它等于用未来训练、用过去测试。

时序数据上正确的做法就是本节的步进检验(第 47 篇第六节还列了另外两种 ML 特有的前视偏差)。

步进也会被过拟合

如果你调整训练窗长度、测试窗长度、或者滚动/锚定的选择,直到步进结果好看为止——那么步进就变成了样本内。

判断标准很简单:你在看到步进结果之后,改过步进的设置吗? 改过,它就不再是样本外检验了。

七、⭐ 多重比较:把「试了多少次」变成数字

第 7 篇点名了紧缩夏普比率,这里给出它背后的直觉和一张能吓住人的表。

你试的次数远比你以为的多

「我只测了三个策略」——通常不是真的。下面每一项都算一次试验

参数网格 20 × 20 × 5              = 2,000 次
在 5 个品种上分别看               ×5
换了 3 个周期                     ×3
加了 / 去掉了一个过滤器           ×2

这就已经是 60,000 次了。 而且还没算你在看图时凭直觉排除掉的那些方向——⚠️ 那些也算,因为它们同样消耗了这份数据。

纯运气能给出多高的夏普

假设每一个策略的真实期望收益都是零,在 T 年数据上估计出的夏普标准误约 1/√T。试 N 次,取最好的那个:

5 年数据(标准误 ≈ 0.45)为例:

试过的次数 N 最好那个的夏普(期望)
10 0.70
100 1.13
1,000 1.46
10,000 1.73

在 5 年数据上试 1000 个参数组合,最好的那个夏普 1.46——而它的真实期望收益是零。

⚠️ 而 1000 个组合,就是上面那个 20×20×5 网格的一半。一次常规的参数扫描,就足以凭空造出一个看起来很专业的夏普。

怎么办

  1. 记一个尝试计数器——在开始之前建一个文件,每跑一次记一行。事后回忆一定低估
  2. 把候选数量压在前三关(成本、样本量、可实现性都不消耗数据)
  3. 报告的时候带上 N:「夏普 1.5」和「试了 2000 次之后最好的夏普 1.5」是两句完全不同的话
  4. 紧缩夏普比率就是把上表那个基准从你的观察值里扣掉——它的含义就是「减去运气能达到的水平」

这条和第 32 篇第六节的过滤器判据是同一个东西的两面:那里说加过滤器要付 √(N原/N后) 的代价,这里说多试一次要付 √(2 ln N) 的代价。两处都是「你动手了,就要付费」。

八、⭐ 三个策略往往不是三个策略

这是全手册之前完全没有出现过的一块,而它的后果很大。

假设你有 10 个策略,每个夏普 0.5。组合起来夏普是多少?

如果它们互相独立0.5 × √10 = 1.58

但它们通常不独立。有效独立策略数

N_有效 = N ÷ (1 + (N−1) × ρ)          ρ = 平均两两相关系数
平均相关 ρ 10 个策略的有效数 组合夏普
0.0 10.0 1.58
0.3 2.70 0.82
0.5 1.82 0.67
0.7 1.37 0.59
0.9 1.10 0.52

平均相关 0.5 时,10 个策略的分散效果只相当于 1.8 个。

⚠️ 而这正是第 27 篇那个结论的量化版:

  • 「均线金叉 + MACD 金叉 + RSI 上穿」——三个信号,ρ 接近 1,N_有效 ≈ 1
  • 「多周期共振」——同一份数据看两遍,ρ 接近 1
  • 只用收盘价的信号,彼此在信息上等价,所以它们的 ρ 天然就高

具体该做什么

① 把每个策略的日收益序列拉出来
② 算两两相关系数矩阵
③ 算 ρ 均值,代入上面的公式
④ ρ > 0.7 的一对:⚠️ 当成一个策略看待

顺序上这一关排在多重比较之后,因为它常常把「我有 10 个策略」变成「我其实有 2 个」——而那会反过来改变第七节里 N 的含义。

九、⚠️ 回测引擎本身是否正确(工程问题,不是统计问题)

前面八节全部建立在一个前提上:回测算出来的数是对的。这个前提经常不成立,而且失败是静默的——引擎不会报错,它只会给你一个偏乐观的数字。

检查清单

典型错误
K 线内顺序 同一根既触止损又创新高,默认假设几乎总是偏乐观(第 30 篇
成交假设 用收盘价成交,但信号是收盘后才算出来的(第 32 篇前视偏差)
复权 美股分红拆股;用未复权价算收益率会凭空造出跳空
point-in-time 标的池 用今天的列表回测过去(第 26 篇
时区与时间戳 交易所时间 / UTC / 本地时间混用,误差恰好一根 K 线
资金占用 同时开 10 个仓的回测,没检查过账户是否真的够保证金
数据修正 宏观数据、财报都会被事后修正,历史库里存的常是修正后的值

⭐ 一个能抓住大部分引擎 bug 的自检

跑一个必然亏损的策略。

规则:随机进场、随机出场,频次和你的真实策略一样
预期结果:净收益 ≈ −(往返成本 × 交易次数)

如果引擎显示它接近零或者赚钱,引擎有 bug。 最常见的两个原因就是成本没算进去、或者存在前视偏差。

这个测试的价值在于它有一个已知答案。 你所有其他的回测都没有已知答案——所以它们无法验证引擎,只有这个可以。

十、实盘偏离:最后一道,也是唯一真实的一道

第 7 篇第五步说要小规模实盘并「测量回测与现实的差距」。这里给指标。

每一笔都记两个价

信号触发时的可见价(回测会用的那个)  →  P_理论
实际成交均价                          →  P_实际

单笔偏离 = |P_实际 − P_理论| ÷ P_理论

累计之后和回测里假设的成本比:

实际往返成本 ÷ 回测假设成本 = 偏离倍数
偏离倍数
< 1.2 🟢 成本模型可用
1.2 – 2.0 ⚠️ 回到第三节重算成本关——很多策略在这里就死了
> 2.0 ❌ 停止,成本模型是错的

停止规则要在开始实盘之前写下来,理由和第 7 篇第四步一样:事后你总能给偏离找到解释。

⚠️ 而且偏离会随规模增长——你用 1/20 的仓位测出来的滑点,不能线性外推到全仓(第 15 篇第五节:持久冲击约按 √(Q/V) 走,拆单的收益是递减的)。

十一、能回答和不能回答什么

问题
这个想法值不值得写回测代码 ✅ 前三关,纯算术,几分钟
我的夏普有多不确定 ✅ 分块 bootstrap 给区间
仓位该按多大的回撤来定 ✅ MDD 分布的高分位,不是回测那一个
我试了这么多次,多少是运气 ✅ 第七节的表直接查
我这 10 个策略真的分散吗 ✅ 相关矩阵 + 有效独立数
我的回测引擎有没有 bug ✅ 跑随机策略,它有已知答案
这个策略未来能赚多少 检验只能否决,不能承诺
样本外失败了,能不能改改再试 ❌ 能,但那份数据已经作废了
回测夏普 2.0 是不是很好 不知道 N 就无法回答
市场结构变了怎么办 ❌ 所有方法都假设同分布,这一条修不了

十二、这一篇的结论

  • 回测的输出不是一个数,是一次抽样。 「夏普 1.2」没有信息量,「夏普 1.2,95% 区间 [0.3, 2.1]」才有。整篇方法论就是把点估计换成分布。
  • 顺序比清单重要:前三关(可实现性、成本、样本量)全是纯算术、不消耗数据、淘汰率最高。在它们之前写回测代码,是把最贵的资源花在最先该被否决的东西上。
  • ⚠️ 每一关必须是布尔判据。 用「样本内表现好」去抵消「成本关不过」,是拿一个估计量抵消一个确定量——方向就错了。
  • 成本会放大样本量需求,而且是平方关系:成本吃掉一半毛收益 ⇒ 所需样本量 ×4;吃掉三分之二 ⇒ ×9。⚠️ 短周期因此双重受罚:既更难赚,也更难证明自己赚了。
  • 分块 bootstrap 是必需的,不是可选的——普通 bootstrap 会毁掉波动率聚集。而块长本身是参数:换个块长结论就变,说明结论依赖块长。
  • 最有实操价值的一个输出是最大回撤的分布:回测里那一个是在你选参数的那条路径上取的,系统性偏小。仓位该按分布的高分位定。
  • 步进真正的产出不是总收益,是各段最优参数的一致性。 每段选出的参数差异巨大 ⇒ 参数没有稳定含义 ⇒ 拟合的是噪声。而看到步进结果之后再改步进设置,它就不再是样本外了。
  • 一次常规参数扫描就足以凭空造出一个专业级夏普:5 年数据、1000 次试验,纯运气的最好夏普是 1.46。所以「夏普 1.5」和「试了 2000 次之后的夏普 1.5」是两句完全不同的话——报告时必须带上 N。
  • 三个策略往往不是三个策略:平均相关 0.5 时,10 个策略的有效独立数只有 1.8。而只用收盘价的信号,ρ 天然就高——这是第 27 篇「同一个观测数了三遍」的量化版。
  • 回测引擎该用一个有已知答案的测试来验:跑随机进出、频次相同的策略,结果必须约等于「−成本×次数」。显示接近零或赚钱 ⇒ 引擎有 bug。你其他所有回测都没有已知答案,所以只有这一个能验证引擎。
  • 实盘偏离要有停止规则,且写在开始之前。 实际成本超过回测假设 2 倍 ⇒ 停。而且偏离随规模增长,小仓位测出的滑点不能线性外推。
  • 最后一条,也是最容易被忘的:这一整套只能否决,不能承诺。全部通过的策略,仍然可能在实盘失败——检验的作用是让你少做几件蠢事,不是给你一张保单。