跳转至

M08 因子陷阱与研究现状

层级:L2 因子投资 | 优先级:★★★ | 预计学时:12 小时 对应原书:因子第 6 章(6.1–6.7);黑箱第 9 章的检验、第 11 章;陈 3.4 前置知识:M01 的 t = SR √Y;M06 的复制率口径;M07 的窗口表。 读书笔记:因子第 6 章的陷阱 状态:reviewed 数据截至:2026-08|最近复核:2026-10-22


学习目标

  1. 用期望的线性说明:1000 次水平为 5% 的独立检验,在全是噪声时平均会「显著」50 次。
  2. 手算 20 个 p 值上的 Bonferroni、Holm 和 Benjamini–Hochberg,并看出三者停在不同的个数。
  3. 把 t = 3 换算成不同样本长度下的年化夏普门槛,并把它和本仓库现行流程里的门槛分开。
  4. 读懂 20,000 次模拟:200 条零溢价、十年样本里,最大夏普的均值是 0.868,不是把任何夏普乘上 0.42。
  5. 把「原论文能否复现」「更严的持仓设定下还显著吗」「发表后的月均掉了多少」分成三件不同的事。
  6. 在月频上算出一组 Deflated Sharpe:年化夏普 1.20、T = 120、M = 100、截面标准差 0.12 时,相对 0 的概率是 0.999555,紧缩后是 0.659086。
  7. 用 4 个时间分块的 6 种组合,算出样本内冠军落入样本外后半区的比例 0.666667。

本章知识拓扑图

flowchart TB
    TRY["同一份数据上的多次尝试"] --> MAX["被留下的是最大的 t 或夏普"]
    MAX --> HURDLE["门槛要跟着尝试次数上移"]
    HURDLE --> HLZ["HLZ:新因子 t 大于 3"]
    HURDLE --> DSR["DSR:月频夏普对选择门槛的概率"]
    DSR --> PBO["CSCV:样本内冠军的样本外名次"]
    HURDLE --> GATE["本轮流程:不把夏普大于 1 写成通用门槛"]
    PUB["发表时的月均"] --> DECAY["样本外与发表后的下降"]
    DECAY --> WINDOW["同一列因子的后一段窗口"]
    CROWD["同质持仓"] --> UNWIND["被迫减仓时的联动"]
    WINDOW --> ACCT["做多、杠杆上限 1.0 的账户"]
    UNWIND --> ACCT

多次尝试改变的是「你留下来的那个数」的分布。它不自动把 French 因子的月均乘上一个固定折扣。


🟢 入门层:好看的回测从哪来

1. 五十次「显著」可以一个真溢价都没有

设每次检验在原假设为真时,以概率 α 拒绝原假设。做了 m 次,假阳性个数的期望是

E[Σⱼ₌₁^m Iⱼ] = Σⱼ₌₁^m E[Iⱼ] = mα

符号 含义
Iⱼ 第 j 次检验被拒绝时为 1
α 单次检验的水平,本章例子用 0.05
m 尝试次数,包括没有写进备忘录的那些

这个等式只用到期望的可加性。检验之间相关,期望个数仍然是 mα。相关改变的是另一件事:这些拒绝会不会挤成同一主题的很多副本,以及最大的那个 t 值有多极端。相关不会把期望个数从 50 改成 5。

手算:m = 1000,α = 0.05。

1000 × 0.05 = 50

若 1000 个信号的真实溢价都是 0,你平均仍会看到 50 个「5% 显著」。只把这 50 个写进结论,读者看到的动物园里没有那些不显著的 950 个。Cochrane (2011, JF 66(4)) 用「因子动物园」描述已经发表的这一层。Harvey, Liu & Zhu (2016, RFS 29(1)) 清点了 313 篇文章里的 316 个因子,并写明这个清点低于真实尝试次数:没发表的失败测试看不到。他们的判断是,已发表结论里有很多会是假的,新因子需要 t 统计量大于 3.0。3.0 是他们那套多重检验框架下的建议,不是「把 1.96 机械改成 3」的本仓库门禁。

2. 个人账户比论文多一道筛选

论文至少留下了因子的定义。个人研究常常只留下夏普最高的那次参数。docs/workflow.md 开头写明:本轮不使用「夏普大于 1」作为通用门槛。同页的清单要求记录尝试次数。实验模板 research/templates/experiment_template.md 已有「尝试过的参数组合数」一栏。这一栏要计入放弃的规格。只记最后一次,等于把 m 写成 1。

M01 的对照仍然有效:三年、夏普 1.5,t = 1.5 √3 = 2.60;二十年、夏普 0.6,t = 0.6 √20 = 2.68。两个 t 值接近。二十年跨过的制度更多,先验上更值钱。单看 t 值会把短样本的高夏普和长样本的普通夏普当成同等证据。


🟡 进阶层:三种门槛和一次零溢价模拟

3. Bonferroni、Holm 和错误发现率

Bonferroni 是洁癖标准:一整批检验里,连一个假货都不许漏进结论,临界值按次数收紧。 Benjamini–Hochberg 是质检抽查:允许按比例残留假货,控制的是假发现占拒绝次数的期望。 相关不改变「水平 5%、全是噪声时期望有 5% 显著」这件事。它改变的是你会不会同时看见一大片显著。 个人研究日志里的 m 含放弃的规格。只报告夏普最高的一组,不能把 m 写成 1。

记排序后的 p 值为 p₍₁₎ ≤ … ≤ p₍ₘ₎,水平 α = 0.05。

方法 控制的对象 规则
Bonferroni 至少一次假阳性的概率 每个 p 与 α/m 比
Holm (1979) 同一个族错误率,逐步放宽 第 k 个要过 α / (m − k + 1),第一次失败就停止
Benjamini–Hochberg (1995) 假阳性在「被拒绝的那些」里的期望比例 最大的 k 使 p₍ₖ₎ ≤ α k/m

Benjamini & Yekutieli (2001) 在检验相关时使用更严的临界值。Harvey、Liu 与 Zhu 的附录里,BHY 在 5% 水平对应的 t 门槛约为 3.05,和他们正文的「大于 3.0」同一量级。本章手算用的是独立情形的 BH,不是 BHY。

20 个 p 值:0.001、0.004、0.012、0.02、0.03,其余 15 个都是 0.11。

Bonferroni 的临界值是 0.05/20 = 0.0025。只有 0.001 小于它。

Holm 的前两步:

0.05/20 = 0.0025, 0.001 ≤ 0.0025

0.05/19 = 0.002632, 0.004 > 0.002632

第二次失败,停止。仍然只有第一个被拒绝。

BH 的前三步临界值是 0.05/20 = 0.0025、0.10/20 = 0.005、0.15/20 = 0.0075。0.001 和 0.004 都过了各自的临界值,0.012 没有。最大的 k 是 2。BH 拒绝前两个,Holm 只拒绝第一个。错误发现率允许「被拒绝的集合里有一小部分是假的」,所以同样的 p 值可以多留一个。把 BH 的结果说成「族错误率也控制在 5%」就不对了。

独立正态、双侧、族错误率 5% 的 Bonferroni 临界 t,以及 √(2 ln m) 这个最大次序统计量的首项,随 m 增长:

m Bonferroni 临界 ‖t‖ √(2 ln m)
1 1.960 —
20 3.023 2.448
100 3.481 3.035
316 3.778 3.393

316 次独立检验若要把「至少误拒一次」的概率压在 5%,临界值是 3.78,高于 Harvey、Liu 与 Zhu 的 3.0。他们的 3.0 允许因子之间相关,也不是这张独立 Bonferroni 表。相关越强,有效的独立检验次数越少,独立 Bonferroni 就越严。JKP 把因子收成主题,罚的就是「同一主题算成几百次独立测试」。

4. t 大于 3 对应多高的夏普

M01 在收益独立、夏普用样本标准差时有

t = SR × √Y

Y 是样本里的年数。月频因子用 Y = T/12,日频回测用 Y = n/252。两边不要混。原假设下、自相关为 0 时,年化夏普估计量的标准误大约是 1/√Y。这是 Lo (2002) 公式在夏普为 0 时的特例,不是对一个已经很高的夏普再做的标准误。

由此

SRₜ₌₃ = 3/√Y, SR_(t=1.96) = 1.96/√Y

年数 t = 1.96 对应的夏普 t = 3 对应的夏普
5 0.877 1.342
10 0.620 0.949
20 0.438 0.671
50 0.277 0.424

手算十年、t = 3:√10 = 3.162,3/3.162 = 0.949。同一段样本上,t = 1.96 只要求夏普 0.620。从 2 提到 3,十年样本的夏普门槛上升约 0.33,不是把回测夏普乘以 ½。

五年样本若坚持 t > 3,夏普要到 1.34。Harvey、Liu 与 Zhu 的 3.0 是对着几十年的截面研究说的。把它抄到五年个人回测上,经济含义已经变了。本轮流程没有把这个换算写成准入线。docs/workflow.md 里历史表上的「样本外夏普大于 1」也标明是长期参考,不是本轮通用门槛。

5. 两百条零溢价策略

首项近似:m 个独立标准正态的最大值,量级是 √(2 ln m)。十年样本、标准误 1/√10 = 0.3162、m = 200:

ln 200 = 5.2983, 2 ln 200 = 10.5966, √(2 ln 200) = 3.255

0.3162 × 3.255 = 1.029

这是量级,不是期望本身。极值分布还要再减一项,首项因此偏高。脚本用种子 7 做了 20,000 次:每次 200 个独立的 N(0, 0.3162²) 年化夏普,记录其中的最大值。

量 数值
最大夏普的均值 0.868
最大夏普的 95% 分位 1.097
至少有一条夏普超过 1 的实验比例 14.5%
单格夏普超过 0.5 的频率 5.7%
200 格里超过 0.5 的期望条数 11.42

同一脚本在这 20,000 次之后,再抽了一次日频样本:200 条策略、2520 天、日收益均值真为 0、夏普用样本标准差乘 √252。这一次的最大夏普是 0.969,超过 1 的条数是 0,超过 0.5 的条数是 11。一次抽样的 0 条,不能推翻 14.5% 的实验里至少会出现一条夏普超过 1。排名以 20,000 次的分布为准。

左:200 条零溢价策略的最大夏普,20,000 次。右:French 月频在全样本和 2016–2025 的 iid t。

Bailey & López de Prado (2014, JPM 40(5)) 的 Deflated Sharpe 把尝试次数、样本长度、偏度和峰度放进「真实夏普是否还高于选择门槛」的概率。第 10 节在月频上把它算完。上表仍是零溢价、正态、独立情形下最大夏普的模拟,用来看最大值的分布,不代替那一个概率。Harvey & Liu (2015, JPM) 的回测折扣也随尝试次数上升。折扣不是固定的五成,更不是下文 McLean 与 Pontiff 的 58%。

import math

import numpy as np


def null_max_sharpe(n_exp: int = 20_000, n_strat: int = 200, years: float = 10.0) -> np.ndarray:
    """零溢价策略的最大年化夏普。均值设为 0,才是「没有溢价」的原假设。

    标准误用 1/√年数,对应独立收益、真实夏普为 0。这不是把某次回测结果打折。
    """
    rng = np.random.default_rng(7)
    se = 1.0 / math.sqrt(years)
    draws = rng.normal(0.0, se, size=(n_exp, n_strat))
    return draws.max(axis=1)

6. 三篇「复制」论文问的不是同一句话

论文 他们问的话 本章采用的原句
Harvey, Liu & Zhu (2016, RFS 29(1)) 在已经发表了 316 个因子之后,新因子的 t 要多高 大于 3.0。316 低于真实尝试次数,门槛可能更高
Hou, Xue & Zhang (2020, RFS 33(5)) NYSE 断点、市值加权之下,异象的高减低还显著吗 452 个里 65% 的 ‖t‖ 不到 1.96;交易摩擦一类 96% 不到;门槛 2.78 时失败率 82%
Chen & Zimmermann (2022, Critical Finance Review 11(2)) 沿原论文的做法,t 值能不能再现 319 个特征。原文明确显著的 161 个里,98% 的复现多空 t 大于 1.96。复现 t 对原文 t 的斜率 0.90,R² = 83%
Jensen, Kelly & Pedersen (2023, JF 78(5)) 换一种「复现」的定义,比例是多少 可比的美国原始收益口径 55.6%;他们偏好的贝叶斯全球口径 82.4%。13 个主题里 11 个的贝叶斯复制率高于 50%,例外是低杠杆和规模

98% 说的是:按原论文的股票池和加权,t 值可以再算出来。65% 说的是:换成更接近可投资的市值加权之后,很多原结果不再显著。55.6% 和 82.4% 的分母也不相同。四句话可以同时为真。不能收成「因子要么都能复制,要么都是假的」。

Feng, Giglio & Xiu (2020, JF 75(3)) 问的是下一条:控制了一大组已有因子之后,新因子还有没有增量。很多新因子在这一关上是冗余的。增量例子不写成「只剩盈利」这种名单。M07 已经算过一件相关的事:HML 对 CMA 的斜率约等于 1,月截距的 t 值是 −0.28。那是增量检验的一个例子,不是 Feng 等人的输出。

7. 发表后下降的 26% 和 58% 是预测变量的收益

McLean & Pontiff (2016, JF 71(1)) 研究 97 个预测变量。原样本结束到发表之前,收益下降约 26%。发表之后,下降约 58%。他们把多出来的大约 32 个百分点联系到读者按论文去交易。下降在更容易套利的股票里更明显。

这两个百分比是那 97 个预测变量的平均收益相对原文样本的降幅。它们不是:

  • 任何一条策略的夏普都乘以 1 − 0.58;
  • French 的 HML 或 Mom 已经按 58% 衰减过;
  • 个人回测在扣成本之前先打五折的规则。

Novy-Marx & Velikov (2016, RFS 29(1)) 按换手给异象分类,说明成本对高换手的实现是一阶的。港股印花税买卖双方各 0.1% 的量级见 M00。月换仓的学术多空,在个人账户上要先过这一项,再谈 58%。


🔴 深入层:衰减、拥挤和流程

8. 先验写在看数之前

统计门槛拦的是「噪声里的最大值」。它不提供经济方向。看完数据再补故事,故事会跟着最大的那个 t 值走。实验目录里的假设要先写预期方向和窗口,再跑。M03 的前视和滚动挑选,是同一条纪律在数据时点上的版本。

行为上的持续可以来自套利限制:借不到券、资金期限短、噪声交易者把价格推得更远。Baker & Wurgler (2006, JF 61(4)) 发现,高情绪时期之后,小盘、年轻、高波动、不盈利这一类股票的随后收益更低。这是他们的样本结论,不是一条可以每月切换的情绪开关。本章不把情绪指数接进下单。

一个因子上线之后表现差,至少分开三件事:路径仍落在回测的回撤分布里;月均相对发表时下降,但定义还在;制度变了,定义本身不再对应原来的交易。M01 已经说明夏普的估计误差大。六个月的亏损很少能够单独做成一次 t 检验。M04 的单位净值闸门处理的是账户能不能继续扛,不是用六个月数据宣布因子死亡。

9. 本轮流程里已经写下的,和本章不改的

动作 位置 本章的用法
记录尝试次数,含放弃的规格 docs/workflow.md 清单;实验模板第 4 节 m 用这个数,不用 1
本轮不用「夏普大于 1」做通用门槛 docs/workflow.md 开头 t > 3 同样不写回去
时间切分,测试段只用一次 同页清单 挑选发生在测试段之外
同时报告预先写定的窗口和后一段 本章清单新增的一句 对上 M07 的 2016–2025

基本面量化是把已经有经济方向的规则写成可检验的信号,再用上面的 m 和窗口去压它。方向来自主观经验,不免除前视和成本。事件和公告日上的检验留在后面的专题,不把月频因子的 t 值挪过去。

10. Deflated Sharpe:门槛、偏度和峰度进同一个概率

PSR 是只和 0 比的及格率。100 次尝试里,瞎蒙也能摸到一条最高分,那条最高分的期望就是门槛 SR₀。 扣掉这道门槛、再计入偏度和厚尾之后,同一条标准误上的概率从 0.999555 降到 DSR 0.659086。 下降主要来自选择,不是把夏普乘上一个固定折扣。0.659086 也不是本轮准入线。 年化夏普 1.20 已经乘过 √12,不能再和月数 120 放进同一条标准误。

第 5 节的模拟回答的是:零溢价、正态、独立时,最大年化夏普的分布在哪里。Deflated Sharpe Ratio(Bailey & López de Prado, 2014, JPM 40(5))回答的是另一句:你挑出来的那一条,真实夏普超过「多次尝试造成的门槛」的概率有多大。两句不要合成一个折扣。

夏普、门槛和标准误都用与收益相同的频率,这里是月。年化夏普 1.20 先换成月夏普

ˆSR = 1.20/√12 = 0.346410

再进入下面三步。把 1.20 和 T = 120 直接代进标准误,等于把年化尺度的夏普和月频的期数乘在一起。

第 1 步。 M 次独立试验下,标准正态最大值的期望用 Euler–Mascheroni 常数近似。本章把 γ 固定为 0.5772156649,不再加更长的尾数。

Z₀ = (1 − γ) Φ⁻¹(1 − 1/M) + γ Φ⁻¹(1 − 1/(Me))

M = 100 时,1 − 1/M = 0.99,Me = 271.828183,1 − 1 / (Me) = 0.996321。对应的分位数是 2.326348 和 2.680210。加权之后

Z₀ = 2.530603

尝试集合的月夏普截面标准差记为 σ_({ˆSR})。本例给定 0.12,它描述 100 条尝试彼此差多远,不是选中那一条自己的标准误。基准门槛

SR₀ = 0.12 × 2.530603 = 0.303672

第 2 步。 选中策略的夏普估计还有自己的标准误。ˆγ₃ 是收益的矩偏度,ˆγ₄ 是 Pearson 峰度,正态参照是 3,不是超额峰度 0。

ˆSE(ˆSR) = √((1 − ˆγ₃ ˆSR + ((ˆγ₄ − 1) / 4) ˆSR²) / (T − 1))

T = 120,偏度 −0.5,Pearson 峰度 5.0。因为 (1.20)² / 12 = 0.12,分子是

1 + 0.5 × 0.346410 + 0.12 = 1.293205

正态、偏度为 0、峰度为 3 时,分子只到 1 + 0.5 × 0.12 = 1.06,标准误是 0.094380。负偏和厚尾把它抬到

ˆSE = √(1.293205/119) = 0.104246

峰度若误用超额值 2,(2 − 1) / 4 会把厚尾那一项缩掉。5.0 是 Pearson。

第 3 步。 紧缩夏普是标准正态在「估计值超过门槛」处的分布函数:

DSR = Φ((ˆSR − SR₀) / ˆSE(ˆSR))

注:函数内部的比值打印到六位是 0.409970,对应概率 0.659086。用表里已经收成六位的 0.346410、0.303672、0.104246 再除,会得到 0.409973,末位差 0.000003。正文以函数内部的六位为准。

只和 0 比、还没放进尝试次数时,同一条标准误给出

PSR = Φ(0.346410/0.104246 的未舍入比值) = 0.999555

量 六位打印 它是什么
月夏普 0.346410 1.20/√12
Z₀ 2.530603 100 个标准正态最大值的近似期望
SR₀ 0.303672 0.12 × Z₀,选择门槛
分子 1.293205 含偏度和 Pearson 峰度
ˆSE 0.104246 选中这一条的标准误
标准化 0.409970 (ˆSR − SR₀) / ˆSE
PSR 0.999555 门槛还放在 0 上
DSR 0.659086 门槛放在 SR₀ 上

年化夏普 1.20、十年样本,若只做一次试验,1.20 √10 ≈ 3.79,看起来过了 t > 3。负偏和峰度把标准误从 0.094380 抬到 0.104246;100 次尝试再把要超过的月夏普从 0 抬到 0.303672。剩下的差额只有 0.41 个标准误,概率从 0.999555 降到 0.659086,过不了 0.95。下降主要来自选择门槛,不是来自把夏普乘上 0.58。

0.659086 也不是本轮流程的准入线。docs/workflow.md 开头仍然不用「夏普大于 1」。DSR 要的四样输入是期数、尝试次数、尝试集合的截面标准差、以及这条收益的偏度和峰度。缺一个,就不要套这一个概率。

import math


def norm_cdf(z: float) -> float:
    return 0.5 * (1.0 + math.erf(z / math.sqrt(2.0)))


def norm_ppf(p: float) -> float:
    # 默认环境没有 scipy。80 步二分与绘图脚本同一实现。
    lo, hi = -8.0, 8.0
    for _ in range(80):
        mid = 0.5 * (lo + hi)
        if norm_cdf(mid) < p:
            lo = mid
        else:
            hi = mid
    return 0.5 * (lo + hi)


def deflated_sharpe(
    sr: float,
    sr_std: float,
    n_trials: int,
    n_obs: int,
    skew: float,
    kurtosis: float,
) -> float:
    """夏普与收益同一频率。截面标准差只用来做门槛,不代替这一条的标准误。"""
    gamma = 0.5772156649
    z0 = (1.0 - gamma) * norm_ppf(1.0 - 1.0 / n_trials)
    z0 += gamma * norm_ppf(1.0 - 1.0 / (n_trials * math.e))
    sr0 = sr_std * z0
    # Pearson 峰度。正态是 3;(kurtosis - 1) / 4 才回到 SR^2 / 2。
    scale = 1.0 - skew * sr + (kurtosis - 1.0) / 4.0 * sr * sr
    se = math.sqrt(scale / (n_obs - 1))
    return norm_cdf((sr - sr0) / se)


print(f"{deflated_sharpe(1.20 / math.sqrt(12.0), 0.12, 100, 120, -0.5, 5.0):.6f}")

figures/M08/plot_multiple_testing.py 的 selection_bias_demo 打印上表。可选库没装上时,分位数用上面的二分法,并降级打印 scipy_norm skipped。装上之后,对照调用是 scipy.stats.norm.ppf 与 scipy.stats.norm.cdf。六位结果在未安装时仍以上表为准。

11. 四个分块上的过拟合概率

CSCV 把时间切成几块,反复让一块当模拟考、另一块当期末考。 PBO 是模拟考状元在期末考掉进后半名的比例。这张 4×4 构造矩阵上是 4/6 = 0.666667。 它描述的是这张矩阵上的选择,不是「策略有三分之二的概率是假的」。 换一张构造矩阵,比例就换。训练块 (1,3) 和 (2,3) 的冠军没有掉进后半区。

CSCV(Bailey, Borwein, López de Prado & Zhu, 2017, Journal of Computational Finance 20(4))把时间切成 S 段等长的块,S 为偶数。每次拿 S/2 段做样本内,剩下的做样本外。组合数是 C(S, S/2)。互补的两组都要各算一次,不能只留一半。PBO 是:样本内冠军在样本外的名次落入后半区的比例。名次从 1 起,1 最好。后半区指名次大于列数的一半。

下面 4 行 4 列是构造的分块超额,只用来排序,不是一段行情。列 A、B、C、D 是四条策略。

分块 A B C D
1 1 −3 1 −1
2 7 7 −2 2
3 0 2 8 6
4 −6 4 6 −2

C(4, 2) = 6。得分用两块之和。等长时,和与均值的排序相同。四列在每一次切分里都没有并列,所以冠军和名次不依赖排序是否稳定。

训练块 样本内 A,B,C,D 冠军 样本外 A,B,C,D 名次 后半区
1,2 8, 4, −1, 1 A −6, 6, 14, 4 4 是
1,3 1, −1, 9, 5 C 1, 11, 4, 0 2 否
1,4 −5, 1, 7, −3 C 7, 9, 6, 8 4 是
2,3 7, 9, 6, 8 B −5, 1, 7, −3 2 否
2,4 1, 11, 4, 0 B 1, −1, 9, 5 4 是
3,4 −6, 6, 14, 4 C 8, 4, −1, 1 4 是

后半区出现 4 次。

PBO = 4/6 = 0.666667

没有落入后半区的是训练块 (1,3) 和 (2,3)。A 在前两块上是冠军,换到后两块就排到第 4。这是这张构造矩阵上的选择反转,不能写成「真实策略有 ⅔ 的概率过拟合」。列数、分块数或收益换了,比例就换了。

PBO 也不决定账户能不能做空。它只说挑选本身在这张矩阵上有多容易挑中样本内的幸运儿。只做多、杠杆上限 1.0 的账户,仍然不能用一条空头腿去「对冲」这个比例。

import numpy as np

blocks = np.array(
    [
        [1.0, -3.0, 1.0, -1.0],
        [7.0, 7.0, -2.0, 2.0],
        [0.0, 2.0, 8.0, 6.0],
        [-6.0, 4.0, 6.0, -2.0],
    ]
)
# 六种切分都打印。后半区记 1。PBO 是这些 1 的比例。
summary = probability_of_backtest_overfitting(blocks)

真实案例

案例 1:全样本 t 值过了 3,后十年可以不过 0

右图的 t 值来自 M07 同一份 202608 月频,1963-07 至 2026-08 对 2016-01 至 2025-12。动量全样本 t = 3.90,后十年 t = 0.08。投资全样本 t = 3.23,后十年 t = −0.24。价值全样本 t = 2.72,后十年 t = −0.12。盈利后十年仍有 t = 1.48,没有过 1.96。市场后十年 t = 2.60。

这些因子在 1992、1993 年的论文之后并没有立刻失去全样本里的正号。价值从 2006-12 高点到 2020-09 的回撤是 −57.78%,2026-08 仍比该高点低 32.06%(M07)。这和「发表后不久就衰减 58%」不是同一条时间路径。McLean & Pontiff 的 26% 和 58% 是 97 个预测变量的平均降幅,不是 HML 这一列的乘数。

Arnott, Harvey, Kalesnik & Linnainmaa (2021, FAJ 77(1)) 强调价值这段回撤里估值价差的扩大。Maloney & Moskowitz 的 2020 年 5 月工作论文则说明利率并不是稳定的择时信号。两篇都没有授权把后十年的 t = −0.12 写成「价值已被证伪」,也没有授权把 2021 年以来 68 个月的正均值写成「回撤已经结束」。

案例 2:2007 年 8 月的同质减仓

Khandani & Lo (2007, Journal of Investment Management, 第四季度) 用 Lehmann (1990) 与 Lo & MacKinlay (1990) 的反转策略描述 2007-08-06 那一周。这不是 French 的 HML 或 Mom。无杠杆策略在 8 月 10 日回到 +5.92%,当周累计 −0.43%。他们按 2006 年大约 8:1 的杠杆、把无杠杆收益乘以 4 之后,8 月 7 日至 9 日的收益是 −4.64%、−11.33%、−11.43%,三天去掉约四分之一。无杠杆的中间市值组更差,第 4 组三天累计 −9.33%。8 月 9 日标普 500 下跌 2.95%,解释不了 7 日和 8 日市场中性策略已经在跌。论文转述的媒体口径是,一些量化基金当月跌幅在 −5% 到 −30%。那是报道的范围,不是本章重算的基金净值。

机制是:相近的多空持仓被一起减掉,价格冲击再触发下一轮减仓;冲击过去之后,周五出现大幅回升。当周无杠杆 −0.43% 和杠杆后三天约 −25% 可以同时为真。杠杆倍数决定账户看到的是哪一个数。本轮杠杆上限 1.0,复制不了他们的 8:1。周四按亏损砍掉仓位的规则,会离开周五那一笔无杠杆 +5.92% 的回升。这只说明止损改变的是路径,不说明应该在清算周里取消风控。

案例 3:2024 年初的小微盘拥挤

M00 案例 3 的窗口是 2024-01-02 至 2024-02-05 前后。拥挤的小市值暴露加上杠杆产品的减仓,使现货小盘的流动性和超额一起变差。中证 2000 在该窗口下跌约三成,点位以中证指数公司为准。媒体统计的指增超额回撤大约是两位数,各家样本不同。本章不另给一个超额百分比。

Hou、Xue 与 Zhang 的交易摩擦一类里,96% 过不了 ‖t‖ ≥ 1.96。小盘、低价差股票上的研究溢价,和个人账户能成交的部分不是同一个数。港股还要加上指定可沽空名单和印花税。等权或微盘上的 t 值,先经过这两项再谈有没有增量。


方法论来源

概念 / 模型 核心文献或规范出处
因子动物园 Cochrane (2011), JF 66(4);Harvey, Liu & Zhu (2016), RFS 29(1)
Holm Holm (1979)
BH 与 BHY Benjamini & Hochberg (1995);Benjamini & Yekutieli (2001)
夏普标准误 Lo (2002),夏普为 0 时约为 1/√Y
Deflated Sharpe 与 PBO Bailey & López de Prado (2014), JPM 40(5);Bailey, Borwein, López de Prado & Zhu (2017), Journal of Computational Finance 20(4)。月频手算见第 10–11 节
推荐门槛 Harvey, Liu & Zhu 建议 t 大于 3.0。316 个检验的临界值不是他们的推荐值
图 plot_multiple_testing.py,种子 7

💡 思考回顾与巩固

核心认知纠偏

误区 反例 用法
1000 次检验大约 50 次显著,所以相关一高,假阳性就会少很多 期望个数始终是 mα。全相关时,次数是 0 或 1000,概率 5% 时期望仍是 50 相关改变的是最大值和主题是否重复
t > 3 就是 316 次独立 Bonferroni 独立 Bonferroni 在 m = 316 时是 3.78。HLZ 的建议是大于 3.0,并允许相关 两个门槛分开引用
发表后衰减 58%,所以夏普 1.2 的未来值是 0.50 58% 是 97 个预测变量的收益降幅。零溢价、200 次尝试、十年样本的最大夏普均值是 0.868 不把 0.58 乘到任意夏普上
回测夏普要统一打五折 Harvey & Liu (2015) 的折扣随尝试次数变。本章首项近似 1.03,模拟均值 0.87 先数 m,再算原假设下的最大值
原论文复现了,就说明换市值加权仍然显著 Chen & Zimmermann 的 98% 是原方法。HXZ 的 65% 失败是另一套持仓 两套设计都要写
全样本 t > 3 的因子,后十年不会接近 0 动量 3.90 对 0.08,投资 3.23 对 −0.24 后一段窗口和全样本一起报
2007 年 8 月当周策略只跌了 0.43%,所以杠杆账户也一样 无杠杆当周 −0.43%;他们的 8:1 三天约 −25% 写明杠杆
一次种子的最大夏普 0.969,说明夏普超过 1 不会出现 20,000 次里有 14.5% 的实验至少出现一条 用分布,不用一次抽样
年化夏普 1.20 的 DSR 仍接近 1 月频上 PSR 是 0.999555,放进 100 次尝试后 DSR 是 0.659086 门槛用 SR₀,不是把夏普乘 0.58

费曼自测

  1. 为什么检验相关时,假阳性个数的期望仍然是 mα?那相关还改变了什么?
  2. 同一组 20 个 p 值,为什么 Holm 拒绝 1 个、BH 拒绝 2 个?
  3. 十年样本里 t 从 1.96 提到 3,夏普门槛上升多少?这和「打五折」是同一个操作吗?
  4. Chen & Zimmermann 的 98% 和 Hou、Xue 与 Zhang 的 65% 失败率,哪一个在说可投资组合?
自测要点 1. 期望可加,不需要独立性。每个 Iⱼ 的期望都是 α,加起来就是 mα。相关改变个数的方差,也改变最大 t 值的分布。因子若是同一主题的副本,独立 Bonferroni 会把它们当成 316 次互不影响的测试,门槛偏严。 2. Holm 在第二个 p 值 0.004 处对着 0.002632 失败,并且停止。BH 允许第二个 p 值对着 0.005 通过,第三个 0.012 对着 0.0075 失败,所以最大 k = 2。BH 控制的是发现集合里的假阳性比例,不是「一个假阳性都不许有」。 3. 1.96/√10 = 0.620,3/√10 = 0.949,上升 0.329。打五折会把 0.620 收到 0.310,那是另一个数。尝试次数上升时,原假设下的最大夏普会上移,上移多少要用 m 和年数算,不能用固定成数。 4. 98% 是沿原论文做法复现 t > 1.96 的比例。65% 是换成 NYSE 断点和市值加权之后 ‖t‖ 达不到 1.96 的比例。更接近「可投资、且不被微盘主导」的是后者。前者说明原文结果多半不是算错,而是换规则之后会变。

四维习题

A. 概念题

  1. 写出假阳性个数期望等于 mα 的理由。相关为什么不能拿来把 50 改小?
  2. Harvey、Liu 与 Zhu 的 t > 3 从哪来?它为什么不等于 m = 316 的独立 Bonferroni 临界值 3.78?
  3. 列出发表后收益变小的三类原因,每一类对上哪一篇文献或本章的哪一张表。
  4. 一个策略上线之后落后于回测,怎样区分「还在历史回撤里」「月均已经下降」和「交易制度变了」?

B. 计算题

  1. 用第 3 节的 20 个 p 值复核 Bonferroni、Holm 和 BH 各拒绝几个。
  2. 样本 10 年和 50 年,分别计算 t = 1.96 和 t = 3 对应的年化夏普。
  3. 计算 √(2 ln 200) / √10。它和模拟得到的最大夏普均值 0.868 为什么不一样?
  4. 三年、夏普 1.5,以及二十年、夏普 0.6。计算两个 t 值。哪一个离 t = 3 更近?
  5. 用第 10 节的输入,写出月夏普、Z₀、SR₀、ˆSE、PSR 和 DSR。为什么年化夏普 1.20 不能直接代进标准误?
  6. 第 11 节的六次切分里,哪两次的冠军没有落入后半区?PBO 是多少?这个比例是不是「策略有 ⅔ 的概率是假的」?

C. 编程实战题

  1. 运行 figures/M08/plot_multiple_testing.py,核对 20,000 次模拟的均值 0.868、95% 分位 1.097,以及至少一条夏普超过 1 的比例 14.5%。
  2. 在同一输出里核对六条 French 因子的全样本 t 和 2016–2025 的 t。确认右图用的是 iid t,不是 Newey–West。
  3. 把脚本里的日频那一次单独改到种子的第一步,说明最大夏普不必仍是 0.969。本章引用的 0.969 依赖脚本里的抽取顺序。

D. 个人实战思考题

  1. 你试了 30 组参数,只把夏普最高的一组写进实验模板。清单上的「尝试次数」应填多少?这和 Bonferroni 的 m 是什么关系?
  2. 杠杆上限 1.0。Khandani 与 Lo 的 8:1 三天跌幅,哪一部分不能当成你的账户路径?
  3. 港美股账户准备检验一个新的月频特征。在看收益之前要写下哪四项,才能和本章的图对上?
完整参考答案与解析 **A1.** Iⱼ 是第 j 次被拒绝的指示变量,E[Iⱼ] = α。和的期望等于期望的和,所以总数的期望是 mα,无论 Iⱼ 是否相关。m = 1000、α = 0.05 时期望是 50。全相关时,要么一次都不拒绝,要么 1000 次一起拒绝;后者的概率若是 0.05,期望仍是 50。相关改变的是你看到的「独立主题」个数,以及最大 t 值。 **A2.** 316 是他们从 313 篇文章里数出的因子个数,并且低于未发表的失败测试。大于 3.0 是那篇论文的多重检验建议;附录里的 BHY 门槛约 3.05。独立、双侧、族错误率 5% 的 Bonferroni 在 m = 316 时是 3.778。后者把每次检验当成互不相关。因子主题相关时,这个 3.78 偏严。两个数都不要写成对方。 **A3.** 统计筛选:本章第 1 节的 50 次假阳性,以及 Harvey、Liu 与 Zhu 对未发表失败测试的提醒。套利和拥挤:McLean & Pontiff 把发表后相对样本外多出来的约 32 个百分点联系到按论文交易;案例 2 的 2007 年 8 月是同质持仓被一起减掉。交易成本:Novy-Marx & Velikov 按换手分类;M00 的港股印花税是个人账户上的对应项。三者都不是「夏普乘 0.42」。 **A4.** 先看亏损是否还落在回测已经出现过的回撤分布里,并记住六个月的夏普误差很大,常常做不成拒绝。再看同一条定义在预先留下的后一段窗口里,月均和 t 值是否还在,M07 的 2016–2025 就是这种对照。最后看交易规则是否变了,例如卖空名单、印花税、杠杆上限。制度变了就不是同一条策略的回撤。 **B5.** Bonferroni 临界值 0.0025,只有 0.001 通过。Holm 第一步临界值同样是 0.0025,通过;第二步 0.05/19 = 0.002632,0.004 失败并停止,只拒绝 1 个。BH 第二步临界值 0.005,0.004 通过;第三步 0.0075,0.012 失败。最大 k = 2,拒绝前两个。 **B6.** 10 年:1.96/√10 = 0.620,3/√10 = 0.949。50 年:1.96/√50 = 0.277,3/√50 = 0.424。同样的 t 门槛,样本越短,要求的夏普越高。 **B7.** ln 200 = 5.2983,2 ln 200 = 10.5966,平方根 3.255。除以 √10 得 1.029。模拟均值 0.868 更低,因为标准正态最大值的期望比首项 √(2 ln m) 小。本章引用分布时用 0.868 和 95% 分位 1.097,首项只用来看量级。 **B8.** 1.5 √3 = 2.598,0.6 √20 = 2.683。二十年的 t 值略高,两者都低于 3。离 3 更近的是 2.683。证据强度接近,但二十年覆盖的制度更多。短样本上为了让 t 超过 3,夏普要到 3/√3 = 1.732,高于这里的 1.5。 **B9.** 月夏普 0.346410,Z₀ = 2.530603,SR₀ = 0.303672,分子 1.293205,ˆSE = 0.104246,标准化 0.409970,PSR 0.999555,DSR 0.659086。1.20 已经乘过 √12。标准误的 T − 1 = 119 是月数。把 1.20 和 120 放进同一条式子,频率就错了。用六位表内的三个数再除,得到的是 0.409973,不采用。 **B10.** 训练块 (1,3) 和 (2,3) 的名次是 2,没有进入后半区。其余四次进入。PBO = 4/6 = 0.666667。这是这张 4×4 构造矩阵上的比例,不是一条策略为假的概率。换矩阵,比例就换。 **C11–C13.** 与 `figures/M08/plot_multiple_testing.py` 同一契约。种子 7。20,000 次渐近模拟:最大夏普均值 0.868,95% 分位 1.097,至少一条超过 1 的比例 0.145,超过 0.5 的期望条数 11.42。日频那一次发生在渐近模拟之后:最大夏普 0.969,超过 1 的有 0 条,超过 0.5 的有 11 条。French 六列的 t 值就是案例 1 和第 5 节的图,标准误按月收益独立同分布,没有 Bartlett 核。把日频抽到种子的第一步会换成另一条路径,0.969 不再适用。同一脚本还打印 DSR 0.659086、PBO 0.666667,以及 `scipy_norm skipped`。
import math

import numpy as np


def record_trials(tried: int, reported: int) -> int:
    """多重检验的 m 用试过的规格数。只汇报最优的那一次时,reported 不能代替 tried。"""
    if tried < reported:
        raise ValueError("尝试次数不能少于写进结论的规格数")
    return tried


def sharpe_hurdle(t_star: float, years: float) -> float:
    # 年数用样本长度除以一年的期数。月频是 T/12,日频是 n/252。
    return t_star / math.sqrt(years)
**D14.** 填 30。Bonferroni 和「最大夏普」的原假设分布用的 m 都是 30,不是最后留下的 1。只填 1 会把临界值假装成单次检验的 1.96。放弃的 29 组若和留下的那组高度相关,有效独立次数低于 30,但备忘录仍先写下 30,再说明相关,而不是直接改成 1。 **D15.** −4.64%、−11.33%、−11.43% 是他们把无杠杆反转收益乘以 4 之后的路径,对应大约 8:1 的杠杆。杠杆上限 1.0 的账户走不到这条路径。无杠杆当周累计是 −0.43%,周五单日 +5.92%。账户若在周四的亏损上触发了自己的闸门,看到的是减仓之后的净值,不是论文里继续持有到周五的研究曲线。中间市值组的无杠杆三天跌幅,例如第 4 组 −9.33%,还说明冲击集中在他们当时更能交易的那一段,不是全市场均匀的一天。 **D16.** 四项都要在看收益之前写下来。第一,特征的定义和形成日,会计数据要像 M07 那样从下一月才开始乘收益。第二,预期方向,高减低还是低减高,不能看完再选。第三,预先指定的样本窗口,以及准备并列报告的后一段,右图用的是全样本对 2016–2025。第四,尝试次数的计数口径,包括放弃的参数。看完再补的「经济故事」不计入这四项。

📌 章节知识总结

一句话核心洞察

  1. 1000 次水平 5% 的检验,在全是噪声时期望有 50 次显著。相关不改变这个期望。
  2. 独立 Bonferroni 在 316 次时是 ‖t‖ > 3.78。Harvey、Liu 与 Zhu 的建议是 t > 3.0,两者不是同一个门槛。
  3. 十年样本上,t = 3 对应夏普 0.949,t = 1.96 对应 0.620。这不是把夏普乘以 0.42 或 0.5。
  4. 200 条零溢价、十年样本,最大夏普的模拟均值是 0.868,95% 分位 1.097。首项近似 1.029 偏高。
  5. 动量的全样本 t = 3.90,2016–2025 的 t = 0.08。原方法能复现,和后一段窗口仍然显著,是两件事。
  6. 年化夏普 1.20 在这组月频输入下,PSR 是 0.999555,DSR 是 0.659086。下降来自选择门槛和厚尾,不是乘上 0.58。
  7. 四块矩阵的 PBO 是 0.666667。它只描述这张构造矩阵上的选择,不写成策略为假的概率。

公式与样本

对象 表达式或数值 前提
假阳性期望 mα 期望可加,不要求独立
Bonferroni 临界 p 为 α/m 20 次、5% 时为 0.0025
Holm 第二步 α / (m − k + 1) 0.004 对 0.002632 失败
BH 最大 k 使 p₍ₖ₎ ≤ α k/m 本例 k = 2;假定独立
m = 316 的独立临界 t 3.778 双侧,族错误率 5%
HLZ t > 3.0 他们的框架;316 偏低
夏普门槛 t/√Y 独立收益;月频 Y = T/12
十年、t = 3 夏普 0.949 3/√10
最大夏普首项 √(2 ln m) / √Y m = 200、十年时为 1.029
模拟均值 0.868 20,000 次,种子 7
2016–2025 动量 t 0.08 全样本 3.90;iid
McLean & Pontiff 样本外约 −26%,发表后约 −58% 97 个预测变量的收益,不是夏普乘数
月夏普 0.346410 1.20/√12,T = 120,M = 100
Z₀ 与 SR₀ 2.530603 与 0.303672 σ_({ˆSR}) = 0.12,与收益同频率
ˆSE 0.104246 偏度 −0.5,Pearson 峰度 5.0,分母 T − 1
PSR 与 DSR 0.999555 与 0.659086 门槛从 0 换成 SR₀
PBO 0.666667 S = 4,6 种切分里 4 次落入后半区

跨模块

结论 交给 位置
t = SR √Y 短样本高夏普和长样本普通夏普可以有相近的 t M01
尝试次数含放弃的规格 实验记录 ../../../docs/workflow.md,../../../research/templates/experiment_template.md
后一段窗口的 t 值 不要只留全样本 M07,../figures/M08/multiple_testing.png
原方法复现不等于市值加权仍显著 复制率必须带口径 M06
8:1 的三天跌幅 杠杆上限 1.0 的账户走不到 M04,../../../docs/risk_policy.md
2024 年初小微盘 约三成是中证 2000 的指数跌幅 M00 案例 3
公告日上的事件 不把月频 t 值挪去当持有期 M19
只做多账户上的第二层过滤 不靠空头腿消化多次尝试 M16

学习指引

概念卡片:多重检验门槛 · 紧缩夏普与元标签

  • 精读:因子第 6 章的数据窥探、发表后失效和行为解释。黑箱第 9 章先写假设、第 11 章对回测数量的批评。陈 3.4 的数据窥探和本章第 5 节是同一件事。
  • 对照:26% 与 58% 只描述 McLean & Pontiff 的 97 个预测变量。French 窗口以 M07 和本章脚本的打印为准。
  • 项目:不把 t > 3 或「夏普大于 1」写回本轮准入。尝试次数继续记在实验模板已有的那一栏。
  • 下一步:M09 把留下的信号放进信息比率和广度。本章的模拟说明,广度若来自反复试参数,而不是来自新的独立赌注,信息比率的公式用不上。

自检清单

  • 能说明 1000 次检验的期望假阳性是 50,而且相关不改变这个期望
  • 能手算 20 个 p 值上 Holm 拒绝 1 个、BH 拒绝 2 个
  • 能把十年样本的 t = 3 换算成夏普 0.949,并说出它不是 58% 折扣
  • 能复述最大夏普的模拟均值 0.868、95% 分位 1.097,以及一次日频抽样的 0.969 为什么不能单独引用
  • 能把 Chen & Zimmermann 的 98%、HXZ 的 65% 和 JKP 的 55.6% 与 82.4% 各放回它们的定义
  • 能指出 2007 年 8 月无杠杆当周 −0.43% 和 8:1 三天约 −25% 来自同一篇论文的两种杠杆
  • 能复述这组月频输入下的 DSR 0.659086,并说明它不是 PSR 0.999555,也不是夏普乘 0.58
  • 能用手算四块矩阵的 PBO 0.666667,并指出 (1,3) 与 (2,3) 没有落入后半区