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 一张图看懂多重比较谬误

下面这张图,是我给团队培训时画的。它把整个逻辑串起来了:

多重比较谬误:从因子挖掘到风险暴露 因子池(N个候选因子) N次独立统计检验(每次 α = 0.05) 发现 K 个“显著”因子 假阳性:纯属运气 概率 ≈ 1 - (1-0.05)^N 真阳性:真正有效 需要多重比较校正 结论:N 越大,假阳性越多,必须做校正!

4.5 实战中的避坑指南

讲了这么多理论,来点实在的。以下是我这些年总结的几条经验:

避坑指南 1:先定因子数量,再做检验

我曾经犯过一个错:先跑回测,看到结果不好,又加了一批因子继续跑。这等于变相增加了检验次数。正确的做法是:在开始回测之前,就把要测试的因子清单定死。

避坑指南 2:用样本外数据做最终验证

把所有因子在样本内跑一遍,用FDR校正筛选。然后,把选中的因子放到从未见过的样本外数据中测试。如果还能显著,那才是真本事。

避坑指南 3:警惕“因子族”效应

很多因子其实是同一类。比如5日反转、10日反转、20日反转,它们高度相关。测试10个这样的因子,跟测试1个没太大区别。我建议先做因子聚类,每类只留1-2个代表。

特别提醒:

有些平台会提供“因子自动挖掘”功能,一键生成几百个因子。这种工具用起来很爽,但坑也最大。我见过有人用这种工具挖出50个“显著”因子,结果实盘全军覆没。不是工具不好,是没做多重比较校正。

4.6 一个简单的检查清单

每次做完因子筛选,我都会问自己几个问题:

检查项 说明 通过标准
因子总数 一共测试了多少个因子 记录在案,不事后追加
校正方法 用了哪种多重比较校正 至少用了FDR或Bonferroni
样本外验证 是否在独立数据上验证过 必须通过
因子独立性 显著因子之间是否高度相关 相关性 < 0.7
时间稳定性 因子在不同时间段是否一致 至少2/3时间段显著

这五条都过了,我才敢把因子放进实盘组合里。

最后说一句:因子挖掘不是比谁挖得多,而是比谁挖得真。控制好多重比较谬误,你的因子池才能从“数字游戏”变成“真金白银”。