3. 风险归因:分析最大回撤的来源,区分系统性风险与特异性风险

做回测分析,我最怕看到什么?不是收益率低,而是最大回撤深不见底。你想想看,辛辛苦苦跑了一年的策略,突然某个月净值曲线像瀑布一样往下砸,那种感觉,嗯,我经历过不止一次。

今天我们就来聊聊怎么拆解最大回撤。说白了,就是要搞清楚:这波亏损,是市场整体崩了(系统性风险),还是我策略自己作死(特异性风险)?

核心观点: 最大回撤不可怕,可怕的是你不知道它从哪来的。归因分析就是给回撤做“尸检”。

3.1 系统性风险 vs 特异性风险:本质区别

先给个简单定义:

  • 系统性风险:市场整体下跌带来的亏损。比如2020年3月疫情爆发,全球股市一起崩。你买什么都跌,这不是策略的问题。
  • 特异性风险:策略自身的问题。比如你重仓了一只股票,结果它暴雷了。或者你的择时信号连续出错。

我在项目中遇到过一种情况:某学员的策略回撤了15%,他以为是市场不好。结果我一查,同期沪深300只跌了3%。那剩下的12%哪来的?全是策略自己“作”出来的。这就是典型的特异性风险被低估了。

我的习惯: 每次回撤超过5%,我都会先看一眼同期基准指数的表现。如果基准也跌了,那先别慌,继续往下拆。

3.2 归因分析的核心方法:Brinson 分解的变体

传统的Brinson分解是用来分析超额收益的。但我们可以把它反过来,用来分析回撤。核心思路就一句话:

回撤 = 市场拖累 + 行业配置拖累 + 个股选择拖累 + 交互效应

具体怎么算?我直接给代码:

import pandas as pd
import numpy as np

def drawdown_attribution(portfolio_returns, benchmark_returns, 
                         portfolio_weights, benchmark_weights,
                         sector_map):
    """
    回撤归因分析
    portfolio_returns: 组合每日收益率 (DataFrame, 列是股票代码)
    benchmark_returns: 基准每日收益率 (Series)
    portfolio_weights: 组合权重 (Series)
    benchmark_weights: 基准权重 (Series)
    sector_map: 行业映射 (dict, 股票代码 -> 行业)
    """
    # 1. 计算市场拖累
    market_effect = benchmark_returns.mean() * len(benchmark_returns)
    
    # 2. 计算行业配置拖累
    sectors = list(set(sector_map.values()))
    sector_effect = 0
    for sector in sectors:
        # 组合在该行业的权重
        p_weight = sum([portfolio_weights.get(stock, 0) 
                       for stock in sector_map if sector_map[stock] == sector])
        # 基准在该行业的权重
        b_weight = sum([benchmark_weights.get(stock, 0) 
                       for stock in sector_map if sector_map[stock] == sector])
        # 行业超额收益
        sector_return = benchmark_returns.mean()  # 简化处理
        sector_effect += (p_weight - b_weight) * sector_return
    
    # 3. 计算个股选择拖累
    selection_effect = 0
    for stock in portfolio_returns.columns:
        if stock in benchmark_returns.index:
            p_ret = portfolio_returns[stock].mean()
            b_ret = benchmark_returns[stock] if isinstance(benchmark_returns, pd.DataFrame) else 0
            selection_effect += portfolio_weights.get(stock, 0) * (p_ret - b_ret)
    
    # 4. 交互效应
    interaction_effect = 0  # 实际计算较复杂,此处简化
    
    total_drawdown = market_effect + sector_effect + selection_effect + interaction_effect
    
    return {
        'total_drawdown': total_drawdown,
        'market_effect': market_effect,
        'sector_effect': sector_effect,
        'selection_effect': selection_effect,
        'interaction_effect': interaction_effect
    }

注意: 上面的代码是简化版。实际项目中,行业配置和个股选择会有交叉影响。我曾经因为没处理好交互效应,把行业配置的锅甩给了个股选择,结果优化方向全错了。

3.3 实战案例:拆解一次真实回撤

我记得有一次帮朋友分析他的量化策略。2022年4月,他的策略回撤了8.2%。我们来看归因结果:

归因项 贡献值 占比 解读
市场拖累 -3.5% 42.7% 同期大盘跌了3.5%,这部分正常
行业配置拖累 -2.1% 25.6% 超配了新能源,而新能源跌得比大盘多
个股选择拖累 -2.0% 24.4% 选中的几只股票跑输了行业平均
交互效应 -0.6% 7.3% 交叉影响,可忽略
总回撤 -8.2% 100%

看到这个结果,我立刻明白了:市场跌了3.5%,这是系统性风险,没办法。但剩下的4.7%是策略自身的问题。其中行业配置和个股选择各占一半。

关键结论: 这个策略的问题不是择时,而是选股和行业配置。优化方向应该是改进选股因子,而不是调整仓位管理。

3.4 如何区分系统性风险与特异性风险?

这里我分享一个我自己常用的方法——滚动相关性分析

  1. 计算策略收益率与基准收益率的滚动30日相关系数
  2. 如果相关系数突然下降(比如从0.8降到0.3),说明策略开始“特立独行”了
  3. 这时候的回撤,大概率是特异性风险

为什么会这样?因为系统性风险来临时,所有股票一起跌,策略和基准的相关性会很高。但如果策略自己出了问题,相关性就会下降。

def rolling_correlation(portfolio_returns, benchmark_returns, window=30):
    """计算滚动相关性,用于区分风险类型"""
    rolling_corr = portfolio_returns.rolling(window).corr(benchmark_returns)
    
    # 判断标准
    if rolling_corr.iloc[-1] > 0.7:
        risk_type = "系统性风险为主"
    elif rolling_corr.iloc[-1] > 0.4:
        risk_type = "混合风险"
    else:
        risk_type = "特异性风险为主"
    
    return rolling_corr, risk_type

避坑指南: 我曾经犯过一个错误——只看整体相关性。后来发现,策略在牛市和熊市的相关性完全不同。所以我现在会分市场状态来分析。比如上涨段和下跌段分别算相关性。

3.5 风险归因的SVG流程图

下面这张图,是我自己总结的风险归因分析流程。每次做归因,我都会按这个步骤走一遍:

风险归因分析流程图 输入:组合收益 + 基准收益 + 权重 计算总回撤:Max Drawdown Brinson分解:市场 + 行业配置 + 个股选择 + 交互效应 判断:滚动相关性分析 → 系统性风险 vs 特异性风险 输出:归因报告 + 优化建议

3.6 我的实战经验总结

做了这么多年回测分析,我总结了几条铁律:

  • 不要只看最大回撤数值。回撤20%和回撤20%不一样。如果20%里18%是市场跌的,那策略其实没问题。
  • 特异性风险才是优化重点。系统性风险你控制不了,但特异性风险可以通过分散化、改进选股模型来降低。
  • 归因要分时间段。同一个策略,牛市的回撤原因和熊市的回撤原因可能完全不同。我习惯按市场状态分段分析。

一句话总结: 风险归因不是找谁背锅,而是帮我们看清——哪些亏损是“天灾”,哪些是“人祸”。天灾躲不过,人祸必须改。

嗯,今天就聊到这。记住,下次看到回撤别急着改参数,先做归因分析。搞清楚敌人是谁,再决定怎么打。

专注资料整理