功能

回撤分析

通过记录衡量每次回撤的深度、持续时间与恢复

策略下跌了多深,低迷持续多久,恢复速度如何?VBT 将每次回撤转换为一条包含峰值、谷底和恢复情况的记录,让 Python 中的回撤分析成为可排序、筛选和绘图的表格,并与熟悉的最大回撤和水下曲线一起使用。

Find the five worst drawdowns of a 60/40 portfolio
data = vbt.YFData.pull(["SPY", "TLT"], start="2005-01-01", end="2025-01-01")
rebalance = ~data.index.tz_localize(None).to_period("M").duplicated()
weights = data.symbol_wrapper.fill(np.nan)
weights[rebalance] = [0.6, 0.4]
pf = vbt.PF.from_orders(
    data,
    size=weights,
    size_type="targetpercent",
    group_by=True,
    cash_sharing=True,
    call_seq="auto",
)

dd = pf.drawdowns  
pd.DataFrame({
    "peak": dd.readable["Start Index"].dt.date,
    "valley": dd.readable["Valley Index"].dt.date,
    "depth": dd.drawdown.values,
    "decline": dd.decline_duration.values,  
    "recovery": dd.recovery_duration.values,
    "status": dd.readable["Status"],
}).sort_values("depth").head(5).round(3)
           peak      valley  depth  decline  recovery     status
48   2007-12-06  2009-03-09 -0.311    314.0     388.0  Recovered
298  2021-12-27  2022-10-20 -0.275    206.0     475.0  Recovered
254  2020-02-20  2020-03-18 -0.184     19.0      48.0  Recovered
226  2018-08-29  2018-12-24 -0.107     80.0      55.0  Recovered
176  2015-03-20  2015-08-25 -0.078    109.0     142.0  Recovered
dd.plot(top_n=5).show()
2005 至 2024 年按月再平衡的 SPY 与 TLT 60/40 投资组合价值,突出显示五次最大回撤 图表数据(JSON)

2022 年的回撤比 2008 年浅,但恢复耗时更久:475 个交易日,而非 388 个,因为股票和债券同时下跌。

回撤记录

pf.drawdowns 为价值序列中的每次回撤保存一条记录。记录包含峰值、谷底、结束位置、这些时点的价值,以及状态:已恢复,或截至数据末尾仍未恢复。由于它们是记录,因此可以筛选,例如 dd.status_recovered,映射到任意字段,并通过 dd.stats() 生成统计。注意命名:不带“s”的 pf.drawdown 是当前价值低于累计峰值的百分比时间序列。

回撤深度以峰值价值的百分比表示。如果策略从大账户中拿出固定金额交易,大部分价值是闲置现金,百分比便显得很小。货币金额回撤只需访问相应字段:(dd.start_val - dd.valley_val).max() 返回以账户币种计的最大峰值跌幅。

同样的记录适用于任意序列,而非仅限投资组合。price.vbt.drawdowns 可以查找价格、指标或其他来源权益曲线的回撤。

持续时间与恢复

深度只是风险的一半。下跌持续时间统计从峰值到谷底的 K 线数,恢复持续时间则统计从谷底回到峰值的 K 线数。水下曲线同时显示两者:

Plot the underwater curve
pf.plot_underwater().show()
2005 至 2024 年按月再平衡的 SPY 与 TLT 60/40 投资组合水下曲线 图表数据(JSON)

低于此前峰值的时间占比为 (pf.drawdown < 0).mean(),该投资组合为 87.3% 的 K 线。如果策略应尽量缩短水下时间,这可以作为有用的优化目标。

未恢复回撤与平均回撤

截至数据末尾仍未恢复的回撤属于活跃回撤。它已经有深度和持续时间,但尚无恢复过程的完整结果。pf.stats() 在最大回撤中包含活跃回撤,而 dd.stats() 默认不将其纳入汇总,除非在 vbt.settings.drawdowns 中启用 incl_active。您也可通过 dd.stats(settings=dict(incl_active=True)) 仅在某次报告中将其纳入。

活跃回撤可能已经开始恢复。最差深度说明账户曾下跌多少,而当前深度说明还需恢复多少:

Separate the worst loss from the loss still to recover
equity = pd.Series([100.0, 90.0, 100.0, 120.0, 90.0, 108.0], index=pd.date_range("2026-01-01", periods=6))
equity_dd = equity.vbt.drawdowns
pd.Series({
    "Worst recovered [%]": equity_dd.status_recovered.max_drawdown * 100,
    "Worst overall [%]": equity_dd.max_drawdown * 100,
    "Current drawdown [%]": equity_dd.active_drawdown * 100,
}).round(1)
Worst recovered [%]    -10.0
Worst overall [%]      -25.0
Current drawdown [%]   -10.0
dtype: float64

第一次从 100 跌至 90 后已完全恢复。第二次从 120 跌至 90,亏损 25%,仅恢复到 108。账户目前低于峰值 10%,但排除这次未完成回撤会掩盖最严重的损失。这既适用于外部权益曲线,也适用于 VBT 投资组合,因此可以使用相同工具比较它们的回撤历史。

平均回撤有两种含义。记录深度的平均值 dd.avg_drawdown 在此为 1.2%,因为数百次浅回撤与深回撤具有同样权重。回撤序列的时间平均值 pf.drawdown.mean() 则为 4.5%,因为深回撤持续更久。报告时请说明使用哪种定义。

参数网格中的回撤

最大回撤与其他指标一样,可以跨参数比较。此处以夏普比率和最大回撤,展示绩效与风险指标页面中的 100 组均线参数:

Map Sharpe ratio and maximum drawdown across 100 parameter pairs
btc = vbt.YFData.pull("BTC-USD", start="2020-01-01", end="2025-01-01")
fast_w, slow_w = np.meshgrid(np.arange(5, 55, 5), np.arange(60, 260, 20))
fast = vbt.MA.run(btc.close, window=fast_w.ravel(), short_name="fast")
slow = vbt.MA.run(btc.close, window=slow_w.ravel(), short_name="slow")
grid_pf = vbt.PF.from_signals(
    btc,
    fast.ma_crossed_above(slow),
    fast.ma_crossed_below(slow),
    fees=0.001,
)

fig = vbt.make_subplots(
    rows=1,
    cols=2,
    subplot_titles=["Sharpe ratio", "Max drawdown"],
    horizontal_spacing=0.15,
)
grid_pf.sharpe_ratio.vbt.heatmap(
    x_level="fast_window",
    y_level="slow_window",
    fig=fig,
    add_trace_kwargs=dict(row=1, col=1),
    trace_kwargs=dict(colorbar=dict(x=0.43, len=0.9)),
)
grid_pf.max_drawdown.vbt.heatmap(
    x_level="fast_window",
    y_level="slow_window",
    fig=fig,
    add_trace_kwargs=dict(row=1, col=2),
    trace_kwargs=dict(colorbar=dict(x=1.0, len=0.9)),
)
fig.show()
100 组比特币均线窗口参数的夏普比率和最大回撤热图 图表数据(JSON)

夏普比率最高的组合为 5 天和 120 天,其最差跌幅为 38.6%;而 5 天和 160 天的组合在网格中回撤最浅,为 30.1%。在整个网格中,较高夏普比率往往伴随更深回撤。

结合两张图,寻找收益与回撤平衡值得进一步研究的参数区域。稳健性与过拟合介绍如何检验这些结果能否超越最佳回测而成立。

权益等于或低于零

回撤相对于峰值价值衡量,因此只有价值保持为正时才以 100% 为上限。使用杠杆或空头持仓时,价值可能跌至零以下,此时回撤会超过 100%。模拟器不会在这一刻自动清算账户。如需在此结束分析,请将模拟结束位置设为价值首次达到零的 K 线。

版权所有 © 2021–2026 Oleg Polakow。保留所有权利。

本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。