跳到正文

Romano-Wolf 逐步下降法:兼顾统计功效的 Bootstrap 多重检验

你的 50 个策略不是 50 个独立检验。Bonferroni 假装它们是,Romano-Wolf 用 bootstrap 把真正的有效检验数测出来——这就是它多出来的功效的全部来源。

第 11 篇 · 共 12 篇 约 15 分钟

你有 50 个策略,全都是同一个动量信号的不同变体。Bonferroni 拒绝 1 个,Holm 拒绝 3 个。你的直觉是这个结果太苛刻了,而且你的直觉是对的——但理由不是”检验太严”,而是这 50 个检验根本不是 50 个检验。

如果这 50 个策略的收益率两两相关性是 0.95,它们提供的独立信息大约相当于 3 个检验,不是 50 个。Bonferroni 把阈值按 50 分,Holm 按排序稍微放松一点,但两者都不看你的策略之间到底有多像。Romano-Wolf 逐步下降程序做的就是这件事:用 bootstrap 把”有效检验数”从数据里测出来,再据此定阈值。

本文推导这个程序,走一遍 bootstrap 逐步下降逻辑,并说清楚它多出来的功效具体从哪里来、在什么条件下这些功效会消失。

功效问题:有效检验数

Bonferroni 和 Holm 都只用一个输入——检验数量 mm——来设阈值。它们不看检验统计量之间的依赖结构。在正相关下,有效独立检验数远小于 mm,所以这两个阈值都定得过严。

这句话可以直接量化。取 m=30m = 30 个 studentized 检验统计量,在原假设下服从标准正态,两两等相关系数为 ρ\rho。我们关心的是 max⁡iti\max_i t_i 在原假设下的分布——因为控制 FWER 就等于控制”最大值超出阈值”这件事的概率。下面是 20 万次抽样得到的第 95 百分位,以及反解出来的有效检验数(满足 Φ(q)meff=0.95\Phi(q)^{m_{\text{eff}}} = 0.95 的那个 meffm_{\text{eff}}):

两两相关性 ρ\rhomax⁡iti\max_i t_i 原假设分布的 95 百分位有效检验数 meffm_{\text{eff}}
0(独立)2.9229.3
0.52.7517.2
0.92.234.0
0.991.841.5

第一行是这张表的自检:独立时反解出来的有效检验数是 29.3,基本就是 30,说明这套算法没有偏。Bonferroni 在 α=0.05\alpha = 0.05、m=30m = 30 下的单侧阈值是 z=2.94z = 2.94,和第一行的 2.92 几乎重合——这就是 Bonferroni 的全部内容:它永远按”独立”那一行给你阈值,不管你的策略实际长在哪一行。

而量化研究的策略池几乎从不落在第一行。同一个动量信号换 5 个回看窗口、再乘 6 个股票池,得到的 30 条收益率曲线相关性轻松到 0.9 以上,也就是表里第三行:真正的阈值是 2.23,Bonferroni 却给你 2.94。一个真实年化夏普 0.8、跑了 10 年的策略,t 统计量大约是 0.8×10=2.530.8 \times \sqrt{10} = 2.53。它过得了 2.23,过不了 2.94。Bonferroni 会把它当噪声扔掉。

Romano-Wolf(2005)通过 bootstrap 计算修正 p 值。修正 p 值自动落在你的策略池实际所在的那一行,因此程序在同样控制 FWER 的前提下,能比 Bonferroni 或 Holm 拒绝更多原假设。

算法

  1. 对每个策略 ii,在原始数据上计算检验统计量 tit_i(例如 studentized 夏普)。
  2. 对每次 bootstrap 迭代 b=1,…,Bb = 1, \ldots, B:
    • 重采样数据(时间序列数据用 block bootstrap)。
    • 对每个策略 ii,在原假设下计算 bootstrap 检验统计量 ti(b)t_i^{(b)},得到一个 B×mB \times m 的矩阵。
  3. 把原始检验统计量从大到小排序:t(1)≥t(2)≥…≥t(m)t_{(1)} \ge t_{(2)} \ge \ldots \ge t_{(m)}。
  4. 逐步下降。令 Kk={(k),(k+1),…,(m)}K_k = \{(k), (k+1), \ldots, (m)\} 是走到第 kk 步时还没有被拒绝的那些假设,取 bootstrap 的最大值只在这个集合上取:
Mk(b)=max⁡j∈Kktj(b)M_k^{(b)} = \max_{j \in K_k} t_j^{(b)}

第 kk 个假设的 Romano-Wolf 修正 p 值是

p(k)RW=max⁡{p(k−1)RW, 1B∑b=1B1[Mk(b)≥t(k)]}p_{(k)}^{RW} = \max\left\{ p_{(k-1)}^{RW},\ \frac{1}{B} \sum_{b=1}^{B} \mathbb{1}\left[M_k^{(b)} \ge t_{(k)}\right] \right\}

其中 p(0)RW=0p_{(0)}^{RW} = 0,外层取 max 是为了保证修正 p 值随排序单调不减。若 p(k)RW≤αp_{(k)}^{RW} \le \alpha 就拒绝 H(k)H_{(k)} 并进入第 k+1k+1 步;否则停止,剩下的假设全部不拒绝。

关键在第 4 步里那个 KkK_k。它每走一步就少一个元素,而 max⁡\max 是在一个越来越小的集合上取的,所以 Mk(b)M_k^{(b)} 的分布随 kk 单调左移,后面的假设面对的阈值越来越低。这就是”逐步下降”这四个字的全部含义,也是它比单步版本更有功效的唯一原因。

单步版本,以及差在哪

作为对照,更简单的单步(single-step)版本永远在全部 mm 个假设上取最大值:

pisingle=1B∑b=1B1[max⁡j=1,…,mtj(b)≥ti]p_i^{\text{single}} = \frac{1}{B} \sum_{b=1}^{B} \mathbb{1}\left[\max_{j = 1, \ldots, m} t_j^{(b)} \ge t_i\right]

对最大的那个统计量(k=1k = 1),两者完全相同——因为此时 K1K_1 就是全集。差别从第二个开始出现,并且随着 kk 增大而扩大:单步版本让排第 20 位的策略去跟”30 个统计量的最大值”比,逐步下降版本只让它跟”剩下 11 个的最大值”比。

在一次 m=30m = 30、ρ=0.7\rho = 0.7、T=2520T = 2520 的模拟里,两者的修正 p 值是这样的(只列前几名):

tit_i逐步下降 pRWp^{RW}单步 psinglep^{\text{single}}
4.750.0000.000
3.910.0010.001
2.960.0170.017
2.880.0200.021
2.600.0410.043
2.100.1290.141
1.730.1290.247

排名靠前时差别小到看不见,排名靠后时差别就明显了。注意逐步下降那一列在 0.129 处平掉了——那是程序在 t=2.10t = 2.10 处停下来之后,剩余假设共享同一个修正 p 值的结果。标准实现用逐步下降,用它就是了。

Studentization:不是可选项

检验统计量可以用原始值 ti=rˉit_i = \bar{r}_i,也可以用 studentized 值 ti=rˉi/σ^it_i = \bar{r}_i / \hat{\sigma}_i。当各策略的波动率不同时,后者不是风格问题而是正确性问题:没有 studentization,高波动策略会系统性地主导最大值,于是 max⁡iti\max_i t_i 的 bootstrap 分布实际上只反映了那几个高波动策略的原假设,低波动策略被拿一个跟自己无关的阈值去检验。标准的 Romano-Wolf 实现一律 studentize:

tistudentized=rˉiσ^it_i^{studentized} = \frac{\bar{r}_i}{\hat{\sigma}_i}

比 studentization 更容易写错的是原假设中心化——bootstrap 必须在原假设成立的世界里生成统计量,而”原假设成立”具体是什么样,取决于你检验的是哪个原假设。

检验 H0:E[ri]≤0H_0: E[r_i] \le 0 时,中心化后的收益率是 ri,tc=ri,t−rˉir_{i,t}^c = r_{i,t} - \bar{r}_i,于是 rˉic=0\bar{r}_i^c = 0,bootstrap 样本的期望超额收益正好被压到零。检验 H0:E[ri]≤μbH_0: E[r_i] \le \mu_b(μb\mu_b 是基准)时,中心化后的收益率是 ri,tc=ri,t−μbr_{i,t}^c = r_{i,t} - \mu_b,此时 rˉic=rˉi−μb\bar{r}_i^c = \bar{r}_i - \mu_b,原假设下的检验统计量是 tic=(rˉi−μb)/σ^it_i^c = (\bar{r}_i - \mu_b) / \hat{\sigma}_i。检验 H0:SRi≤SRbH_0: SR_i \le SR_b 时对夏普做同样性质的中心化。

实现上有个坑值得单独点出来:中心化必须减掉原始样本的均值,不能减掉每个 bootstrap 重采样自己的均值。后者会把每一次重采样的均值精确压成零,于是每个 bootstrap 统计量恒等于零,最大值也恒等于零,所有策略都会被”拒绝”。这个 bug 不会报错,只会给你一份漂亮的结果。

Bootstrap 是怎么学到依赖结构的

Romano-Wolf 能工作,是因为 bootstrap 重采样的是联合收益率分布。多个策略的收益率相关时,重采样保留了这种相关性,于是 max⁡iti\max_i t_i 的 bootstrap 原假设分布自动落在前面那张表里对应的那一行上。

上面那张表其实就是这件事的封闭形式版本:Bonferroni 假设最坏情况(独立),Holm 用了排序但阈值仍来自解析公式,而 bootstrap 是以你手上这批数据的实际依赖结构为条件在算分布。它不需要你告诉它 ρ\rho 是多少,也不需要依赖结构是等相关这种好看的形式。

代价是 bootstrap 得重采样对,才能保留依赖。金融收益率有自相关和波动率聚集,不可交换,所以简单的逐点有放回重采样会打碎时间依赖。这也是 Romano-Wolf 用 bootstrap 而不是用置换的原因:置换是不放回地重排数据,保留边际分布但打破联合结构;它在可交换数据上是精确的,在时间序列上不是。正确做法是 block bootstrap,按观测块重采样,或者用 stationary bootstrap(块长随机)。

块长(或者说截断滞后)LL 是这里唯一一个需要你拿主意的参数。Politis 和 White(2004)给了一个基于自相关函数谱估计的自动选择规则,R 的 np::b.star() 是它的标准实现,能用就用它。手工选的话,Newey-West 式的经验规则 L=⌊4(T/100)2/9⌋L = \lfloor 4(T/100)^{2/9} \rfloor 是个合理起点;对日频股票收益率取 L=5L = 5(一周)、对月收益率取 L=3L = 3(一个季度)也够用,更高频的数据需要更大的 LL。好消息是在中等以上的 TT 下,p 值对 LL 相当不敏感——T>500T > 500 且 LL 取值合理时,差异基本可以忽略。

一个算到底的例子

回到开头那批策略,把数字填实。30 个策略,全是同一个动量信号的变体,收益率两两相关性 ρ≈0.9\rho \approx 0.9,日频数据 T=2520T = 2520(10 年)。

先看真实优势能产生多大的统计量。一个真实年化夏普 0.8 的策略,跑满 10 年,它的 t 统计量是

t=SR年化×年数=0.8×10≈2.53t = SR_{\text{年化}} \times \sqrt{\text{年数}} = 0.8 \times \sqrt{10} \approx 2.53

(注意这里不是拿夏普去除以波动率——夏普本身已经是波动率归一化过的量,再除一次没有意义。把年化夏普换算成 t 统计量,乘的是样本年数的平方根。)

再看阈值。按前面那张表,ρ=0.9\rho = 0.9 时 max⁡iti\max_i t_i 的原假设分布 95 百分位是 2.23,而 Bonferroni 给的是 2.94。于是:

  • Romano-Wolf 拒绝所有 ti>2.23t_i > 2.23 的策略。
  • Bonferroni 只拒绝 ti>2.94t_i > 2.94 的策略。

一个真实夏普 0.8 的策略,期望统计量 2.53,落在这两个阈值中间。Romano-Wolf 找得到它,Bonferroni 找不到。这就是那 0.7 个单位的阈值差在实践中意味着什么。

顺带说一句这个优势的边界在哪:把 ρ\rho 换成 0.5,阈值是 2.75,跟 Bonferroni 的 2.94 只差 0.19,多拒绝的策略寥寥无几。Romano-Wolf 的功效增益正比于你的策略实际有多像。策略池越同质,它赢得越多;策略池真的多样化,它和 Bonferroni 就没什么差别——这时候 Bonferroni 也不冤枉,因为你确实做了那么多独立检验。

各程序的对照

把 50 个策略、其中 10 个真实夏普 0.5、40 个真实夏普为 0 的情形跑一遍,各程序大致是这样的表现:

程序FWER 控制功效(拒绝的真阳性)假阳性
不修正无(FWER ≈ 87%)10 个中拒绝 9 个40 个中约 2 个
Bonferroni是(在 α\alpha 下)10 个中拒绝 1 个40 个中 0 个
Holm是(在 α\alpha 下)10 个中拒绝 3 个40 个中 0 个
Benjamini-HochbergFDR(在 α\alpha 下)10 个中拒绝 6 个40 个中 0–1 个
Romano-Wolf是(在 α\alpha 下)10 个中拒绝 7 个40 个中 0 个

这些数字是示意,实际功效取决于效应大小、样本量和依赖结构,但模式很稳定:在保持 FWER 控制的同时,Romano-Wolf 的功效高于 Bonferroni 和 Holm。

第一行需要解释一下,因为它最容易被误读。不修正时,40 个真原假设在 α=0.05\alpha = 0.05 下的期望假阳性只有 2 个,听起来不算多;真正的问题是至少出现一个假阳性的概率是 1−0.9540=87%1 - 0.95^{40} = 87\%。FWER 控制的对象是这个 87%,不是那个 2。你只从池子里挑一个策略去部署时,“至少一个假阳性”就是”你挑中的那个可能是假的”。

BH 的位置也值得说清楚。它产生更多拒绝,但明确允许其中混入一定比例的假阳性——这是 FDR 的定义,不是它的缺陷。如果你的目的是”选一批候选进下一轮测试”,BH 合适;如果你的目的是”选出一个能直接上钱的策略”,你要的是 FWER,Romano-Wolf 更对路。

它什么时候不管用

三种失效模式。

Bootstrap 没抓到依赖结构。 如果原始数据和 bootstrap 重采样之间的依赖结构不一致(最常见的原因是非平稳),修正 p 值就是错的,而且错的方向不确定。用 block bootstrap 保留时间依赖是底线,但 block bootstrap 也救不了真正的结构断裂。

策略太少。 只有 5 个策略时,最大值的 bootstrap 分布本身就很嘈杂,Romano-Wolf 相对 Bonferroni 的优势小到不值得多写这些代码。直接用 Bonferroni。

策略几乎完全相同。 如果 30 个策略的相关性达到 0.99,按前面那张表,有效检验数是 1.5——bootstrap 基本把它们看成一个策略。Romano-Wolf 仍然正确控制 FWER,但它拒绝的策略数会少于你的期待。这不是程序失灵,这是它在如实汇报:30 个实质相同的策略,不等于 30 份证据。

一份可跑的实现

import numpy as np


def romano_wolf(returns_matrix, benchmark=0.0, B=1000, alpha=0.05, seed=0):
    """
    Romano-Wolf step-down procedure for multiple testing (one-sided).

    Parameters
    ----------
    returns_matrix : ndarray of shape (T, m)
        Returns for each of m strategies over T observations.
    benchmark : float
        Benchmark mean return (default 0).
    B : int
        Number of bootstrap iterations.
    alpha : float
        FWER level.

    Returns
    -------
    rejected : ndarray of bool, shape (m,)
    t_stats  : ndarray of float, shape (m,)
    rw_pvals : ndarray of float, shape (m,)
    """
    rng = np.random.default_rng(seed)
    T, m = returns_matrix.shape

    # Test statistics on the original data
    excess = returns_matrix - benchmark
    means = excess.mean(axis=0)
    stds = excess.std(axis=0, ddof=1)
    t_stats = means / (stds / np.sqrt(T))

    # Bootstrap under the null; the B x m matrix is computed ONCE
    boot_t = np.zeros((B, m))
    for b in range(B):
        idx = rng.integers(0, T, size=T)
        resampled = excess[idx]
        centered = resampled - means      # centre on the ORIGINAL mean, not the resample's
        boot_means = centered.mean(axis=0)
        boot_stds = centered.std(axis=0, ddof=1)
        boot_t[b] = boot_means / (boot_stds / np.sqrt(T))

    # Step-down: the max is taken over the shrinking active set
    order = np.argsort(-t_stats)
    rw_pvals = np.ones(m)
    rejected = np.zeros(m, dtype=bool)
    active = list(order)
    prev_p = 0.0
    while active:
        i = active[0]
        max_active = boot_t[:, active].max(axis=1)
        p = max((max_active >= t_stats[i]).mean(), prev_p)   # enforce monotonicity
        rw_pvals[i] = p
        if p > alpha:
            for j in active:
                rw_pvals[j] = p
            break
        rejected[i] = True
        prev_p = p
        active.pop(0)

    return rejected, t_stats, rw_pvals


# Demo: 30 correlated strategies, 5 of them with a real edge
rng = np.random.default_rng(14)
T, m, rho = 2520, 30, 0.7
annual_sr = np.array([1.0] * 5 + [0.0] * 25)
daily_vol = 0.01
daily_mean = annual_sr / np.sqrt(252) * daily_vol
common = rng.standard_normal((T, 1))
idio = rng.standard_normal((T, m))
returns = daily_mean + (np.sqrt(rho) * common + np.sqrt(1 - rho) * idio) * daily_vol

rejected, t_stats, rw_pvals = romano_wolf(returns, B=1000, seed=7)
print(f"Romano-Wolf rejects: {rejected.sum()} of {m}")
print(f"True positives: {rejected[:5].sum()} of 5, false positives: {rejected[5:].sum()} of 25")

这个 demo 会拒绝 4 个策略:4 个真阳性、0 个假阳性。第 5 个真有优势的策略被漏掉了,因为它这一次的 t 统计量只有 1.74——这就是 FWER 控制的代价,你换来的是”拒绝的 4 个里一个假的都没有”这个保证。换几十个不同的数据种子重跑,典型结果是拒绝 4 到 5 个真策略、假阳性一直是 0;把 annual_sr 全部设成 0 重复几百次,经验 FWER 落在 0.05 以下,程序偏保守——这符合预期,单侧 bootstrap 在所有原假设都恰好取在边界上时本来就会保守一些。

代码里有一处值得再强调:boot_t 这个 B×mB \times m 矩阵只算一次。逐步下降的每一步只是在这个矩阵的一个子集列上重新取 max,成本是 O(B⋅m)O(B \cdot m),相对于 bootstrap 本身(BB 次重采样、每次 T×mT \times m 次运算)可以忽略。所以”逐步下降比单步慢很多”是个误解——多花的那点时间在几百个策略的规模上都看不出来。真正的成本一直是 BB 和 TT。

它和 DSR、SPA、Westfall-Young 的关系

去膨胀夏普比率(DSR)和 Romano-Wolf 回答的是不同的问题。DSR 问:“既然我从 mm 个策略里挑了最好的一个,我报告的这个夏普能不能和原假设下的期望最大值区分开?“这是对选中的那一个做单假设检验。Romano-Wolf 问:“修正多重检验、并考虑依赖结构之后,我这 mm 个策略里哪些的收益能和基准区分开?“这是多假设检验,输出是一个子集而不是一个是非题。

两者配合使用的顺序是清楚的:先用 Romano-Wolf 划出有统计证据的候选子集,再用 DSR 评估最终挑中的那一个。如果 Romano-Wolf 一个都没拒绝,就没必要往下走了;如果拒绝了几个,就对其中最好的那个跑 DSR,看这个”最好”是不是选择偏差的产物。

Westfall-Young(1993)的重采样多重检验和 Romano-Wolf 血缘很近,两者都用重采样算修正 p 值、都尊重依赖结构,而且 Westfall-Young 本身就同时给出了单步和逐步下降两个版本——所以差别不在”有没有 step-down”。真正的差别在假设上:Westfall-Young 的理论依赖 subset pivotality(子集枢轴性)这个条件,它在一些实际情形下会失效;Romano-Wolf 的贡献是给出一个不需要这个条件的框架,在相当一般的依赖结构下证明了渐近 FWER 控制。金融数据上两种方法通常给出接近的结果,推荐 Romano-Wolf 是因为它对假设的要求更弱。

至于 SPA,它和 Romano-Wolf 处理的是同一类问题的不同切面:SPA 针对异方差和单侧备择做了专门处理,输出是对”最好的策略是否优于基准”的单个 p 值。实践中三个一起跑是有价值的——DSR 先做一轮快速检查,Romano-Wolf 划候选子集,SPA 从另一个方法论视角确认。三者一致时结论比较硬;不一致时,多半是假设出了分歧:DSR 假设收益率高斯且试验独立,Romano-Wolf 通过 bootstrap 尊重依赖结构,SPA 处理异方差和单侧备择。查清楚是哪条假设在你的数据上不成立,比追问”哪个方法对”更有用。

家族结构:一个真实的边界

Romano-Wolf 假设所有检验属于同一个家族,FWER 在整个家族上得到控制。当家族本身有结构时,这个假设值得推敲。

举例:在 10 个股票池上测 5 个动量窗口,一共 50 个检验,但它们天然分成 10 组或 5 组。Romano-Wolf 会把 FWER 控制在这 50 个检验的整体上——注意,问题不是它把它们当成了独立检验(bootstrap 恰恰会看到组内的高相关,这正是它的长处),而是”整体 FWER”这个目标本身可能比你真正想要的更严。如果你的决策单位是”每个股票池里选一个窗口”,那么合适的目标是在每个股票池内部控制错误,而不是在 50 个检验的全局上控制。

对这类结构化家族,文献里有专门的分层检验程序可用。Benjamini 和 Yekutieli(2001)常在这个语境里被提到,但要说清楚它做的是什么:它证明的是 BH 型 FDR 控制在任意依赖结构下的有效性(代价是一个 ∑i=1m1/i\sum_{i=1}^{m} 1/i 的调整因子),不是分层家族的 FWER 程序。分层结构要另找工具。

对绝大多数量化场景,基础的 Romano-Wolf 已经够用,把整个搜索空间当成一个家族也是诚实的——毕竟你确实在整个空间上搜过。

怎么读它的输出,以及它不告诉你什么

Romano-Wolf 的输出是一个通过 FWER 修正检验的策略子集,这个子集带一个明确的保证:以至少 1−α1 - \alpha 的概率,子集里没有任何一个假阳性。这是 FWER 陈述,比 FDR 强得多——FDR 只保证假阳性占拒绝数的比例受控。(上面的算法用的是 max⁡iti\max_i t_i,对应单侧备择”优于基准”;要做双侧,把最大值换成 max⁡i∣ti∣\max_i |t_i|。)

这个保证很强,但它保证的东西很窄。它说的是”这些策略的收益在统计上能和基准区分开”,不是”这些策略经过了验证”。一个通过 Romano-Wolf 的策略仍然可能预期收益为负(如果基准本身很高)、可能对容量极度敏感、可能只在某一种市场状态下有效、也可能有实现层面的问题。这些东西没有一样在 Romano-Wolf 的射程之内。

所以它在流程里的位置是筛选,不是判决。一个可用的工作流大致是:搜索假设空间生成 mm 个候选策略;对每个策略计算相对基准的 studentized 表现差异;用 B≥1000B \ge 1000 次 bootstrap(块长按数据自相关选)跑 Romano-Wolf,得到候选子集;对子集里的每个策略再跑其他诊断——Haircut Sharpe、PBO、容量分析、状态检验;综合所有结果决定升级到模拟交易还是拒绝。

这里的关键是”综合”。过了 Romano-Wolf 但过不了容量分析的策略没准备好;容量没问题却过不了 Romano-Wolf 的策略值得怀疑。两项都要过,而且这两项谁也代替不了谁。

报告的时候,“Romano-Wolf 在 50 个策略中拒绝了 5 个”这一句是不完整的。读者需要看到策略数量 mm、用的是哪个检验统计量、bootstrap 方法和迭代次数 BB、每个策略的修正 p 值,以及结果对 bootstrap 参数(尤其是块长)的敏感性。修正 p 值那一列尤其重要——一个 pRW=0.049p^{RW} = 0.049 的策略和一个 pRW=0.001p^{RW} = 0.001 的策略在”拒绝”这个二元标签下看起来一样,实际上完全不是一回事。

对中等规模的 mm,Romano-Wolf 是控制 FWER 最有功效的程序,这也是它在研究论文里成为默认选择的原因。行业里 SPA 和 DSR 更常见,主要是因为它们更容易实现、更容易向非统计背景的人解释,而 Romano-Wolf 需要更多 bootstrap 迭代才能让 p 值稳定。这是工程取舍,不是方法论优劣。

参考文献

  • Romano, J. P. and Wolf, M. (2005). “Stepwise Multiple Testing as Formalized Data Snooping.” Econometrica, 73(4), 1237–1282.
  • Romano, J. P. and Wolf, M. (2005). “Exact and Approximate Stepdown Methods for Multiple Hypothesis Testing.” Journal of the American Statistical Association, 100(469), 94–108.
  • Westfall, P. H. and Young, S. S. (1993). Resampling-Based Multiple Testing. Wiley。重采样多重检验的完整专著,单步与逐步下降版本都在里面。
  • Politis, D. N. and White, H. (2004). “Automatic Block-Length Selection for the Dependent Bootstrap.” Econometric Reviews, 23(1), 53–70.
  • Benjamini, Y. and Yekutieli, D. (2001). “The Control of the False Discovery Rate in Multiple Testing under Dependency.” Annals of Statistics, 29(4), 1165–1188.
  • López de Prado, M. (2018). Advances in Financial Machine Learning. Wiley. ISBN 978-1-119-48208-6。第 14 章讲回测统计量与去膨胀夏普比率。