跳到主要内容

回撤与最大回撤

直觉

「你的组合从最高点跌了多少?」——这就是回撤。它是投资者心理上最直接的风险感受:账面富贵缩水的痛苦。最大回撤(MDD) 衡量这段历史上最惨的一次「从巅峰到谷底」有多深,是评估策略「能不能拿得住」的关键。

数学原理

权益曲线 VtV_t,截至 tt 的历史最高点(running max)为 Mt=maxstVsM_t=\max_{s\le t} V_s。回撤序列:

Dt=VtMtMt(,0]D_t = \frac{V_t - M_t}{M_t} \in (-\infty, 0]

最大回撤取其绝对值的最深点:

MDD=maxt MtVtMt=mintDt\text{MDD} = \max_t\ \frac{M_t - V_t}{M_t} = -\min_t D_t
「人话」解释:回撤和波动率哪个更该看?

波动率把所有涨跌一视同仁地累加;回撤只盯「从高点往下跌」的部分,更贴近「真实亏损体验」。 一个策略可能年化波动不高,但某次回撤 −50%——意味着你要承受资产腰斩的煎熬,很多人此刻就割肉了。 所以最大回撤常被当作「能不能拿住」的底线指标。

可运行案例:画出「水下曲线」

读取 spy_daily.csv,构造买入持有权益,画出回撤序列(underwater curve),并报告最大回撤及其发生位置。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('/data/spy_daily.csv', parse_dates=['date']).set_index('date')
equity = (df['adj_close'] / df['adj_close'].iloc[0])

running_max = equity.cummax()
drawdown = equity / running_max - 1          # 负值序列

mdd = -drawdown.min()
trough = drawdown.idxmin()
peak = equity.loc[:trough].idxmax()
print(f"最大回撤   : {mdd:.2%}")
print(f"峰值日期   : {peak.date()}   价格={equity.loc[peak]:.3f}")
print(f"谷底日期   : {trough.date()}   价格={equity.loc[trough]:.3f}")

fig, ax = plt.subplots(2, 1, figsize=(9, 5.5), sharex=True,
                     gridspec_kw={'height_ratios':[2,1]})
ax[0].plot(equity, color="#2563eb"); ax[0].plot(running_max, color="#94a3b8", ls='--', lw=1)
ax[0].set_title("权益曲线(蓝) 与 历史最高点(灰虚线)")
ax[1].fill_between(drawdown.index, drawdown, 0, color="tab:red", alpha=0.5)
ax[1].set_title("回撤(水下曲线)"); ax[1].set_ylabel("回撤")
plt.tight_layout(); plt.show()

动手改一改

权益曲线里,灰虚线(历史最高点)和蓝线之间的「缝隙」就是回撤。试着在脑中标记最宽的缝隙——它对应的就是最大回撤时刻,和上方打印的谷底日期一致。

小结

  • 回撤 = 当前权益相对历史最高点的跌幅,永远 0\le 0
  • 最大回撤 = 历史上最深的一次「巅峰到谷底」;
  • 回撤比波动率更贴近「亏损体验」,是「能不能拿住策略」的核心指标。