13 多重检验

多重检验问题

量化投资中的”假发现”陷阱

假设你测试了 1000 个量化因子,每次使用 \(\alpha = 0.05\) 的显著性水平:

  • 即使所有因子都无效\(H_0\) 全为真),期望的”显著”因子数 = \(1000 \times 0.05 = 50\)
  • 至少出现 1 个假阳性的概率:

\[\text{FWER} = 1 - (1 - \alpha)^m = 1 - (1-0.05)^{1000} \approx 100\%\]

核心问题:当同时进行大量假设检验时,第一类错误(假阳性)发生的概率急剧膨胀。

假设检验的基本框架回顾

\(H_0\) 为真 \(H_0\) 为假
不拒绝 \(H_0\) 正确接受 (U) 第二类错误 (T)
拒绝 \(H_0\) 第一类错误 (V) 正确拒绝 (S)

关键定义:

  • 第一类错误\(\alpha\)):拒绝了一个真实的原假设(假阳性)
  • 第二类错误\(\beta\)):未能拒绝一个虚假的原假设(假阴性)
  • 检验效力 (Power):\(1 - \beta\),正确拒绝虚假原假设的概率

模拟实验:1000 个无效因子的检验

Code
import numpy as np  # 导入numpy用于数值计算
import matplotlib.pyplot as plt  # 导入绑图库
from scipy import stats  # 导入统计模块

plt.rcParams['font.sans-serif'] = ['Source Han Serif SC']  # 设置中文字体
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

np.random.seed(42)  # 设置随机种子保证可重现
num_tests = 1000  # 假设检验次数(1000个因子)
null_test_statistics = np.random.standard_normal(num_tests)  # 从标准正态分布生成检验统计量(所有H0为真)
null_p_values = 2 * (1 - stats.norm.cdf(np.abs(null_test_statistics)))  # 计算双侧p值

num_false_positives = np.sum(null_p_values < 0.05)  # 统计p<0.05的假阳性个数

fig, axes = plt.subplots(1, 2, figsize=(12, 4.5))  # 创建1行2列子图

# 左图:p值直方图
axes[0].hist(null_p_values, bins=50, color='#008080', alpha=0.7, edgecolor='white')  # 绘制p值频率直方图
axes[0].axvline(x=0.05, color='#E3120B', linestyle='--', linewidth=2, label='α = 0.05')  # 标注显著性阈值
axes[0].set_xlabel('p 值')  # x轴标签
axes[0].set_ylabel('频次')  # y轴标签
axes[0].set_title(f'p值分布(假阳性: {num_false_positives} 个)')  # 标题(含假阳性数)
axes[0].legend()  # 显示图例

# 右图:-log10(p)散点图
axes[1].scatter(range(num_tests), -np.log10(null_p_values), s=5, alpha=0.5, color='gray')  # 绘制-log10(p)散点
axes[1].axhline(y=-np.log10(0.05), color='#E3120B', linestyle='--', linewidth=2, label='α = 0.05')  # 显著性阈值线
axes[1].set_xlabel('因子编号')  # x轴标签
axes[1].set_ylabel('$-\\log_{10}(p)$')  # y轴标签
axes[1].set_title('所有因子都无效,但假阳性大量出现')  # 标题
axes[1].legend()  # 显示图例

plt.tight_layout()  # 自动调整间距
plt.show()  # 显示图表
Figure 1: 1000个全部为零假设的p值分布(模拟数据)

FWER 与 FDR:两种不同的错误控制策略

两种错误控制策略对比 FWER 控制 家族错误率 Pr(至少1个假阳性) ≤ α 极度保守 ✓ 控制任何假阳性 ✗ 可能错过真效应 代表方法: Bonferroni, Holm FDR 控制 错误发现率 E[V/R] ≤ q 适度宽松 ✓ 允许少量假阳性 ✓ 保留更多真效应 代表方法: BH, Storey q 更保守 更灵活 检验次数 m 越大,二者的差距越大 大规模因子筛选时 FDR 控制通常更实用
Figure 2: FWER 与 FDR 对比:两种不同的多重检验校正的哲学

Bonferroni 校正

Bonferroni:最简单的 FWER 控制方法

核心思想:将显著性水平均匀分配给每一次检验。

\[\alpha_{\text{adjusted}} = \frac{\alpha}{m}\]

等价地,对 p 值进行调整:

\[p_{\text{adjusted}} = \min(m \times p_{\text{原始}}, \; 1)\]

优点:简单、直观、对任何依赖结构都有效

缺点:当 \(m\) 很大时极度保守——例如 \(m = 1000\) 时,\(\alpha_{\text{adjusted}} = 0.00005\)

案例:A 股强势股票筛选

import pandas as pd  # 导入pandas用于数据操作
import os  # 导入os用于路径处理

DATA_DIR = 'C:/qiufei/data' if os.name == 'nt' else '/home/ubuntu/r2_data_mount/qiufei/data'  # 跨平台数据路径

# 读取前复权股价数据
stock_price_data = pd.read_hdf(os.path.join(DATA_DIR, 'stock/stock_price_pre_adjusted.h5')).reset_index()  # 读取并重置索引
stock_price_2020 = stock_price_data[  # 筛选2020年度数据
    (stock_price_data['date'] >= '2020-01-01') & (stock_price_data['date'] < '2021-01-01')  # 限定时间范围
]  # 用于强势股票筛选分析

# 计算日收益率
stock_price_2020 = stock_price_2020.copy()  # 复制以避免SettingWithCopyWarning
stock_price_2020['daily_return'] = stock_price_2020.groupby('order_book_id')['close'].pct_change()  # 按股票计算日收益率

# 筛选交易日数 >= 100 的股票
valid_stock_counts = stock_price_2020.groupby('order_book_id')['daily_return'].count()  # 各股票的有效交易日数
valid_stock_list = valid_stock_counts[valid_stock_counts >= 100].index.tolist()  # 筛选数据充足的股票
print(f'2020年有效股票数量: {len(valid_stock_list)}')  # 输出有效股票数
2020年有效股票数量: 3932

未校正 vs Bonferroni 校正效果对比

Code
from scipy import stats  # 导入统计库
from statsmodels.stats.multitest import multipletests  # 导入多重检验校正工具

# 对每只股票执行单侧 t 检验 (H1: μ > 0)
p_value_list = []  # 存储原始p值
t_stat_list = []  # 存储t统计量
for stock_code in valid_stock_list[:500]:  # 取前500只股票(控制计算时间)
    stock_returns = stock_price_2020[stock_price_2020['order_book_id'] == stock_code]['daily_return'].dropna()  # 提取收益率
    t_statistic, two_sided_p = stats.ttest_1samp(stock_returns, 0)  # 单样本t检验
    one_sided_p = two_sided_p / 2 if t_statistic > 0 else 1 - two_sided_p / 2  # 转为单侧p值
    p_value_list.append(one_sided_p)  # 记录p值
    t_stat_list.append(t_statistic)  # 记录t统计量

p_values_array = np.array(p_value_list)  # 转为numpy数组

# Bonferroni 校正
bonferroni_reject, bonferroni_p_adjusted, _, _ = multipletests(p_values_array, alpha=0.05, method='bonferroni')  # Bonferroni校正

fig, axes = plt.subplots(1, 2, figsize=(13, 5))  # 创建1行2列子图

# 左图:未校正
is_significant_raw = p_values_array < 0.05  # 未校正下的显著性判断
axes[0].scatter(range(len(p_values_array)), -np.log10(p_values_array),  # 绘制-log10(p)散点
    c=['#E3120B' if s else 'gray' for s in is_significant_raw], s=8, alpha=0.6)  # 显著=红色
axes[0].axhline(y=-np.log10(0.05), color='blue', linestyle='--', label='α=0.05')  # 原始阈值线
axes[0].set_title(f'未校正(显著: {sum(is_significant_raw)} 只)')  # 标题
axes[0].set_xlabel('股票编号')  # x轴
axes[0].set_ylabel('$-\\log_{10}(p)$')  # y轴
axes[0].legend()  # 图例

# 右图:Bonferroni 校正后
axes[1].scatter(range(len(p_values_array)), -np.log10(p_values_array),  # 同样的-log10(p)
    c=['#E3120B' if s else 'gray' for s in bonferroni_reject], s=8, alpha=0.6)  # Bonferroni显著=红色
bonferroni_threshold = 0.05 / len(p_values_array)  # 计算Bonferroni校正阈值
axes[1].axhline(y=-np.log10(bonferroni_threshold), color='blue', linestyle='--', label=f'α/m={bonferroni_threshold:.2e}')  # 校正阈值线
axes[1].set_title(f'Bonferroni 校正(显著: {sum(bonferroni_reject)} 只)')  # 标题
axes[1].set_xlabel('股票编号')  # x轴
axes[1].set_ylabel('$-\\log_{10}(p)$')  # y轴
axes[1].legend()  # 图例

plt.tight_layout()  # 自动调整间距
plt.show()  # 显示图表
Figure 3: 未校正与 Bonferroni 校正的股票筛选对比

Benjamini-Hochberg (BH) 方法

BH 方法:自适应的 FDR 控制

BH 算法步骤

  1. \(m\) 个 p 值从小到大排序:\(p_{(1)} \leq p_{(2)} \leq \cdots \leq p_{(m)}\)
  2. 找到最大的 \(k\),使得 \(p_{(k)} \leq \frac{k}{m} \cdot q\)
  3. 拒绝排序在前 \(k\) 个的所有假设

核心优势:BH 的阈值线 \(\frac{k}{m} \cdot q\)线性递增的:

  • 排序靠前的 p 值(更可能是真信号)获得更宽松的阈值
  • 排序靠后的 p 值(更可能是噪声)获得更严格的阈值
  • 自适应地平衡发现率假阳性率

数学保证:在独立或正依赖条件下,\(\text{FDR} \leq \frac{m_0}{m} \cdot q \leq q\)

BH 实战:注入 Alpha 信号的检测

# 使用2021年数据
stock_price_2021 = stock_price_data[  # 筛选2021年数据
    (stock_price_data['date'] >= '2021-01-01') & (stock_price_data['date'] < '2022-01-01')  # 限定时间范围
].copy()  # 创建副本

stock_price_2021['daily_return'] = stock_price_2021.groupby('order_book_id')['close'].pct_change()  # 计算日收益率

# 选取数据充足的股票
valid_2021_counts = stock_price_2021.groupby('order_book_id')['daily_return'].count()  # 各股票交易日数
valid_2021_stocks = valid_2021_counts[valid_2021_counts >= 100].index.tolist()  # 筛选有效股票

# 随机注入100只股票的正Alpha(+0.5%/天)
np.random.seed(2024)  # 设置随机种子
injected_alpha_stocks = np.random.choice(valid_2021_stocks, size=100, replace=False)  # 随机选100只注入Alpha
injected_stock_set = set(injected_alpha_stocks)  # 转为集合便于查询

# 为注入Alpha的股票修改收益率
stock_price_2021_modified = stock_price_2021.copy()  # 复制数据
alpha_mask = stock_price_2021_modified['order_book_id'].isin(injected_stock_set)  # 标记被注入的股票
stock_price_2021_modified.loc[alpha_mask, 'daily_return'] += 0.005  # 注入每日+0.5%的正Alpha

print(f'2021年有效股票总数: {len(valid_2021_stocks)}')  # 输出总股票数
print(f'注入 Alpha 的股票数: {len(injected_alpha_stocks)}')  # 输出注入Alpha的股票数
2021年有效股票总数: 4428
注入 Alpha 的股票数: 100

三种方法对比:BH 的优势一目了然

Code
# 对所有股票进行 t 检验
all_p_values_2021 = []  # 存储所有p值
all_t_stats_2021 = []  # 存储所有t统计量
all_is_true_alpha = []  # 标记是否为注入Alpha的股票

for stock_code in valid_2021_stocks:  # 遍历每只有效股票
    returns = stock_price_2021_modified[stock_price_2021_modified['order_book_id'] == stock_code]['daily_return'].dropna()  # 提取收益率
    if len(returns) < 30:  # 数据不足时跳过
        continue  # 跳过数据量不够的股票
    t_stat, two_p = stats.ttest_1samp(returns, 0)  # 单样本t检验
    one_p = two_p / 2 if t_stat > 0 else 1 - two_p / 2  # 转单侧p值
    all_p_values_2021.append(one_p)  # 记录p值
    all_t_stats_2021.append(t_stat)  # 记录t统计量
    all_is_true_alpha.append(stock_code in injected_stock_set)  # 标记真实Alpha

p_arr = np.array(all_p_values_2021)  # 转numpy数组
t_arr = np.array(all_t_stats_2021)  # 转numpy数组
truth_arr = np.array(all_is_true_alpha)  # 真实标签数组

# 三种校正方法
raw_reject = p_arr < 0.05  # 未校正
bonf_reject, _, _, _ = multipletests(p_arr, alpha=0.05, method='bonferroni')  # Bonferroni
bh_reject, bh_p_adj, _, _ = multipletests(p_arr, alpha=0.05, method='fdr_bh')  # BH
Figure 4
# 计算各方法的关键指标
def calculate_performance_metrics(reject_decisions, true_labels):  # 定义性能计算函数
    true_positive = np.sum(reject_decisions & true_labels)  # 真阳性(正确拒绝)
    false_positive = np.sum(reject_decisions & ~true_labels)  # 假阳性(错误拒绝)
    total_rejections = np.sum(reject_decisions)  # 总拒绝数
    fdr = false_positive / total_rejections if total_rejections > 0 else 0  # 错误发现率
    tpr = true_positive / np.sum(true_labels) if np.sum(true_labels) > 0 else 0  # 真阳性率(检验效力)
    return {'TP': true_positive, 'FP': false_positive, 'R': total_rejections, 'FDR': f'{fdr:.2%}', 'TPR': f'{tpr:.2%}'}  # 返回指标字典

results_comparison = pd.DataFrame({  # 创建对比表
    '未校正 (α=0.05)': calculate_performance_metrics(raw_reject, truth_arr),  # 未校正结果
    'Bonferroni': calculate_performance_metrics(bonf_reject, truth_arr),  # Bonferroni结果
    'BH (q=0.05)': calculate_performance_metrics(bh_reject, truth_arr)  # BH结果
})  # 三列三行的性能对比表
results_comparison  # 显示对比表格
Table 1: 三种方法的检验性能对比
未校正 (α=0.05) Bonferroni BH (q=0.05)
TP 92 11 39
FP 242 0 0
R 334 11 39
FDR 72.46% 0.00% 0.00%
TPR 92.00% 11.00% 39.00%

可视化:BH 自适应阈值线与火山图

Code
fig, axes = plt.subplots(1, 2, figsize=(13, 5))  # 创建1行2列子图

# 左图:BH 自适应阈值线
sorted_indices = np.argsort(p_arr)  # 按p值从小到大排序的索引
sorted_p = p_arr[sorted_indices]  # 排序后的p值
m_total = len(p_arr)  # 总检验次数
bh_threshold_line = np.arange(1, m_total + 1) / m_total * 0.05  # BH阈值线: k/(m*q),线性递增

axes[0].scatter(range(m_total), sorted_p, s=3, c='gray', alpha=0.5, label='排序后的p值')  # 排序p值散点图
axes[0].plot(range(m_total), bh_threshold_line, color='#008080', linewidth=2, label='BH 阈值线')  # BH阈值线
axes[0].set_xlabel('排序位置 k')  # x轴
axes[0].set_ylabel('p 值')  # y轴
axes[0].set_title('BH 自适应阈值线')  # 标题
axes[0].set_ylim(0, 0.1)  # 限制y轴范围便于观察
axes[0].legend()  # 图例

# 右图:火山图
for i in range(len(t_arr)):  # 遍历每个检验
    color = 'gray'  # 默认灰色(不显著)
    if bh_reject[i] and t_arr[i] > 0:  # BH显著且t为正
        color = '#E3120B'  # 红色(显著正效应)
    elif bh_reject[i] and t_arr[i] < 0:  # BH显著且t为负
        color = '#008080'  # 青色(显著负效应)
    axes[1].scatter(t_arr[i], -np.log10(p_arr[i]), s=6, c=color, alpha=0.5)  # 绘制散点

axes[1].set_xlabel('t 统计量')  # x轴
axes[1].set_ylabel('$-\\log_{10}(p)$')  # y轴
axes[1].set_title('火山图(BH 校正)')  # 标题

plt.tight_layout()  # 自动调整间距
plt.show()  # 显示图表
Figure 5: BH 自适应阈值线(左)与火山图(右)

其他校正方法与实际应用

Holm 方法与 Storey q值

Holm 方法(逐步下降法):

  1. 排序 p 值:\(p_{(1)} \leq p_{(2)} \leq \cdots \leq p_{(m)}\)
  2. 从最小开始,比较 \(p_{(k)}\)\(\frac{\alpha}{m - k + 1}\)
  3. 一旦某个 \(p_{(k)}\) 不显著,停止,后续全部不拒绝

Storey q 值方法

  • 估计真零假设的比例 \(\hat{\pi}_0\) → 更精确的 FDR 控制
  • q 值 = 某个发现被判为显著时的最小 FDR
  • 适用于 \(m\) 非常大且真效应比例较高的场景
方法 控制目标 保守程度 检验效力
Bonferroni FWER 最保守
Holm FWER 较保守
BH FDR 适中
Storey q FDR 最灵活 最高

P-Hacking 与数据偷窥的危害

P-Hacking(p值黑客)是量化金融研究中的严重问题:

  • 在大量回测中”挖掘”出看起来显著的策略
  • Harvey, Liu & Zhu (2016) 发现:超过一半的金融”异象因子”可能是假的
  • 他们建议将 t 统计量的显著性阈值提高到 t > 3.0

常见的数据偷窥手法

  • 在同一数据集上测试大量策略,只报告显著的
  • 不断调整回测参数直到出现好结果
  • 对同一假设尝试多种统计检验方法

防范措施

  • 预先注册研究计划和假设
  • 样本外验证:将数据分为训练集和测试集
  • 报告所有结果,包括不显著的检验
  • 使用 BH 或 Bonferroni 进行系统性的多重检验校正

案例:海康威视技术指标的多重检验

Code
# 获取海康威视(002415.XSHE)的股价数据
hikvision_data = stock_price_data[  # 筛选海康威视
    (stock_price_data['order_book_id'] == '002415.XSHE') &  # 股票代码
    (stock_price_data['date'] >= '2020-01-01')  # 2020年以来
].copy().sort_values('date')  # 按日期排序

hikvision_data['daily_return'] = hikvision_data['close'].pct_change()  # 计算日收益率

# 测试 50 条均线策略 (MA5 到 MA54)
ma_periods = range(5, 55)  # MA周期从5到54
strategy_p_values = []  # 存储各策略的p值

for ma_period in ma_periods:  # 遍历每个MA周期
    hikvision_data[f'MA_{ma_period}'] = hikvision_data['close'].rolling(ma_period).mean()  # 计算移动平均
    signal_mask = hikvision_data['close'] > hikvision_data[f'MA_{ma_period}']  # 价格在均线上方时持仓
    strategy_returns = hikvision_data.loc[signal_mask, 'daily_return'].dropna()  # 持仓期间的收益率
    if len(strategy_returns) > 30:  # 样本量充足时
        t_stat, p_val = stats.ttest_1samp(strategy_returns, 0)  # t检验:均值是否>0
        strategy_p_values.append(p_val / 2 if t_stat > 0 else 1)  # 转为单侧p值
    else:  # 样本量不足
        strategy_p_values.append(1.0)  # 设为不显著

strategy_p_arr = np.array(strategy_p_values)  # 转numpy数组
bonf_reject_ma, _, _, _ = multipletests(strategy_p_arr, alpha=0.05, method='bonferroni')  # Bonferroni校正
bh_reject_ma, _, _, _ = multipletests(strategy_p_arr, alpha=0.05, method='fdr_bh')  # BH校正

fig, ax = plt.subplots(figsize=(11, 5))  # 创建图表
x_pos = list(ma_periods)  # x轴位置
ax.bar(x_pos, -np.log10(strategy_p_arr), color=[  # 绘制-log10(p)柱状图
    '#E3120B' if strategy_p_arr[i] < 0.05 else 'gray' for i in range(len(strategy_p_arr))  # 未校正显著=红色
], alpha=0.7)  # 透明度0.7
ax.axhline(y=-np.log10(0.05), color='blue', linestyle='--', label='未校正 α=0.05')  # 未校正阈值
ax.axhline(y=-np.log10(0.05/len(strategy_p_arr)), color='#E3120B', linestyle='-', label='Bonferroni')  # Bonferroni阈值
ax.set_xlabel('移动平均周期 (MA)')  # x轴
ax.set_ylabel('$-\\log_{10}(p)$')  # y轴
ax.set_title(f'海康威视50条MA策略: 未校正显著 {sum(strategy_p_arr < 0.05)} 条 → 校正后 {sum(bonf_reject_ma)} 条')  # 标题
ax.legend()  # 图例
plt.tight_layout()  # 自动调整间距
plt.show()  # 显示图表
Figure 6: 海康威视50条均线策略的多重检验校正

关键发现:大量未校正下”显著”的策略,在 Bonferroni 或 BH 校正后全部变为不显著 → 典型的数据偷窥结果!

案例:行业 Alpha 的多重检验

Code
# 读取上市公司基本信息获取行业分类
stock_basic = pd.read_hdf(os.path.join(DATA_DIR, 'stock/stock_basic_data.h5'))  # 读取基本信息

# 使用近三年数据
stock_price_3yr = stock_price_data[stock_price_data['date'] >= '2021-01-01'].copy()  # 筛选近三年数据
stock_price_3yr['daily_return'] = stock_price_3yr.groupby('order_book_id')['close'].pct_change()  # 计算日收益率

# 合并行业信息
stock_with_industry = stock_price_3yr.merge(  # 合并行业分类
    stock_basic[['order_book_id', 'industry_name']], on='order_book_id', how='left'  # 左连接
)  # 获取每只股票的行业归属

# 按行业计算平均日收益率并做 t 检验
industry_groups = stock_with_industry.groupby('industry_name')['daily_return']  # 按行业分组
industry_p_values = {}  # 存储各行业的p值

for industry_name, group_returns in industry_groups:  # 遍历每个行业
    clean_returns = group_returns.dropna()  # 去除缺失值
    if len(clean_returns) > 100:  # 数据量充足时
        t_stat, p_val = stats.ttest_1samp(clean_returns, 0)  # 检验行业平均收益是否≠0
        industry_p_values[industry_name] = p_val  # 记录双侧p值

industry_names = list(industry_p_values.keys())  # 行业名列表
industry_p_arr = np.array(list(industry_p_values.values()))  # p值数组
Figure 7
Code
# 三种校正
industry_raw_sig = industry_p_arr < 0.05  # 未校正显著判断
industry_bonf_rej, _, _, _ = multipletests(industry_p_arr, alpha=0.05, method='bonferroni')  # Bonferroni
industry_bh_rej, _, _, _ = multipletests(industry_p_arr, alpha=0.05, method='fdr_bh')  # BH

fig, axes = plt.subplots(1, 3, figsize=(15, 5))  # 1行3列子图
method_configs = [  # 三种方法的配置
    ('未校正 (α=0.05)', industry_raw_sig, int(sum(industry_raw_sig))),  # 未校正配置
    ('Bonferroni', industry_bonf_rej, int(sum(industry_bonf_rej))),  # Bonferroni配置
    ('BH (q=0.05)', industry_bh_rej, int(sum(industry_bh_rej)))  # BH配置
]  # 三种方法

for ax_idx, (method_name, reject_mask, count) in enumerate(method_configs):  # 遍历三种方法
    colors = ['#E3120B' if r else 'gray' for r in reject_mask]  # 显著=红色,不显著=灰色
    axes[ax_idx].barh(range(len(industry_p_arr)), -np.log10(industry_p_arr), color=colors, alpha=0.7)  # 水平柱状图
    axes[ax_idx].set_title(f'{method_name}(显著: {count})')  # 标题含显著数量
    axes[ax_idx].set_xlabel('$-\\log_{10}(p)$')  # x轴标签

plt.tight_layout()  # 自动调整间距
plt.show()  # 显示图表
Figure 8: 行业Alpha检验: 未校正 vs Bonferroni vs BH

实践指南与总结

方法选择决策框架

多重检验校正方法选择指南 检验次数 m m < 10: 可不校正 需要严格控制假阳性? 是 → FWER Bonferroni 最简单、最保守 α_adj = α/m Holm 比Bonferroni更有效力 否 → FDR BH 方法 最常用、平衡好 FDR ≤ q Storey q 值 m很大且真效应多时 m很大: 优先FDR 最佳实践清单 1. 预先注册研究假设 2. 报告所有检验结果 3. 样本外验证 4. 关注效应量而非p值 5. 尽可能使用独立数据复制关键发现
Figure 9: 多重检验校正方法的选择决策树与最佳实践

本章小结

三个核心认知

  1. 多重检验会累积假阳性:同时做 \(m\) 次检验时,第一类错误概率 \(\to 1 - (1-\alpha)^m\),远大于 \(\alpha\)
  2. 两种控制策略:FWER(不容许任何假阳性)vs FDR(控制假阳性比例)
  3. 三大校正方法:Bonferroni(最保守)→ Holm(中间)→ BH(最灵活)

量化投资中的最佳实践

  • 事前:明确研究假设,使用独立的样本外数据验证
  • 事中:对所有检验应用 BH 或 Bonferroni 校正
  • 事后:报告所有结果(包括不显著的),关注效应量而非仅看 p 值
  • 终极原则:用独立数据复制你的关键发现