Skip to content

最大回撤分析

引言

在量化投资中,我们很容易被高收益所吸引——一个年化收益率 30% 的策略看起来非常诱人。但收益只是故事的一半。真正考验投资者承受能力的问题是:这个策略在运行过程中,最坏的时候会亏多少? 这就是最大回撤(Maximum Drawdown)要回答的问题。

最大回撤是衡量投资风险最直观、最重要的指标之一。它告诉你,在历史上最糟糕的一段时期内,你的策略从最高点到最低点损失了多少。理解最大回撤,不仅是评估策略的需要,更是管理自身心理预期的需要。因为一个策略能否坚持运行,往往不取决于它赚了多少,而取决于你能否承受它最坏的时刻。


一、最大回撤的定义

1.1 什么是回撤

**回撤(Drawdown)**是指策略净值从历史最高点下跌的幅度。用公式表示:

当前回撤 = (当前净值 - 历史最高净值) / 历史最高净值

举个例子:

  • 你的策略净值从 1.0 涨到 1.5(历史最高点)
  • 之后净值下跌到 1.2
  • 此时的回撤 = (1.2 - 1.5) / 1.5 = -20%

注意,回撤始终是负数或零(在净值创新高时回撤为零)。日常交流中,我们通常说"回撤了 20%",省略负号。

1.2 什么是最大回撤

**最大回撤(Maximum Drawdown,MDD)**是在整个考察期间内,回撤的最大绝对值。简单说就是:从策略开始运行到当前,净值从最高点到最低点的最大跌幅。

最大回撤 = min(所有回撤值)

或者更直观地:

最大回撤 = (谷底净值 - 峰值净值) / 峰值净值

其中峰值是谷底之前出现的历史最高净值。

1.3 最大回撤的时间维度

最大回撤不仅有一个幅度,还有一个持续时间:

  • 回撤期(Drawdown Period):从净值创新高开始,到净值恢复(再次创新高)为止的时间跨度。
  • 回撤恢复时间(Recovery Time):从净值最低点恢复到创新高所需要的时间。

一个策略的最大回撤可能只有 15%,但如果花了 3 年才恢复,这对投资者的心理考验同样巨大。


二、最大回撤的计算方法

2.1 Python 实现最大回撤计算

下面我们用 Python 来计算最大回撤:

python
import numpy as np
import pandas as pd

def calculate_max_drawdown(nav_series):
    """
    计算最大回撤

    参数:
        nav_series: pd.Series, 净值序列,索引为日期

    返回:
        max_drawdown: float, 最大回撤(负数)
        peak_date: 峰值日期
        trough_date: 谷底日期
        recovery_date: 恢复日期(如果已恢复)
    """
    # 计算历史最高净值的累计序列
    cumulative_max = nav_series.cummax()

    # 计算每个时点的回撤
    drawdown = (nav_series - cumulative_max) / cumulative_max

    # 找到最大回撤
    max_drawdown = drawdown.min()

    # 找到谷底日期
    trough_idx = drawdown.idxmin()
    trough_date = trough_idx

    # 找到峰值日期(谷底之前的最高点)
    peak_idx = nav_series[:trough_idx].idxmax()
    peak_date = peak_idx

    # 找到恢复日期(谷底之后净值首次超过峰值)
    peak_value = nav_series[peak_idx]
    post_trough = nav_series[trough_idx:]
    recovery_mask = post_trough >= peak_value
    if recovery_mask.any():
        recovery_date = post_trough[recovery_mask].index[0]
    else:
        recovery_date = None  # 尚未恢复

    return max_drawdown, peak_date, trough_date, recovery_date


# 示例:模拟一个策略净值序列
np.random.seed(42)
dates = pd.date_range('2020-01-01', '2023-12-31', freq='B')  # 工作日
returns = np.random.normal(0.0003, 0.015, len(dates))  # 日收益率
nav = pd.Series((1 + pd.Series(returns)).cumprod(), index=dates)

mdd, peak, trough, recovery = calculate_max_drawdown(nav)

print(f"最大回撤: {mdd:.2%}")
print(f"峰值日期: {peak.strftime('%Y-%m-%d')}")
print(f"谷底日期: {trough.strftime('%Y-%m-%d')}")
if recovery:
    print(f"恢复日期: {recovery.strftime('%Y-%m-%d')}")
    print(f"回撤持续天数: {(recovery - peak).days}")
else:
    print("策略尚未恢复")

2.2 滚动最大回撤

在实际应用中,我们经常关注滚动最大回撤——过去 N 天内的最大回撤,这能帮助我们了解策略风险的动态变化:

python
def rolling_max_drawdown(nav_series, window=252):
    """
    计算滚动最大回撤

    参数:
        nav_series: pd.Series, 净值序列
        window: int, 滚动窗口大小(默认252个交易日,约1年)

    返回:
        pd.Series, 滚动最大回撤序列
    """
    rolling_mdd = pd.Series(index=nav_series.index, dtype=float)

    for i in range(window, len(nav_series)):
        window_nav = nav_series.iloc[i - window:i]
        cumulative_max = window_nav.cummax()
        drawdown = (window_nav - cumulative_max) / cumulative_max
        rolling_mdd.iloc[i] = drawdown.min()

    return rolling_mdd

2.3 回撤曲线可视化

绘制回撤曲线可以直观地展示策略的历史回撤情况:

python
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

def plot_drawdown(nav_series, title="策略回撤分析"):
    """绘制策略净值和回撤曲线"""
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 8),
                                     gridspec_kw={'height_ratios': [2, 1]},
                                     sharex=True)

    # 上图:净值曲线
    ax1.plot(nav_series.index, nav_series.values, color='#2196F3', linewidth=1.2)
    ax1.set_ylabel('策略净值', fontsize=12)
    ax1.set_title(title, fontsize=14)
    ax1.grid(True, alpha=0.3)

    # 计算回撤
    cumulative_max = nav_series.cummax()
    drawdown = (nav_series - cumulative_max) / cumulative_max * 100

    # 下图:回撤曲线(填充区域)
    ax2.fill_between(drawdown.index, drawdown.values, 0,
                     color='#F44336', alpha=0.4)
    ax2.plot(drawdown.index, drawdown.values, color='#F44336', linewidth=0.8)
    ax2.set_ylabel('回撤 (%)', fontsize=12)
    ax2.set_xlabel('日期', fontsize=12)
    ax2.grid(True, alpha=0.3)

    # 标注最大回撤位置
    mdd_idx = drawdown.idxmin()
    mdd_val = drawdown.min()
    ax2.annotate(f'最大回撤: {mdd_val:.1f}%',
                xy=(mdd_idx, mdd_val),
                xytext=(mdd_idx, mdd_val * 0.6),
                arrowprops=dict(arrowstyle='->', color='black'),
                fontsize=11, fontweight='bold')

    plt.tight_layout()
    plt.show()

# 使用示例
plot_drawdown(nav, title="模拟策略回撤分析")

三、恢复难度:跌得越深,爬得越难

3.1 回撤与恢复的不对称性

最大回撤最令人恐惧的地方在于它的不对称性——下跌和上涨需要的幅度是不对等的。

回撤幅度恢复所需涨幅恢复/回撤比
-5%+5.3%1.05x
-10%+11.1%1.11x
-20%+25.0%1.25x
-30%+42.9%1.43x
-40%+66.7%1.67x
-50%+100.0%2.00x
-60%+150.0%2.50x
-70%+233.3%3.33x
-80%+400.0%5.00x
-90%+900.0%10.00x

计算公式很简单:

恢复所需涨幅 = 1 / (1 - 回撤幅度) - 1

关键结论:跌 50% 需要涨 100% 才能回本。 这意味着一次严重的回撤,可能需要数年才能恢复。许多投资者在深度回撤中丧失信心,在最低点割肉离场,永远失去了回本的机会。

3.2 不同回撤水平的恢复时间估算

假设一个策略的年化收益率为 R%,从最大回撤中恢复所需的时间大致为:

python
def estimate_recovery_time(drawdown_pct, annual_return_pct):
    """
    估算从回撤中恢复所需的年数

    参数:
        drawdown_pct: float, 最大回撤百分比(正数,如 30 表示 30%)
        annual_return_pct: float, 年化收益率百分比(正数)

    返回:
        float, 预估恢复年数
    """
    recovery_needed = 1 / (1 - drawdown_pct / 100) - 1
    annual_return = annual_return_pct / 100
    if annual_return <= 0:
        return float('inf')
    # 粗略估计(不考虑复利)
    years = recovery_needed / annual_return
    return years

# 示例:不同回撤水平下的恢复时间
for dd in [10, 20, 30, 50]:
    years = estimate_recovery_time(dd, 15)  # 假设年化15%
    print(f"回撤 {dd}% → 需要约 {years:.1f} 年恢复(年化15%)")

输出:

回撤 10% → 需要约 0.7 年恢复(年化15%)
回撤 20% → 需要约 1.7 年恢复(年化15%)
回撤 30% → 需要约 2.9 年恢复(年化15%)
回撤 50% → 需要约 6.7 年恢复(年化15%)

3.3 不对称性的启示

回撤与恢复的不对称性告诉我们一个重要道理:控制下行风险比追求上行收益更重要。

一个策略如果能把最大回撤控制在 15% 以内,即使年化收益率"只有" 15%,其长期复合回报可能优于一个年化 25% 但最大回撤 40% 的策略。因为后者在深度回撤中损失了太多本金,恢复起来非常困难。

这也是巴菲特投资准则"第一条不要亏钱,第二条记住第一条"的数学基础。

示例:最大回撤示例

0%-18%最大回撤 -18.0%1月5月9月1月5月9月

回撤是策略净值从最高点到最低点的跌幅,越小说明策略抗风险能力越强


四、不同策略的最大回撤特征

4.1 常见策略类型的典型回撤范围

策略类型典型年化收益典型最大回撤说明
沪深300指数持有8%-12%40%-70%纯多头,回撤大
均线趋势跟踪10%-20%20%-35%有止损机制,回撤可控
均值回归策略10%-15%15%-25%回撤较浅但频率高
多因子选股12%-20%15%-30%分散化降低回撤
市场中性策略5%-10%5%-15%对冲掉市场风险
CTA 期货策略15%-25%15%-30%多空双向交易

以上数据仅为历史参考区间,不构成对未来表现的任何保证。

4.2 回撤的分布特征

最大回撤不是一个固定值,它会随时间变化。通过蒙特卡洛模拟,我们可以了解最大回撤的概率分布:

python
def simulate_max_drawdown_distribution(annual_return=0.10, annual_vol=0.20,
                                        years=3, n_simulations=10000):
    """
    蒙特卡洛模拟最大回撤分布

    参数:
        annual_return: 年化收益率
        annual_vol: 年化波动率
        years: 模拟年数
        n_simulations: 模拟次数

    返回:
        max_drawdowns: 最大回撤数组
    """
    days = years * 252
    daily_return = annual_return / 252
    daily_vol = annual_vol / np.sqrt(252)

    max_drawdowns = np.zeros(n_simulations)

    for i in range(n_simulations):
        # 生成随机收益率序列
        daily_returns = np.random.normal(daily_return, daily_vol, days)
        # 计算净值曲线
        nav = np.cumprod(1 + daily_returns)
        # 计算最大回撤
        cummax = np.maximum.accumulate(nav)
        drawdowns = (nav - cummax) / cummax
        max_drawdowns[i] = drawdowns.min()

    return max_drawdowns

# 模拟并查看分位数
mdds = simulate_max_drawdown_distribution()
for q in [50, 75, 90, 95, 99]:
    print(f"最大回撤的 {q}% 分位数: {np.percentile(mdds, q):.1%}")

这个模拟会告诉我们,在给定的收益和波动率假设下,最大回撤超过某个阈值的概率有多大。


五、降低最大回撤的方法

5.1 仓位管理

仓位管理是控制最大回撤最直接的手段。核心思想是:回撤与仓位成正比。

python
def kelly_position(win_rate, win_loss_ratio):
    """
    凯利公式计算最优仓位比例

    参数:
        win_rate: 胜率
        win_loss_ratio: 盈亏比(平均盈利/平均亏损)

    返回:
        float, 凯利最优仓位比例
    """
    kelly = win_rate - (1 - win_rate) / win_loss_ratio
    return max(0, kelly)

# 实际使用中通常使用"半凯利"以降低风险
win_rate = 0.55
win_loss_ratio = 1.5
full_kelly = kelly_position(win_rate, win_loss_ratio)
half_kelly = full_kelly / 2

print(f"全凯利仓位: {full_kelly:.1%}")
print(f"半凯利仓位: {half_kelly:.1%}")

常用方法:

  • 固定比例仓位:每只股票不超过总仓位的 10%。
  • 凯利公式及半凯利:根据胜率和盈亏比动态计算最优仓位。
  • 波动率调仓:高波动品种降低仓位,低波动品种可以提高仓位。
  • 风险平价:按各资产的波动率倒数分配仓位,使每类资产对组合风险的贡献相等。

5.2 止损机制

止损是控制单笔交易最大亏损的底线。常见的止损方法:

python
# 方法1:固定百分比止损
def fixed_stop_loss(entry_price, stop_pct=0.08):
    """固定百分比止损"""
    return entry_price * (1 - stop_pct)

# 方法2:ATR 止损
def atr_stop_loss(entry_price, atr_value, multiplier=2):
    """基于 ATR 的动态止损"""
    return entry_price - multiplier * atr_value

# 方法3:移动止损(Trailing Stop)
def trailing_stop_check(current_price, highest_since_entry, trail_pct=0.10):
    """移动止损检查"""
    stop_price = highest_since_entry * (1 - trail_pct)
    return current_price <= stop_price  # True 表示触发止损

# 方法4:时间止损
def time_stop_check(days_since_entry, max_holding_days=20):
    """时间止损:持仓超过N天且未达目标则平仓"""
    return days_since_entry >= max_holding_days

5.3 分散投资

分散投资是降低回撤的根本方法。通过持有相关性低的多个标的或策略,可以有效平滑净值曲线:

python
def portfolio_drawdown_vs_single(weights, correlations, single_mdd=0.25):
    """
    简单估算分散化对回撤的降低效果

    参数:
        weights: 各资产权重列表
        correlations: 资产间平均相关系数
        single_mdd: 单个资产的最大回撤

    返回:
        float, 组合最大回撤估算
    """
    n = len(weights)
    # 简化模型:组合回撤 ≈ 单资产回撤 × √(加权相关性)
    effective_correlation = correlations
    portfolio_mdd = single_mdd * np.sqrt(effective_correlation)
    return portfolio_mdd

# 示例:5个资产,不同相关性水平
for corr in [0.3, 0.5, 0.7, 0.9]:
    est_mdd = portfolio_drawdown_vs_single(
        weights=[0.2]*5, correlations=corr, single_mdd=0.25
    )
    print(f"资产相关性 {corr} → 组合估算最大回撤: {est_mdd:.1%}")

5.4 对冲

对冲是通过建立相反方向的头寸来抵消部分风险:

  • 股指期货对冲:持有股票多头的同时,做空股指期货,对冲掉市场系统性风险。
  • 期权保护:买入认沽期权(Put Option)作为保险,限制最大亏损。
  • 配对交易:做多一个标的、做空另一个高度相关的标的,赚取价差回归的收益。

5.5 动态风险控制

根据市场环境动态调整策略的激进程度:

python
def dynamic_risk_sizing(current_drawdown, max_acceptable_dd=0.15,
                         base_position=1.0):
    """
    根据当前回撤动态调整仓位

    参数:
        current_drawdown: 当前回撤(负数,如 -0.10 表示 -10%)
        max_acceptable_dd: 最大可接受回撤(正数)
        base_position: 基础仓位比例

    返回:
        float, 调整后的仓位比例
    """
    dd_ratio = abs(current_drawdown) / max_acceptable_dd
    # 回撤越大,仓位越小
    adjusted = base_position * max(0, 1 - dd_ratio)
    return adjusted

# 示例
for dd in [-0.03, -0.06, -0.09, -0.12, -0.15]:
    pos = dynamic_risk_sizing(dd)
    print(f"当前回撤 {dd:.0%} → 仓位调整为 {pos:.0%}")

六、最大回撤与其他风险指标的关系

6.1 最大回撤 vs 波动率

  • 波动率衡量的是收益率的离散程度,是"日常风险"。
  • 最大回撤衡量的是极端尾部风险,是"灾难性风险"。

两者都很重要,但最大回撤更能反映投资者的实际痛苦感受。一个策略可能波动率不高,但因为持续缓慢下跌而累积了巨大的最大回撤。

6.2 最大回撤与卡尔马比率

**卡尔马比率(Calmar Ratio)**是年化收益率与最大回撤的比值:

卡尔马比率 = 年化收益率 / |最大回撤|

卡尔马比率越高,说明策略在承受每单位最大回撤的同时获得了更高的收益。一般认为卡尔马比率大于 1 的策略是不错的策略,大于 2 则相当优秀。

6.3 最大回撤与斯特林比率

**斯特林比率(Sterling Ratio)**使用平均年化回撤而非最大回撤:

斯特林比率 = 年化收益率 / 平均年化最大回撤

相比卡尔马比率,斯特林比率使用平均值而非极值,因此更稳定、不易被单一极端事件所影响。


七、实战建议

7.1 设定最大回撤红线

在开始运行策略之前,必须设定一条不可逾越的最大回撤红线。一旦触及这条红线,无条件停止策略运行,重新审视和优化。

建议:

  • 保守型投资者:最大回撤红线设在 10%-15%。
  • 平衡型投资者:最大回撤红线设在 15%-25%。
  • 激进型投资者:最大回撤红线设在 25%-35%。
  • 永远不要接受超过 40% 的最大回撤——这意味着你需要涨 67% 才能回本。

7.2 关注回撤的持续时间

最大回撤的幅度和持续时间同样重要。一个回撤 20% 但 3 个月恢复的策略,远好于一个回撤 15% 但 18 个月才恢复的策略。在评估策略时,要同时关注:

  • 最大回撤幅度
  • 最大回撤持续时间
  • 平均回撤恢复时间
  • 当前处于回撤状态的次数和频率

7.3 区分"正常回撤"和"策略失效"

并非所有回撤都意味着策略出了问题。以下是一些判断依据:

  • 正常回撤:回撤幅度在历史回测的范围内,策略逻辑没有失效,市场环境暂时不利。
  • 策略失效信号:回撤超过历史最大回撤的 1.5 倍,策略在多种市场环境下持续亏损,策略逻辑所依赖的市场特征发生了结构性变化。

面对策略失效,最明智的做法是暂停策略,深入分析原因,而不是加大赌注期望"一把翻本"。


八、总结

最大回撤是量化投资中最重要的风险度量之一,它直接反映了策略在最坏情况下的表现。记住以下几点:

  1. 最大回撤衡量的是极端风险,不是日常波动,它告诉你策略最坏能亏多少。
  2. 回撤和恢复是不对称的,跌 50% 需要涨 100% 才能回本,因此控制下行风险比追求上行收益更重要。
  3. 降低回撤的方法包括:仓位管理、止损机制、分散投资、对冲、动态风险控制。
  4. 设定明确的回撤红线,一旦触及就要停下来重新审视,而不是盲目扛单。
  5. 最大回撤不能孤立看待,要结合卡尔马比率、恢复时间等指标综合评估。

投资是一场长跑,活得久比跑得快更重要。控制最大回撤,就是在确保你能在这场长跑中走到终点。

仅供学习交流,不构成任何投资建议