跳到主要内容

多重检验偏差与 Deflated Sharpe

直觉

你回测了 200 个策略,挑出夏普最高的那个——它看起来棒极了。但这几乎必然是假象:就算所有策略都没真本事,200 次里也总能撞到一个「运气最好」的。这就是多重检验偏差(multiple testing)试的次数越多,最佳成绩越虚高。Bailey 与 López de Prado 提出的 Deflated Sharpe Ratio(DSR,缩水夏普) 就是把「试了多少次」和「样本多短」一起算进去,回答「这个夏普是真本事,还是 N 选 1 的运气」。

Deflated Sharpe 公式

先算「在零假设(无任何技能)下,NN 次试验能期望到的最高夏普」SR0\text{SR}_0

SR0=σSR ⁣[(1γ)Φ1 ⁣ ⁣(11N)+γΦ1 ⁣ ⁣(11Ne)]\text{SR}_0=\sigma_{\text{SR}}\!\left[(1-\gamma)\,\Phi^{-1}\!\!\left(1-\tfrac{1}{N}\right)+\gamma\,\Phi^{-1}\!\!\left(1-\tfrac{1}{N e}\right)\right]

其中 γ0.5772\gamma\approx0.5772 为欧拉常数,σSR\sigma_{\text{SR}} 是夏普估计的标准误(与样本长度 TT、偏度、峰度有关)。再把观测到的夏普与 SR0\text{SR}_0 比较:

DSR=Φ ⁣((SRSR0)T11skewSR+kurt14SR2)\text{DSR}=\Phi\!\left(\frac{(\text{SR}-\text{SR}_0)\sqrt{T-1}}{\sqrt{1-\text{skew}\cdot\text{SR}+\frac{\text{kurt}-1}{4}\,\text{SR}^2}}\right)

DSR 可理解为「这个夏普不是运气的概率」——通常要 >0.95>0.95 才敢相信。

「人话」解释:为什么「试得越多,最优越虚」?

抛 200 枚硬币,总有一枚连续 10 次正面——你不会说那枚硬币「有本事」。回测同理:200 个策略里的「冠军」,很可能只是噪声里最幸运的一个SR0\text{SR}_0 就是「纯靠运气,200 选 1 能选到的最高夏普」;如果冠军夏普只比 SR0\text{SR}_0 高一点点,DSR 就很低,意思是「这点优势,运气也解释得通」。 对策:少试、先验地限定策略族、预留独立的、从不参与挑选的最终测试集。

可运行案例:200 个随机策略的「冠军」有多假?

模拟 200 个纯随机的多空信号回测,挑出夏普冠军,再用 DSR 判定它是不是运气。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.stats import norm

df = pd.read_csv('/data/spy_daily.csv', parse_dates=['date']).set_index('date')
ret = df['adj_close'].pct_change().dropna()
T = len(ret); N = 200                       # 试了 200 个策略
rng = np.random.default_rng(7)

sharpes = []; best = -9; winner = None
for _ in range(N):
  sig = pd.Series(rng.choice([-1, 0, 1], size=T), index=ret.index
                 ).replace(0, np.nan).ffill().fillna(0).shift(1).fillna(0)  # 随机信号 + 防前视
  pr = sig * ret - np.abs(sig.diff().fillna(0)) * 2e-4                       # 轻成本
  sr = pr.mean() / pr.std() * np.sqrt(252)
  sharpes.append(sr)
  if sr > best: best = sr; winner = pr
sharpes = np.array(sharpes); sr = sharpes.max()
print(f"{N} 个随机策略: 冠军夏普={sr:.2f}  (全体均值={sharpes.mean():.2f}, std={sharpes.std():.2f})")

sk, ku = winner.skew(), winner.kurtosis()     # pandas kurtosis 即超额峰度
gamma = 0.5772156649
z1 = norm.ppf(1 - 1/N); z2 = norm.ppf(1 - 1/(N*np.e))
sr_std = np.sqrt((1 - sk*sr + (ku-1)/4*sr**2) / (T-1))
SR0 = sr_std * ((1-gamma)*z1 + gamma*z2)
DSR = norm.cdf((sr - SR0) / sr_std)
print(f"零假设下 {N} 选 1 的期望最高夏普 SR0={SR0:.2f}")
print(f"冠军 Deflated Sharpe = {DSR:.2f}   (>0.95 才算'非运气')")
print("→ 名义夏普漂亮, DSR 却很低: 只是 200 选 1 的运气, 不可投产。")

plt.figure(figsize=(9, 3.4))
plt.hist(sharpes, bins=30, color='lightgray', edgecolor='k')
plt.axvline(sr, color='crimson', lw=2, label=f'冠军 SR={sr:.2f}')
plt.axvline(SR0, color='navy', ls='--', lw=1.6, label=f'SR0={SR0:.2f}(运气上限)')
plt.title(f'{N} 个随机策略的夏普分布'); plt.xlabel('年化夏普'); plt.legend(fontsize=9)
plt.tight_layout(); plt.show()

小结

  • 试的策略/参数越多,最佳夏普越虚高——多重检验偏差
  • Deflated Sharpe 把试验次数 NN、样本长度 TT、偏度峰度一起算进去,给出「非运气」的概率;
  • 实务对策:少试、预限定策略族、留独立测试集——把「冠军」放到一个从未参与挑选的样本上再验一次。

小测验

1. 试的策略/参数越多,最佳夏普通常会?

2. Deflated Sharpe 额外考虑了哪些因素?

3. 某策略名义夏普很高、但 DSR 很低,说明?