第 8 章 · 01 多重检验与 Deflated Sharpe


文档摘要

第 8 章 · 01 多重检验与 Deflated Sharpe 本节摘要:本节回答一个常被忽视却致命的问题——为什么「我试了 100 个策略,挑出夏普最高的那个」几乎注定是一场统计幻觉。回测越多,「偶然表现出色」的策略越容易出现,这就是多重检验(multiple testing)问题。本节讲清三件事:回测过拟合与数据窥探(data-snooping)如何制造虚假显著性;Bailey 与 López de Prado 提出的 Deflated Sharpe Ratio(简称 DSR)如何在「策略数量、收益非正态、样本较短」三重污染下修正夏普比率的显著性判断;以及如何用一段简洁的 Python 代码估计「期望的最大夏普比率」,把运气从能力中剥离。读完本节,你会对任何「漂亮回测」保持应有的怀疑。

第 8 章 · 01 多重检验与 Deflated Sharpe

本节摘要:本节回答一个常被忽视却致命的问题——为什么「我试了 100 个策略,挑出夏普最高的那个」几乎注定是一场统计幻觉。回测越多,「偶然表现出色」的策略越容易出现,这就是多重检验(multiple testing)问题。本节讲清三件事:回测过拟合与数据窥探(data-snooping)如何制造虚假显著性;Bailey 与 López de Prado 提出的 Deflated Sharpe Ratio(简称 DSR)如何在「策略数量、收益非正态、样本较短」三重污染下修正夏普比率的显著性判断;以及如何用一段简洁的 Python 代码估计「期望的最大夏普比率」,把运气从能力中剥离。读完本节,你会对任何「漂亮回测」保持应有的怀疑。

内容来源:原项目 08_ml4t_workflow/01_multiple_testing/deflated_sharpe_ratio.py,汉化并套用体系化模板。

⚠️ 学习提示:本节涉及资金风险判断。一个未经多重检验修正的高夏普策略,实盘表现往往大幅衰减——「过拟合的回测」是量化交易亏钱的首要原因之一。

学习目标

阅读完本节,你应当能够:

  1. 说清回测过拟合数据窥探为何会制造虚假显著性。
  2. 理解多重检验如何系统性地抬高「最佳策略」的期望夏普。
  3. 解释 Deflated Sharpe Ratio 修正夏普显著性的三个维度。
  4. 读懂 deflated_sharpe_ratio.py解析解数值解实现。
  5. 在评估自己的策略时,主动报告试过的策略数量而非只报最优夏普。

一、回测的本质与三大陷阱

回测(backtest)是用历史数据模拟一个策略,目标是判断它在未见过的市场中是否还能赚。但历史数据有限、市场不断变化,实现细节中暗藏三类系统性偏差:

陷阱类别 典型表现 后果
数据 用了未来才公布的数据;只保留活下来的股票 收益虚高
模拟 假设总能以收盘价成交、忽略冲击成本 收益虚高
统计 试了 200 个策略,挑出夏普最高的 显著性虚高

前两类靠工程手段能缓解(用点在时数据、加滑点手续费),第三类最隐蔽——它不是 bug,而是「选择」本身造成的。López de Prado 在这块发表过大量研究,并提供了在线的过拟合模拟器供直观感受。

💡 核心心法:回测不是「证明策略有效」的法庭,而是「收集证据决定是否值得上实盘」的筛子。筛子本身有偏,你必须知道偏在哪里。

二、多重检验:为什么试得越多越不可信

直觉:扔硬币的类比

扔 1 枚硬币,正面概率 0.5;但同时扔 100 枚,至少有一枚连续 10 次正面的概率并不小。如果你只盯着那枚「连掷 10 次正面」的硬币看,会误以为它有魔法——其实它只是被你「事后」选出来的。

策略回测同理。如果你在同一份历史数据上试了 N 个候选策略,然后挑出夏普最高的,这个「最高」里混进了选择效应:即便所有策略的真实期望夏普都是 0,纯靠运气,N 越大,观察到的最大夏普也越高。

期望最大夏普的解析式

Bailey 与 López de Prado 推导了在「策略相互独立、收益正态」假设下,试 N 次能期望看到的最大夏普。原项目脚本的核心函数如下:

from scipy.stats import ss def get_analytical_max_sr(mu, sigma, num_trials): """计算期望的最大夏普比率(解析解)""" emc = 0.5772156649 # 欧拉-马歇罗尼常数 maxZ = ((1 - emc) * ss.norm.ppf(1 - 1. / num_trials) + emc * ss.norm.ppf(1 - 1 / (num_trials * np.e))) return mu + sigma * maxZ

这里 musigma 是单次试验夏普分布的均值与标准差,num_trials 是试的策略数。关键变量是 maxZ——它是「N 次试验中最大标准正态分位数」的期望,随 N 单调上升。换句话说:试的策略越多,「最佳」的门槛被自然抬高,但这是运气的抬高,不是能力的抬高。

试的策略数 N 含义
1 单个策略,夏普显著性与平时理解一致
10 期望最大夏普已明显高于单次
100 即便所有策略真实期望为 0,也可能看到看似「显著」的高夏普
1000 几乎一定会有若干策略夏普「漂亮」——但全是噪声

三、Deflated Sharpe Ratio:三重修正

DSR 的目标是在三个污染源下,计算「这个夏普在统计上仍显著的概率」:

  1. 多重检验:你试了 N 个策略,需要扣除「挑最大」带来的虚高。
  2. 收益非正态:金融收益有偏态与肥尾,夏普的标准误会偏。DSR 用偏度 \gamma_3 与峰度 \gamma_4 调整夏普的方差。
  3. 样本较短:样本越短,夏普估计越不准,显著性越要打折扣。

经过修正后,你看到的不再是「裸夏普 2.5 看起来很棒」,而是「在试了 50 个策略、收益明显非正态、只有 3 年样本的条件下,这个夏普真正显著的概率可能只有 0.3」。两个数字天差地别。

⚠️ 常见误用:很多人只在论文/路演里报最优策略的夏普,却绝口不提试了多少个。一个不报 N 的夏普,信息量接近于零。

四、代码:解析解 vs 数值解的对照

原项目脚本同时给出解析解和数值解,二者相互校验。数值解的做法是「真的去模拟很多轮,每轮抽 N 个策略,记最大夏普,再取平均」:

def get_numerical_max_sr(mu, sigma, num_trials, n_iter): """数值解:蒙特卡洛估计期望最大夏普""" max_sr, count = [], 0 while count < n_iter: count += 1 series = np.random.normal(mu, sigma, num_trials) max_sr.append(max(series)) return np.mean(max_sr), np.std(max_sr)

主循环遍历不同的 munum_trials 组合,把两种解与误差写成 DSR.csv,用来验证解析公式的准确性。当解析解与数值解吻合时,我们就可以放心用解析公式快速做修正,而不必每次都跑蒙特卡洛。

💡 动手建议:把脚本里的 num_trials 改成 5、50、500 各跑一遍,你会直观地看到「期望最大夏普」随 N 上涨的曲线——这条曲线就是「虚假显著性」的形状。

五、最小回测长度的启示

DSR 框架还派生出一个实用结论:一个真实夏普为 \text{SR}_0 的策略,需要的最小回测年限大致正比于 \text{SR}_0 的平方,且与试的策略数同向增长。也就是说:

  • 你声称的策略夏普越高,需要的历史样本越长才能信;
  • 你试的策略越多,需要的样本也越长。

这条结论直接击碎了「我用 2 年数据回测出夏普 3.0 的策略」这类说法——在多重检验视角下,2 年样本几乎不足以支撑这么高的夏普显著性。

本节要点回顾

  1. 回测三大陷阱:数据(前视/幸存者)、模拟(成交不现实)、统计(数据窥探/多重检验),最后者最隐蔽。
  2. 多重检验:在同一份数据上试 N 个策略再挑最大,「最大」会随 N 自然抬高,这是运气不是能力。
  3. 期望最大夏普:get_analytical_max_sr 用欧拉常数与正态分位数给出解析解,与蒙特卡洛数值解互相校验。
  4. DSR 三重修正:多重检验、收益非正态(偏度+峰度)、样本短,三者共同决定夏普的「真实显著性」。
  5. 报告纪律:报夏普必须同时报试了多少个策略,否则数字几乎无信息量。
  6. 最小回测长度:声称的夏普越高、试的策略越多,所需样本越长,「短样本+高夏普」基本是过拟合信号。

下一节,我们看向量化回测实战——用最简洁的 pandas 把信号、持仓、收益串起来,快速看一个策略雏形的盈亏。


发布者: 作者: 灏天文库 转发
评论区 (0)
U