多重检验与数据挖掘偏差
P2 全章 49 次检验的总账,以及 p0.1 那个「最优参数」的数学判决
从 p2.1 开始,我一边做检验一边记账。现在把账本摊开:
| 节 | 检验次数 |
|---|---|
| p2.1 MACD 金叉 | 6 |
| p2.2 三条口诀 | 18 |
| p2.3 布林带两个方向 | 8 |
| p2.4 三组量价条件 | 12 |
| p2.5 共振五种组合 | 5 |
| 合计 | 49 |
在纯随机数据上做 49 次检验,会"发现"多少显著?实测:平均 2.45 个,至少出现 1 个的概率是 92.2%,最多的一次出现了 8 个。
这一节做四件事:算随机基线、校正 P2、量化参数搜索的代价,最后回到 p0.1——当时那个夏普 1.28 的"最优参数",低于它自己的噪声门槛 1.55。
我们当时是用样本外结果证明它是噪声的。这一节告诉你:用统计理论,事先就能判定。
一个人连续三次猜中硬币正面,你会觉得他有点东西。
但如果房间里有 1000 个人同时在猜,必然有大约 125 个人连中三次。
从这 125 个人里挑出一个,给他一个"预测大师"的头衔,再让他继续猜——他会回到 50%。
P2 的 49 次检验、p3.3 的 1035 对协整、p0.1 的 591 组参数,都是那个房间。
一、随机基线:49 次检验能"发现"多少
2000 次模拟,每次做 49 个完全无效应的检验(零假设为真时 p 值服从均匀分布):
平均「显著」个数 2.45(理论期望 2.45)
至少 1 个显著的概率 92.2%
至少 3 个显著的概率 44.8%
至少 5 个显著的概率 9.4%
最多的一次出现了 8 个显著
做 49 次检验,"至少出现几个显著"几乎是必然事件。
所以「我找到了一个
p<0.05的信号」这句话,单独说出来时信息量约等于零。它必须和"我一共试了多少次"一起出现,才有意义。
二、校正 P2:35 个显著,还剩 21 个
把 P2 各节实际报告的 49 个 p 值放在一起校正:
检验总数 49 次
未校正 p<0.05 35 个显著
Bonferroni 校正后 21 个显著(阈值 p < 0.00102)
BH-FDR 校正后 33 个显著
被 Bonferroni 淘汰的 14 个(未校正显著、校正后不显著):
| 结论 | 原 p 值 |
|---|---|
| p2.1 金叉 1 日 | 0.0027 |
| p2.1 金叉 3 日 | 0.0120 |
p2.2 RSI>70 60 日 | 0.0272 |
p2.2 RSI<30 5/10/20 日 | 0.0400 / 0.0069 / 0.0025 |
p2.2 J<0 1/3 日 | 0.0035 / 0.0351 |
| p2.3 上轨 10 日 | 0.0017 |
| p2.3 下轨 10 日 | 0.0053 |
| p2.4 OBV 背离 5 日 | 0.0017 |
| p2.4 缩量 5 日 | 0.0267 |
| p2.5 ② / ③ | 0.0239 / 0.0015 |
p2.1 那两颗星没扛住。当时我在课里说"这 2 次显著是真的吗?单看 p 值回答不了"——现在有答案了:扛不住校正。
经 Bonferroni 仍然显著的 21 个(最硬的结论):
- p2.2 RSI 上穿 70:1/3/5/10/20 日全部存活(p 最小到 0.00001)
- p2.3 布林上轨:1/5/20 日存活;下轨 1/5 日存活
- p2.4 OBV 顶背离:1/10/20 日存活;放量上涨 1/5/10/20 日全部存活;缩量 1 日存活
- p2.5 ①、①+②、①+②+③ 存活
口径说明:这里的存活判定用的是 P2 各节的原始 p 值(未叠加 Newey-West 重叠样本修正)。若先按 p3.2 的做法用 Newey-West 修正后的 p 值再做多重检验,存活集合会更小——例如 OBV 顶背离 20 日(原始 t=4.24、NW 后 2.35)会掉出去。两节口径不同,结论方向一致:重叠越重,可信的越少。
Bonferroni 和 BH-FDR 差了 12 个,该信哪个?
- Bonferroni 控制的是"任何一个假阳性的概率",极其保守。做 49 次检验,阈值降到 0.05/49 = 0.00102
- BH-FDR 控制的是"错误发现的比例",在 49 个里允许约 5% 是错的
用哪个取决于代价:
- 如果一个假阳性会让你投入真金白银(比如据此上线一个策略)→ 用 Bonferroni
- 如果这只是研究阶段的筛选、后面还有样本外和实盘小仓验证 → 用 BH-FDR,别把真信号也筛掉
本课的立场:进入 P5 策略开发的,必须过 Bonferroni。因为 P5 之后就是真金白银了。
🔧 动手做:49 个纯噪声检验,平均蒙出几个"显著"?(5 分钟)
跑 python multiple_testing.py。它模拟 2000 次,每次做 49 个完全无效应的检验(对应 P2 全章的检验次数)。
你会看到(实测):每轮平均出现 2.45 个"显著"(纯属运气),至少 1 个显著的概率 92.2%、至少 5 个 9.4%。而 P2 全章 35 个"显著"里,Bonferroni 校正后只剩 21 个。
想明白:显著性会被比较次数吃掉。你检验/搜索得越多,"最优"就越漂亮——但那多半是噪声的最大值(和 p5.4 的参数尖峰是同一件事)。报任何"显著"结果,必须同时报你一共试了多少次,不然那个 p 值毫无意义。
三、参数搜索的代价:搜得越多,"最优"越漂亮
在真实夏普为 0 的纯随机数据上,每"组参数"= 一个与收益无关的随机持仓序列,取其中最优:
| 搜索组数 | 最优夏普(均值) | P95 | 最优夏普>1 的概率 |
|---|---|---|---|
| 1 | -0.01 | 0.51 | 0.0% |
| 10 | 0.35 | 0.78 | 0.3% |
| 50 | 0.51 | 0.92 | 2.0% |
| 200 | 0.60 | 1.00 | 5.3% |
| 1000 | 0.73 | 1.13 | 14.0% |
真实夏普是 0。但只要你肯搜,最优夏普就会稳步上升——搜 1000 组,有 14% 的概率能得到一个夏普 > 1 的"策略"。
"我回测出夏普 1.5"这句话,不说搜索次数就没有意义。
四、通缩夏普比率:把搜索次数折算成门槛
Bailey 与 López de Prado 给出了这个折算:搜索 N 次后,即使全是噪声,最大夏普的期望约为
E[max SR] ≈ σ_SR × [(1-γ)Φ⁻¹(1-1/N) + γΦ⁻¹(1-1/(N·e))]
γ 是欧拉常数。你的夏普必须超过这个门槛,才算携带了信息。
取 σ_SR = 0.5(夏普估计的标准差,典型量级):
| 搜索次数 N | 噪声下的期望最大夏普 |
|---|---|
| 1 | 0.00 |
| 10 | 0.79 |
| 50 | 1.14 |
| 200 | 1.38 |
| 591 | 1.55 |
| 1000 | 1.63 |
| 10000 | 1.93 |
回到 p0.1
第一节我们在 2005–2015 样本内做了 591 组参数网格搜索,冠军是 MA5/55,样本内夏普 1.28。
591 组的噪声门槛是 1.55。
1.28 < 1.55。
这就是 p0.1 那个策略样本外只剩 1.97% 年化的数学原因。
当时我们是怎么知道它不行的?用样本外结果——把参数拿到 2016–2026 跑一遍,发现输给了躺平。那是事后验证。
而现在你知道:当时就能事先判定。样本内夏普 1.28,还没达到"591 次搜索下纯噪声能达到的水平",它就不该被当成一个发现。
这是这一章最实用的一条:在做样本外之前,先用搜索次数算一个门槛。达不到门槛的,连样本外都不必浪费。
五、把它变成习惯
- 记账:从第一次检验开始记搜索次数。参数网格、持有期、阈值、标的池筛选——每一个你试过又放弃的选择,都算一次
- 报数:任何一个 p 值或夏普,必须与"试了多少次"一起出现
- 设门槛:用通缩夏普算出你这次搜索的噪声门槛,再看结果够不够
- 分级:研究筛选用 BH-FDR,上线前用 Bonferroni
📌 免责:本课为技术教学,统计与模拟结果均为历史/合成数据演示,不构成投资建议。
P3 章完结。这一节把整门课前三章的账算清了:
- 随机基线:49 次检验,纯噪声下平均出现 2.45 个"显著",至少 1 个的概率 92.2%
- 校正 P2:未校正 35 个显著 → Bonferroni 后 21 个(BH-FDR 33 个);p2.1 金叉那两颗星没扛住
- 搜索代价:真实夏普为 0 时,搜 1000 组有 14% 概率得到夏普>1 的"策略"
- 通缩夏普门槛:591 组 → 门槛 1.55;而 p0.1 的冠军夏普 1.28——低于门槛,事先就能判定
四条习惯:记账、报数、设门槛、分级(研究用 BH-FDR,上线用 Bonferroni)。
到这里,P0–P3 的路走完了:知道错在哪(P0)→ 数据可信(P1)→ 因子有评价(P2)→ 结论有统计(P3)。
下一章 P4 开始造东西:自研回测引擎。十节,从向量化与事件驱动的取舍开始,一路做到撮合层(T+1、涨跌停)、账户层、双路对账、与 backtrader 对拍,最后打包成一个 pip 可安装的开源库。
那一章会用到这三章的每一条纪律——而它交付的,是这门课的第一个成品。
🔎 来源与核验· 4 条,点开核对
