跳到正文

Hansen SPA 检验:正确推导 Superior Predictive Ability

Hansen SPA 检验通过正确处理异方差和单侧备择,改进了 White Reality Check。本文完整推导 SPA:studentized 统计量、更好的原假设中心化,以及为什么 SPA 在大多数现实场景下比 Reality Check 更有功效。

第 10 篇 · 共 12 篇 约 12 分钟

White Reality Check 有两个问题。策略方差不同时,它会损失统计功效;很多策略的平均表现为负时,它会过于保守。Hansen SPA 检验把这两点都修正了。本文从头推导 SPA,解释为什么它优于 Reality Check,并说明额外功效来自哪里。

SPA 修正什么

考虑 50 个策略。其中 20 个的夏普为 1.0、年化波动率为 15%,另外 20 个的夏普也是 1.0、但波动率为 30%,还有 10 个夏普为 0。高波动策略的检验统计量更大,因为波动率会抬高夏普标准误。Reality Check 的最大值统计量会被高波动策略主导,低波动策略实际上被忽略了。

SPA 使用策略自身的方差做 studentization。高波动策略和夏普相同的低波动策略,会得到相同的检验统计量。最大值不再偏向高波动策略,统计功效也回来了。

另一个问题是中心化。White 的中心化 dc=d−dˉd^c = d - \bar{d} 对每个策略都施加 E[d]=0E[d] = 0。如果 50 个策略中有 30 个的 E[d]=−0.001E[d] = -0.001(为负,但幅度很小),中心化会把它们放到零。Bootstrap 原假设中,这些策略的 E[d]=0E[d] = 0,而真实情况是 E[d]<0E[d] < 0,所以 Bootstrap 会过于保守。

Hansen 的改进是:只对平均差异为正的策略做中心化,平均值为负的策略保持原样。这样得到的 Bootstrap 原假设,是“已实现的原假设”(负均值策略)和“中心化原假设”(正均值策略)的混合,不那么保守。

设定

和 Reality Check 一样。你有 mm 个策略,每个策略在 t=1,…,Tt = 1, \ldots, T 的表现差异为 di,t=fi,t−f0,td_{i,t} = f_{i,t} - f_{0,t}。原假设是:

H0:max⁡iE[di]≤0H_0: \max_i E[d_i] \le 0

也就是最优策略的预期差异不高于零。备择假设是至少有一个策略的预期差异为正。

Studentized 统计量

对每个策略 ii,studentized 平均差异是:

ti=Tdˉiω^it_i = \frac{\sqrt{T} \bar{d}_i}{\hat{\omega}_i}

其中,dˉi=1T∑tdi,t\bar{d}_i = \frac{1}{T} \sum_t d_{i,t},ω^i\hat{\omega}_i 是 di,td_{i,t} 长期方差的一致估计量:

ω^i2=γ^i,0+2∑k=1qwk,qγ^i,k\hat{\omega}_i^2 = \hat{\gamma}_{i,0} + 2 \sum_{k=1}^{q} w_{k,q} \hat{\gamma}_{i,k}

其中,γ^i,k\hat{\gamma}_{i,k} 是滞后 kk 的样本自协方差,wk,qw_{k,q} 是核权重(Bartlett 核或二次谱核)。这就是 HAC 估计量。长期方差会考虑差异中的自相关和异方差。

观测检验统计量为:

TobsSPA=max⁡itiT_{obs}^{SPA} = \max_i t_i

使用更好中心化的 Bootstrap 原假设

对每次 Bootstrap 迭代 b=1,…,Bb = 1, \ldots, B:

  1. 重采样索引 i1(b),…,iT(b)i_1^{(b)}, \ldots, i_T^{(b)}(有放回),保留联合结构。
  2. 对每个策略 ii,计算 Bootstrap 差异均值 dˉi(b)\bar{d}_i^{(b)}。
  3. 应用 Hansen 中心化:
dˉic,(b)=dˉi(b)⋅1[dˉi(b)>0]⋅...\bar{d}_i^{c,(b)} = \bar{d}_i^{(b)} \cdot \mathbb{1}[\bar{d}_i^{(b)} > 0] \cdot ...

等等,这个中心化更微妙。重新说一遍。Hansen 中心化只对观测平均差异为正的策略做中心化:

di,tc,(b)={di,t(b)−dˉiif dˉi>0di,t(b)otherwised_{i,t}^{c,(b)} = \begin{cases} d_{i,t}^{(b)} - \bar{d}_i & \text{if } \bar{d}_i > 0 \\ d_{i,t}^{(b)} & \text{otherwise} \end{cases}

直觉是:如果策略在原始数据中的平均差异为负,Bootstrap 应该保留这个负值。把它中心化到零,会让 Bootstrap 对这个策略过于宽松。

每次 Bootstrap 迭代:

  • 对每个策略 ii,计算 dˉic,(b)=1T∑tdi,tc,(b)\bar{d}_i^{c,(b)} = \frac{1}{T} \sum_t d_{i,t}^{c,(b)}。
  • 计算 Bootstrap studentized 统计量:tic,(b)=Tdˉic,(b)/ω^i(b)t_i^{c,(b)} = \sqrt{T} \bar{d}_i^{c,(b)} / \hat{\omega}_i^{(b)}。
  • 记录最大值:Tc,(b)=max⁡itic,(b)T^{c,(b)} = \max_i t_i^{c,(b)}。

p 值为:

pSPA=1B∑b=1B1[Tc,(b)≥TobsSPA]p_{SPA} = \frac{1}{B} \sum_{b=1}^{B} \mathbb{1}\left[T^{c,(b)} \ge T_{obs}^{SPA}\right]

这种中心化为什么有效

考虑两种情况。

情况 A: 50 个策略的 E[di]=0E[d_i] = 0(真实原假设)。原始均值 dˉi\bar{d}_i 在零附近有噪声,大约一半为正,一半为负。Hansen 中心化会中心化正值的一半,保留负值的一半。最大值的 Bootstrap 原假设分布,就是“25 个中心化统计量 + 25 个未中心化统计量”的最大值分布。未中心化的一半真实均值为零,所以中心化实际上和 White 的做法差不多,p 值计算是正确的。

情况 B: 20 个策略的 E[di]=0.001E[d_i] = 0.001(小幅正值),30 个策略的 E[di]=−0.001E[d_i] = -0.001(小幅负值)。原始均值会反映这一点:平均来说 20 个为正,30 个为负。

White 的中心化:50 个策略全部中心化。Bootstrap 原假设是 50 个中心化统计量的最大值分布。20 个策略的真实正均值被去掉了,Bootstrap 低估了最大值可能达到的水平。

Hansen 的中心化:只中心化 20 个正均值策略,30 个负均值策略保留其负均值。Bootstrap 原假设是“20 个中心化策略(均值为零)+ 30 个未中心化策略(均值为 -0.001)的最大值”。30 个负策略会把最大值往下拉,Bootstrap 最大值分布整体下移。

于是观测最大值更容易超过 Bootstrap 分布,p 值更小,拒绝的原假设更多。

当很多策略的均值为负时,这个改进最明显。典型量化搜索中,你只保留看起来不错的策略,同时会有很多“试过但丢弃”的负均值策略,这种情况很常见。

下界修正

Hansen 的检验还有一个“下界”版本,用来进一步处理中心化策略的 Bootstrap 均值为负的情况。具体写法是:

dˉic,(b)=max⁡(dˉi(b)−dˉi⋅1[dˉi>0],dˉi(b))\bar{d}_i^{c,(b)} = \max\left(\bar{d}_i^{(b)} - \bar{d}_i \cdot \mathbb{1}[\bar{d}_i > 0], \bar{d}_i^{(b)}\right)

不对,下界其实是:

dˉic,(b)=min⁡(dˉi(b)−dˉi⋅1[dˉi>0],dˉi(b))\bar{d}_i^{c,(b)} = \min\left(\bar{d}_i^{(b)} - \bar{d}_i \cdot \mathbb{1}[\bar{d}_i > 0], \bar{d}_i^{(b)}\right)

嗯,这里有点混乱。重新写正确的形式。

Hansen 的原始形式使用“consistent”中心化。对每个策略 ii,Bootstrap 统计量是:

dˉic,(b)=dˉi(b)ifdˉi≤0\bar{d}_i^{c,(b)} = \bar{d}_i^{(b)} \quad \text{if} \quad \bar{d}_i \le 0 dˉic,(b)=dˉi(b)−dˉiifdˉi>0\bar{d}_i^{c,(b)} = \bar{d}_i^{(b)} - \bar{d}_i \quad \text{if} \quad \bar{d}_i > 0

这和上面写的一样。下界版本是一个进一步修正,它使用 max⁡(⋅,⋅)\max(\cdot, \cdot),确保中心化后的统计量不会过度变负。实践中两个版本都可以,原始版本更简单。

功效比较:Reality Check 与 SPA

做一个模拟比较。50 个策略,真实夏普来自:

  • 5 个策略:SR=1.0SR = 1.0(年化)。
  • 45 个策略:SR=0SR = 0。

日波动率:1%。样本量:T=1260T = 1260。Bootstrap 迭代次数:B=1000B = 1000。

在 α=0.05\alpha = 0.05 下进行 200 次模拟,拒绝率如下:

程序拒绝率
Reality Check(White)0.42
SPA(Hansen)0.61
Romano-Wolf 逐步下降法0.58
Bonferroni 修正最大值0.18

当 50 个策略中有 5 个的真实夏普为 1.0 时,SPA 在 61% 的模拟中拒绝原假设,Reality Check 为 42%,Bonferroni 为 18%。SPA 比 Reality Check 多 45% 的功效,比 Bonferroni 高 3.4 倍。

改进来自两个方面:更好的中心化贡献约 60%,长期方差 studentization 贡献约 40%。

异方差很重要

Reality Check 使用样本标准差做 studentization。SPA 使用长期方差(HAC 估计量)。对有自相关或波动率聚集的差异,长期方差大于独立同分布方差估计。Studentized 统计量的绝对值会变小,最大值也会变小。

等等,这个方向说反了。如果标准误更大,t 统计量更小,最大值更小,检验拒绝得更少。所以 HAC studentization 应该降低功效,而不是提高功效。

重新想一下。HAC 方差是平均差异的正确方差。如果差异有自相关,独立同分布方差会低估真实方差。样本标准差会给出过大的 t 统计量,所以 Reality Check 会拒绝过多。

SPA 使用正确方差,修正了这一点,让拒绝率回到名义水平。Reality Check 的拒绝率更高,但原因不对,因为 t 统计量被抬高了。

因此比较应该是:

  • Reality Check:存在自相关时过度拒绝(表面上更高的拒绝率,其实是 I 类错误膨胀)。
  • SPA:在名义水平下正确拒绝。

当两者都校准到相同的名义 I 类错误率时,SPA 的功效高于 Reality Check。原因是 SPA 的更好中心化会让 Bootstrap 原假设分布下移,更容易让观测最大值超过它。

完整例子

假设有 30 个策略。搜索结束后,5 个策略的平均差异为正,25 个为负。5 个正均值策略的 studentized 差异为:3.5、3.2、2.8、2.5、2.1。25 个负均值策略的 studentized 差异在 -3.0 到 -0.5 之间。最大值是 3.5。

Reality Check:30 个策略全部中心化。Bootstrap 最大值原假设分布的第 95 百分位约为 2.7。p 值约为 0.04,拒绝原假设。

SPA:只中心化 5 个正均值策略。25 个负均值策略会把 Bootstrap 最大值往下拉。第 95 百分位约为 2.3。p 值约为 0.02,拒绝原假设。

在这个场景下,SPA 的 p 值更小,因为负均值策略把 Bootstrap 原假设分布向下移了。相对于 SPA 原假设,观测最大值比相对于 Reality Check 原假设更突出。

实现

import numpy as np

def spa_test(differentials, B=1000, alpha=0.05):
    """
    Hansen's SPA test with consistent centering.
    
    Parameters
    ----------
    differentials : ndarray of shape (T, m)
        Performance differentials.
    B : int
        Bootstrap iterations.
    
    Returns
    -------
    p_value : float
    rejected : bool
    """
    T, m = differentials.shape
    
    # Studentized statistics (using sample std for simplicity)
    d_means = differentials.mean(axis=0)
    d_stds = differentials.std(axis=0, ddof=1)
    t_stats = np.sqrt(T) * d_means / d_stds
    T_obs = t_stats.max()
    
    # Consistent centering: only center positive-mean strategies
    positive_mask = d_means > 0
    centered = differentials.copy()
    centered[:, positive_mask] -= d_means[positive_mask]
    
    # Bootstrap null
    boot_max = np.zeros(B)
    for b in range(B):
        idx = np.random.choice(T, size=T, replace=True)
        boot_centered = centered[idx]
        boot_means = boot_centered.mean(axis=0)
        boot_stds = boot_centered.std(axis=0, ddof=1)
        boot_t = np.sqrt(T) * boot_means / boot_stds
        boot_max[b] = boot_t.max()
    
    p_value = (boot_max >= T_obs).mean()
    return p_value, p_value < alpha

# Demo
np.random.seed(42)
T, m = 1260, 30
# 5 strategies with positive SR, 25 with negative
true_sr = np.concatenate([
    np.full(5, 0.5) / np.sqrt(252),
    np.full(25, -0.2) / np.sqrt(252)
])
differentials = true_sr[None, :] + np.random.normal(0, 0.01, size=(T, m))

p_value, rejected = spa_test(differentials, B=1000)
print(f"SPA p-value: {p_value:.4f}")
print(f"Reject null: {rejected}")

什么时候用 SPA、DSR 或 Romano-Wolf

三个检验,三个问题:

  • DSR:把 PSR 的基准替换成原假设下的期望最大值。闭式、快速,但假设收益率高斯、试验独立。
  • Romano-Wolf:逐步下降 Bootstrap,控制 FWER,同时考虑依赖。策略相关时最有功效。
  • SPA:检验“最优策略能否和基准区分开”。很多策略表现为负时最有功效。

选择量化策略时,以下情况通常优先用 SPA:

  • 策略数量中等(20–100 个)。
  • 策略波动率不同。
  • 很多策略的回测表现为负。

以下情况适合 DSR:

  • 想要闭式计算。
  • 收益率大致高斯。
  • 策略大致独立。

以下情况适合 Romano-Wolf:

  • 想找出一个候选子集继续跟踪,而不是只看最优策略。
  • FWER 控制是最高优先级。

对大多数量化团队,SPA 是默认选择。

Stationary Bootstrap 假设

SPA 使用 stationary bootstrap(Politis 和 Romano,1994)生成原假设分布。Stationary bootstrap 会重采样连续观测块,块长度从几何分布中随机抽取,从而保留块内依赖结构。

前提是数据平稳,因此从 block bootstrap 学到的依赖结构能代表真实依赖。对非平稳数据(状态切换、结构断点),stationary bootstrap 会有偏。

对大多数股票收益率序列,在多年日频窗口上平稳性大致成立。对高频数据或跨状态数据,平稳性会失效。这时 SPA 会过于乐观,拒绝过多。

一个实用修正是:使用和状态边界对齐的 block bootstrap,或者使用能保留状态结构的 Bootstrap,例如 Markov bootstrap。

对大多数量化场景,stationary bootstrap 已经够用。在方法章节中把这个假设写清楚。

HAC 方差估计量的实务用法

SPA 使用 HAC(异方差与自相关一致)方差估计量。标准实现使用 Bartlett 核:

ω^i2=γ^i,0+2∑k=1q(1−kq+1)γ^i,k\hat{\omega}_i^2 = \hat{\gamma}_{i,0} + 2 \sum_{k=1}^{q} \left(1 - \frac{k}{q+1}\right) \hat{\gamma}_{i,k}

其中,γ^i,k\hat{\gamma}_{i,k} 是策略 ii 在滞后 kk 的样本自协方差,qq 是截断滞后。

qq 的选择是一个参数。常见选择包括:

  • Newey-West(1994)自动选择:q=⌊4(T/100)2/9⌋q = \lfloor 4(T/100)^{2/9} \rfloor。T=1260T = 1260 时,q=6q = 6。
  • 为简单起见固定为 5 或 10。
  • Andrews(1991)根据数据自相关结构自动选择。

Newey-West 自动滞后是最常见的选择,它平衡了方差估计量的偏差和方差。

一个常见错误是取 q=T−1q = T - 1,也就是完全不截断。这样会包含长滞后上的噪声自协方差,抬高方差估计,导致检验过于不容易拒绝。

与模型置信集的关系

Hansen 的模型置信集(MCS,2011)把 SPA 扩展到了模型选择。MCS 程序是:

  1. 从完整的 mm 个策略集合开始。
  2. 每一步检验当前集合中是否有策略显著差于集合中的最优策略。
  3. 删除最差策略。
  4. 重复这个过程,直到集合“有足够把握”,也就是没有策略显著更差。

MCS 给出的是一组在统计上无法和最优策略区分的策略。这比单独使用 SPA 提供的信息更多:SPA 告诉你“最优策略能否和基准区分开”,MCS 告诉你“哪些策略和最优策略处于同一个置信集合”。

对量化研究,MCS 适合把大量候选缩小到一组同样有效的选择。接下来再根据非统计条件选择,比如容量、稳健性和实现。

假阳性与假阴性的取舍

SPA 在 α\alpha 水平控制 I 类错误,也就是假阳性率。这意味着当备择假设为真时,它有时不会拒绝原假设(II 类错误)。II 类错误概率是 1−Power1 - \text{Power}。

SPA 的功效取决于:

  • 最优策略的真实夏普(越高,功效越大)。
  • 策略数量 mm(越多,每个策略的功效越低,因为 Bootstrap 分布会移动)。
  • 差异的方差(越大,功效越低)。
  • 策略之间的相关性(越高,功效越大,因为 Bootstrap 分布更紧)。

实务中很少做功效分析,通常直接在 α=0.05\alpha = 0.05 下运行 SPA 并接受结果。对大额资本配置等高风险决策,做功效分析很值得。

Bootstrap 方差

SPA p 值是一个 Bootstrap 估计值,本身有抽样波动。Bootstrap p 值的方差取决于 Bootstrap 次数 BB 和真实 p 值:

Var(p^)≈p(1−p)B\text{Var}(\hat{p}) \approx \frac{p(1-p)}{B}

当 B=1000B = 1000、p=0.05p = 0.05 时:Var(p^)≈4.75×10−5\text{Var}(\hat{p}) \approx 4.75 \times 10^{-5},所以 SD(p^)≈0.0069\text{SD}(\hat{p}) \approx 0.0069。p 值的 95% 置信区间约为 [0.05−0.014,0.05+0.014]=[0.036,0.064][0.05 - 0.014, 0.05 + 0.014] = [0.036, 0.064]。

当 B=10000B = 10000 时:Var(p^)≈4.75×10−6\text{Var}(\hat{p}) \approx 4.75 \times 10^{-6},SD(p^)≈0.0022\text{SD}(\hat{p}) \approx 0.0022。95% 置信区间为 [0.046,0.054][0.046, 0.054],紧得多。

确认性分析使用 B≥10000B \ge 10000。筛选时 B=1000B = 1000 可以接受。计算成本大致与 BB 成正比。

多时期扩展

标准 SPA 使用一个回测区间。对多时期验证(例如样本内 + 样本外 + 前向测试),可以扩展 SPA:

SPAcombined=1−∏j=1J(1−SPAj)SPA_{combined} = 1 - \prod_{j=1}^{J} (1 - SPA_j)

其中,SPAjSPA_j 是第 jj 个时期的 p 值。这个 Bonferroni 风格的组合假设各时期相互独立。如果时期相关,它会偏保守。

对非独立时期,需要使用检验统计量的经验联合分布。这要求更复杂的 Bootstrap,并保留跨时期依赖。

对大多数量化研究,单时期 SPA 已经足够。多时期扩展主要用于高风险决策。

常见错误

使用 SPA 时要避免五种错误:

错误 1:中心化方式错了。 如果像 White 那样中心化所有策略,就会损失 SPA 的统计功效优势。Hansen 中心化(只中心化正均值策略)是关键。

错误 2:用样本标准差代替 HAC。 对有自相关或异方差的差异,HAC 估计量才能给出正确标准误。样本标准差会给出错误的、通常偏小的标准误。

错误 3:对绝对收益率而不是差异计算 SPA。 正确输入是 di,t=fi,t−f0,td_{i,t} = f_{i,t} - f_{0,t}。使用绝对收益率,会把策略表现和基准表现混在一起。

错误 4:忽略收益率的时间序列特征。 对单位根或接近单位根的序列,SPA 无效。应该使用收益率(大致平稳),不要使用价格。

错误 5:报告 SPA p 值时不说明 Bootstrap 迭代次数。 p 值依赖 BB。要把 BB 和 p 值一起报告,读者才能判断精度。

SPA 在工具箱中的位置

SPA 是回答以下问题最有功效的通用检验:“考虑多重检验和依赖结构后,我的最优策略能否和基准区分开?”它是量化研究的合理默认方法。

不同场景可以这样选:

  • DSR 更快,但假设收益率高斯。
  • Romano-Wolf 更灵活(逐步下降逻辑),并且能给出一组有效策略。
  • White Reality Check 更简单,但功效较低。

SPA 处在中间位置:比 Reality Check 功效高,比 DSR 的限制少,比 Romano-Wolf 计算成本低。对大多数量化研究,从 SPA 开始比较合适。

关于多重检验的最后一点

和所有多重检验修正一样,SPA 是针对统计问题的统计修正。问题是真实存在的:当你测试很多策略时,总会有一些靠运气看起来很好。修正方法就是调整拒绝阈值。

但统计修正是必要条件,不是充分条件。在 α=0.05\alpha = 0.05 下通过 SPA,表示在真实预期差异为零的全局原假设下,这组收益不太可能出现。它不代表策略会在生产环境中奏效。

完整验证还需要:

  1. 统计:SPA 通过。
  2. 样本外:在未见过的数据上仍然有效。
  3. 容量:在目标规模下仍然有效。
  4. 稳健性:跨市场状态仍然有效。
  5. 实现:在现实执行条件下仍然有效。

SPA 只负责第 1 项。第 2 到第 5 项同样重要,不能跳过。

参考文献

  • Hansen, P. R. (2005). “A Test for Superior Predictive Ability.” Journal of Business and Economic Statistics, 23(4), 365–380。SPA 原始论文。
  • Hansen, P. R., Lunde, A., and Nason, J. M. (2011). “The Model Confidence Set.” Econometrica, 79(2), 453–497。模型选择扩展。
  • López de Prado, M. (2018). Advances in Financial Machine Learning, §14.4. Wiley. ISBN 978-1-119-48208-6。实现及与 DSR 的比较。
  • White, H. (2000). “A Reality Check for Data Snooping.” Econometrica, 69(5), 1097–1126。前身方法。