6 · 28 分钟

绩效指标与调试

Sharpe、回撤、CAGR — 以及实验室常见错误的修复方法。

Sharpe回撤调试过拟合

没有指标的回测只是一张图。Quant Buffet 报告风险调整后的统计量,让你能诚实地比较策略;代码出错时,还会用AI 调试器给出行号。

Drag the sliders to see how Sharpe and Calmar react. These are the same families of stats Quant Buffet reports after a lab run.

Sharpe (approx.)
0.44
(CAGR − RF) / Vol
Calmar (approx.)
0.55
CAGR / |Max DD|
Interpretation

Weak — check overfitting or costs

Lab errorLikely fix
Signal never readyShorten lookback or extend start date; check ASSETS history.
Import blockedOnly backtest.*, numpy, pandas — see API docs.
Symbol not in whitelistUse ETFs from backtest.universes (SPY, TLT, GLD…).
Equity curve too shortEnsure on_day actually calls set_target_weights.

指标词汇

指标含义审慎看待
CAGR路径重演时的年均复合增长单个优异十年可能夸大它
Volatility净值波动程度低波可能掩盖杠杆或流动性差
Sharpe单位风险的收益只有两年数据时毫无意义
Sortino单位*下行*风险的收益对有上行尖峰的策略更友好
Max drawdown最大峰谷亏损你必须在情绪上能扛住的数字
Alpha / Beta相对 SPY 的超额收益;市场敏感度代理基准不等于你的真实组合
Win rate上涨天数占比70% 胜率依然可能亏钱

先建立参照,才知道什么叫「好」

基准大致长期 CAGR大致波动率大致 Sharpe最差回撤
SPY 买入持有约 10%约 15–19%约 0.4–0.5约 -55%
经典 60/40约 8%约 10%约 0.5–0.6约 -30%
BIL(现金)政策利率约 0.3%不适用约 -0.1%

先对照这些数字再决定要不要兴奋。仅做多的日频 ETF 策略若 Sharpe 达到 0.8,已经相当不错;1.5 属于极优且需要仔细审查;而简单日频回测中 Sharpe 超过 2,几乎总意味着前视偏差、不现实的成本假设,或一个「挑过存活者」的资产池。一个参照:麦道夫伪造的收益隐含 Sharpe 约 2.5,而这种不合理正是最早的公开疑点之一。

为何回撤比 CAGR 更重要

回撤是回测与人性交汇的地方。两个事实足以说明问题:亏损 50% 需要上涨 100% 才能回本,而不是 50%;而回本时间可能长达一段投资生涯——纳斯达克综合指数在 2000 年 3 月见顶,直到 2015 年才稳定收复该点位,也就是大约十五年被埋在水下。与之对照,现代史上最快的崩盘(2020 年 2 月至 3 月)只用 33 天就跌了约 34%。

回撤回本所需涨幅现实反应
-10%+11%不舒服
-20%+25%开始质疑模型
-35%+54%多数人在此放弃策略
-50%+100%职业或资本风险

多重检验:反复尝试的统计学

如果你只测一个策略,Sharpe 1.0 是证据;如果你测了一千个并报告最好的那个,Sharpe 1.0 大致就是纯随机所能给出的结果——众多噪声样本的最大值天然偏大。正因如此,学界提出了去偏 Sharpe 比率(Bailey 与 López de Prado),按「产生该结果所用的尝试次数」对其进行惩罚。

  • 统计你的尝试次数。 换十个回看窗口重跑十次,就是十次检验,即使你只截图了一张。
  • 偏好参数平台区。 若 150、200、250 天都有效,效应大概是真的;若只有 187 天有效,那是噪声。
  • 预留一段样本。 用 2000–2015 年建立直觉,再对 2016 年至今只看一次。
  • 要求给出理由。 库中每个策略都附经济逻辑,正因为「数字好看」不是理由。

调试流程

  1. 阅读 IDE 行号栏高亮处的错误信息与行号
  2. 对照契约检查文件:模块级 ASSETS,以及返回 (on_day, ready)make_on_day(prices)
  3. 点击 Ask AI for syntax — 获得「在哪里、为什么」以及可直接粘贴的代码片段。
  4. 查阅 API 文档,确认导入项、白名单标的与模板示例。
  5. 更短的 `ASSETS` 和更晚的 start 重跑,以隔离数据问题。

你真正会遇到的五种错误

错误真实原因修复
NameError: ASSETS is not definedASSETS 定义在函数内部移到文件顶部的模块级
Symbol not in whitelist该标的在实验室中无法模拟换成白名单代理,例如用 EEM 替代单只新兴市场股票
TypeError: cannot unpack non-sequencemake_on_day 只返回了 on_day返回元组 (on_day, ready)
KeyError: <date>索引的序列在 dt 这一行不存在重建索引到 prices.index,或用 .reindex(close.index).ffill()
Timeout: exceeded 90s在长历史上逐日循环计算rolling/shift 向量化,放到 on_day 之外
python
# 慢:每一天都重算一遍 200 日均值(会超时)
def on_day(engine, dt):
    window = prices.loc[:dt, "SPY"].tail(200)
    if prices.loc[dt, "SPY"] > window.mean():
        engine.set_target_weights(dt, {"SPY": 1.0})

# 快:算一次,然后查表
sma = prices["SPY"].rolling(200, min_periods=200).mean()

def on_day(engine, dt):
    if prices.loc[dt, "SPY"] > sma.loc[dt]:
        engine.set_target_weights(dt, {"SPY": 1.0})
python
# 运行成功后平台报告的内容
metrics = compute_metrics(result.equity, benchmark=spy_bh, trades_count=len(result.trades))
print(metrics["sharpe"], metrics["max_drawdown"], metrics["cagr"])

进入第 7 课前 — 您应能够

  • 大致说出 SPY60/40 的 Sharpe 和回撤水平。
  • 解释为何日频 ETF 回测中 Sharpe 超过 2 是危险信号。
  • 把回撤换算成回本所需涨幅
  • 无需帮助即可诊断五类常见实验室错误。
  • 说明多重检验如何抬高你找到的最优结果。