多重检验:Bonferroni、Benjamini-Hochberg 与伪发现率
测试 100 个策略并选择最佳,与找到 1 个有效的策略不是一回事。多重检验校正解释了其原因——以及如何修正。
你测试了 20 个策略。其中一个看起来惊艳无比——Sharpe 2.1,收益稳定,回撤小。你用它开了实盘。三个月后它在持续失血。
发生了什么?你没有考虑多重检验。当你测试 20 个策略时,仅凭随机 chance 就能期望有 1 个看起来很棒(在 α=0.05 显著性水平下)。回测中”最佳”的策略不一定是好的——只是运气最好的。
这就是多重检验问题,在量化金融领域普遍存在。
核心问题
单假设检验中,你设定 α=0.05,接受 5% 的假阳性概率。当只检验一个假设时,这样没问题。
在量化金融中,你通常检验几十甚至数百个:
- 筛选 500 只股票的价值因子
- 测试 50 个技术指标
- 从 200 个机器学习特征中选择
- 从 20 个策略变体中挑选最佳
当你在 α 水平上检验 m 个假设时,你的族错误率(FWER)——任意一个假阳性的概率——变为:
当 α=0.05,m=20:FWER = 64%。当 m=100:FWER = 99.4%。你几乎肯定至少有一个假阳性。
三种校正方法,三个应用场景
Bonferroni:保守选择
Bonferroni 在最坏情况下进行校正:你检验 m 个假设且希望 FWER ≤ α。校正如下:
对于 20 个检验在 α=0.05:α_corrected = 0.0025。只有 p 值低于 0.0025 才算显著。
Bonferroni 简单且保证零假阳性——但对探索性工作来说过于保守。你会错过真实的效应。
在以下情况使用 Bonferroni:
- 你有强先验理由相信恰好有一个假设为真
- 假阳性是灾难性的(监管批准、医学诊断)
- 你的假设是独立的或负相关的
Benjamini-Hochberg:控制伪发现率
Benjamini-Hochberg(BH)不保证零假阳性。它控制的是伪发现率(FDR):被拒绝的假设中,假阳性所占比例的期望值。
BH 不像 Bonferroni 那样保守。对于 20 个检验,如果你在原始 α=0.05 下找到 4 个显著,BH 可能会接受其中 3 个。权衡是:你接受一些假阳性,换取更多捕获真阳性的功效。
BH 程序:
- 计算所有 m 个检验的 p 值
- 对 p 值排序:p_(1) ≤ p_(2) ≤ … ≤ p_(m)
- 找到最大的 k 使得 p_(k) ≤ k·α/m
- 拒绝假设 1 到 k
在 Python 中:
import numpy as np
from scipy import stats
def benjamini_hochberg(p_values, alpha=0.05):
"""
Benjamini-Hochberg FDR correction.
Parameters
----------
p_values : array
Raw p-values for m tests
alpha : float
Target FDR level
Returns
-------
rejected : array (bool)
Which hypotheses to reject
"""
m = len(p_values)
sorted_idx = np.argsort(p_values)
sorted_p = p_values[sorted_idx]
# Find largest k where p_(k) <= k * alpha / m
thresholds = np.arange(1, m + 1) * alpha / m
below_threshold = sorted_p <= thresholds
if not below_threshold.any():
rejected = np.zeros(m, dtype=bool)
else:
k = np.where(below_threshold)[0][-1]
rejected_mask = np.zeros(m, dtype=bool)
rejected_mask[sorted_idx[:k+1]] = True
rejected = rejected_mask
return rejected
在以下情况使用 BH:
- 你在做探索性分析(筛选股票、找特征)
- 你可以容忍一些假阳性,如果这能捕获更多真阳性
- 你的假设正相关(BH 在正相关下是保守的)
Holm:比 Bonferroni 更强大
Holm 的方法是逐步下降程序,在保持 FWER 控制的同时,比 Bonferroni 更强大——它总是至少拒绝与 Bonferroni 一样多的假设。
校正如下:
其中 i 是 p 值的排名(1 = 最小)。你用 α/m 测试最小 p 值,用 α/(m-1) 测试下一个,以此类推,在第一个非显著处停止。
def holm_correction(p_values, alpha=0.05):
"""
Holm's step-down procedure for FWER control.
"""
m = len(p_values)
sorted_idx = np.argsort(p_values)
sorted_p = p_values[sorted_idx]
rejected = np.zeros(m, dtype=bool)
for i in range(m):
threshold = alpha / (m - i)
if sorted_p[i] < threshold:
rejected[sorted_idx[i]] = True
else:
break
return rejected
在以下情况使用 Holm:
- 你想要 FWER 控制但 Bonferroni 太保守
- 你的假设是独立的
Sharpe 比率问题
一个量化金融特有的例子。你测试 50 个策略变体。每个的 Sharpe 比率都从样本外回测中计算得出。你选择 Sharpe 最高的那个。
这是多重检验。即使所有 50 个策略的真实 Sharpe 都为 0,仅凭运气,最佳的那个也会显示非零的回测 Sharpe。
Deflated Sharpe Ratio(White 2000,Bailey and López de Prado 2014)修正了这一点。它通过零真实技能原假设下期望最大 Sharpe 来压缩观察到的 Sharpe:
其中 E_max[SR] 是在所有真实 Sharpe 比率均为零的假设下,m 次试验的期望最大 Sharpe。
为什么标准金融忽略这一点
大多数从业者不校正多重检验,因为:
第一,不方便:如果你诚实地测试 50 个策略,也许没有一个能存活。
第二,校正感觉过度:「我 2.1 的 Sharpe 是真的!」——也许,但当你测试 50 个时,它是假阳性的概率很高。
第三,学术论文也不总是校正:已发表的策略往往存在这个问题——测试了许多变体,报告了最佳,没有校正。
在我们的平台上,我们要求用户在任何结果之前说明他们测试了多少个策略。报告的 Sharpe 会相应地被压缩。这是不可协商的。
常见陷阱
看到部分结果后继续测试:「我先测试 20 个,挑出前 5 个,再更仔细地测试那些。」这是序贯测试,会膨胀假阳性率。在开始前指定你的测试协议。
需要 FWER 时使用 BH:如果你要选择单个策略用于实盘交易,假阳性会让你损失真金白银。使用 Bonferroni 或 Holm,而不是 BH。
忽略相关性:BH 假设独立性或正依赖。如果你的 20 个策略变体都是股票多头的不同因子,它们是正相关的——BH 有效但保守。如果有些是多头有些是空头(混合相关),BH 可能无效。
报告未校正的 p 值:「我们发现了一个 p=0.01 的策略。」但你测试了 50 个。校正后的阈值是 0.0002。那个 p=0.01 在校正后不显著。
在知识体系中的位置
多重检验校正在你从许多候选中选择时是必需的。《Sharpe 比率崇拜》涵盖了为什么单一指标选择(最高 Sharpe)是危险的。PBO(回测过拟合概率)将这一概念扩展到组合交叉验证下的策略选择。Deflated Sharpe Ratio 是在考虑多重检验后比较策略的适当指标。
参考文献
- Benjamini, Hochberg, “Controlling the False Discovery Rate,” Journal of the Royal Statistical Society, 1995.
- Bailey, López de Prado, “The Deflated Sharpe Ratio,” Journal of Portfolio Management, 2014.
- Benjamini, Yekutieli, “The Control of the False Discovery Rate in Multiple Testing Under Dependency,” Annals of Statistics, 2001.
- Westfall, Young, Resampling-Based Multiple Testing, Wiley, 1993. ISBN: 978-0-471-55761-4.