4. 多重比较谬误:为什么你的因子池越大越危险
做量化的人,大多有个通病——总想多挖几个因子。
因子池越大,找到“有效因子”的概率就越高,对吧?
嗯,表面上看是这样。但这里藏着一个很深的坑,叫多重比较谬误。
说白了就是:你测试的因子越多,纯靠运气撞上“显著”结果的可能性就越大。
4.1 什么是多重比较谬误
先讲个我自己的经历。
几年前,我帮一家私募搭建因子库。当时团队干劲十足,一口气挖了200多个因子。回测结果一出来,好家伙,30多个因子看起来都挺能打。大家兴奋得不行,觉得找到了宝藏。
结果呢?实盘跑了一个月,大部分都扑街了。
为什么会这样?
因为我们在200次独立测试中,哪怕所有因子都是随机噪声,按照5%的显著性水平,也天然会有大约10个因子“显著”。这就是多重比较谬误。
核心概念:
多重比较谬误(Multiple Comparison Fallacy)是指:当同时进行多次统计检验时,每次检验都有一定的概率犯第一类错误(假阳性)。检验次数越多,至少出现一次假阳性的概率就越高。
用公式表达更直观:
假设每次检验的显著性水平为 α(通常取0.05)
独立进行 n 次检验
至少出现一次假阳性的概率 = 1 - (1 - α)^n
当 n = 20 时,概率 ≈ 64%
当 n = 100 时,概率 ≈ 99.4%
你想想看,测试100个随机因子,几乎必然会有几个看起来“显著”的。这不是你的策略厉害,是数学在跟你开玩笑。
4.2 因子挖掘中的典型场景
我见过最典型的场景有这么几种:
- 参数遍历式挖掘:一个基础因子,换10个参数窗口,换5种计算方式,一下子变成50个因子
- 多分组测试:同一个因子,分5组、10组、20组分别测试,取最好看的结果
- 多时间段回测:2015-2020不行?那就试2016-2021,再试2017-2022,直到找到一段好看的
- 多市场验证:A股不行试港股,港股不行试美股,总有一个市场能跑通
这些做法,说白了都是在“偷看答案”。
警告:
千万不要以为“多试几次总能找到好的”是勤奋的表现。在统计学的眼里,这叫数据窥探(Data Snooping),是因子研究的大忌。
4.3 如何校正:几种常用方法
那怎么办?总不能只测一个因子吧?
当然不是。我们需要对多重比较进行校正。我个人习惯用以下几种方法:
4.3.1 Bonferroni 校正
最简单粗暴的方法。把显著性水平除以检验次数。
原始 α = 0.05
测试了 50 个因子
校正后 α' = 0.05 / 50 = 0.001
只有 p 值 < 0.001 的因子才算显著
这个方法太保守了。我在项目中用过,经常把真因子也杀掉了。但作为底线标准,它很可靠。
4.3.2 FDR(False Discovery Rate)控制
这是我现在最常用的方法。它不控制“不能有假阳性”,而是控制“假阳性在所有阳性中的比例”。
说白了,Bonferroni 是“宁可错杀一千,不放过一个”。FDR 是“允许少量假阳性,但别太多”。
# Python 实现 Benjamini-Hochberg 过程
import numpy as np
from scipy.stats import rankdata
def bh_correction(p_values, q=0.05):
"""
Benjamini-Hochberg FDR 校正
p_values: 所有因子的 p 值列表
q: 期望的 FDR 水平,通常取 0.05 或 0.1
"""
n = len(p_values)
ranked = rankdata(p_values)
# 计算每个 p 值的阈值
thresholds = (ranked / n) * q
# 找到最大的满足 p_i <= threshold 的索引
sorted_idx = np.argsort(p_values)
max_idx = -1
for i in sorted_idx:
if p_values[i] <= thresholds[i]:
max_idx = i
# 标记显著的因子
significant = np.zeros(n, dtype=bool)
if max_idx >= 0:
significant[:max_idx+1] = True
return significant
我的经验:
在因子初筛阶段,我一般用 q=0.1(允许10%的假阳性)。到了精选阶段,会收紧到 q=0.05。这样既不会漏掉太多真因子,又能控制风险。
4.3.3 交叉验证法
这个方法更贴近实战。把数据分成几段,要求因子在每一段都显著。
我曾经用过一个简单规则:
- 把回测期分成3段(比如早期、中期、近期)
- 因子必须在至少2段中显著
- 且不能在任何一段中显著反向
这招虽然不严谨,但在实际项目中很管用。它能过滤掉那些只在特定时间段有效的“运气因子”。
4.4 一张图看懂多重比较谬误
下面这张图,是我给团队培训时画的。它把整个逻辑串起来了:
4.5 实战中的避坑指南
讲了这么多理论,来点实在的。以下是我这些年总结的几条经验:
避坑指南 1:先定因子数量,再做检验
我曾经犯过一个错:先跑回测,看到结果不好,又加了一批因子继续跑。这等于变相增加了检验次数。正确的做法是:在开始回测之前,就把要测试的因子清单定死。
避坑指南 2:用样本外数据做最终验证
把所有因子在样本内跑一遍,用FDR校正筛选。然后,把选中的因子放到从未见过的样本外数据中测试。如果还能显著,那才是真本事。
避坑指南 3:警惕“因子族”效应
很多因子其实是同一类。比如5日反转、10日反转、20日反转,它们高度相关。测试10个这样的因子,跟测试1个没太大区别。我建议先做因子聚类,每类只留1-2个代表。
特别提醒:
有些平台会提供“因子自动挖掘”功能,一键生成几百个因子。这种工具用起来很爽,但坑也最大。我见过有人用这种工具挖出50个“显著”因子,结果实盘全军覆没。不是工具不好,是没做多重比较校正。
4.6 一个简单的检查清单
每次做完因子筛选,我都会问自己几个问题:
| 检查项 | 说明 | 通过标准 |
|---|---|---|
| 因子总数 | 一共测试了多少个因子 | 记录在案,不事后追加 |
| 校正方法 | 用了哪种多重比较校正 | 至少用了FDR或Bonferroni |
| 样本外验证 | 是否在独立数据上验证过 | 必须通过 |
| 因子独立性 | 显著因子之间是否高度相关 | 相关性 < 0.7 |
| 时间稳定性 | 因子在不同时间段是否一致 | 至少2/3时间段显著 |
这五条都过了,我才敢把因子放进实盘组合里。
最后说一句:因子挖掘不是比谁挖得多,而是比谁挖得真。控制好多重比较谬误,你的因子池才能从“数字游戏”变成“真金白银”。