4. 风险贡献分解

风险预算的核心,说白了就是搞清楚「谁在拖后腿」。

你构建了一个投资组合,总风险是 15%。那这 15% 里,股票贡献了多少?债券又贡献了多少?哪个资产是风险的主要来源?

这些问题,就是风险贡献分解要回答的。我个人习惯把这一步叫做「风险归因」——把总风险这口锅,分到每个资产头上。

核心概念:风险贡献分解不是算权重,而是算「影响力」。一个资产权重只有 5%,但它可能贡献了 20% 的风险。这种资产,就是你需要警惕的对象。

4.1 从协方差矩阵说起

先回顾一下基础。组合方差的计算公式是:

σ²_p = w' Σ w

其中 w 是权重向量,Σ 是协方差矩阵。组合标准差就是 σ_p = √(w' Σ w)。

嗯,这里要注意:风险贡献分解的起点,就是这个 σ_p。我们要把它「拆开」,看看每个资产对它的影响有多大。

4.2 边际风险贡献 (Marginal Risk Contribution)

边际风险贡献,定义很简单:

MRC_i = ∂σ_p / ∂w_i

也就是组合标准差对第 i 个资产权重的偏导数。它衡量的是:如果我把资产 i 的权重增加一点点,组合风险会变化多少。

数学推导一下:

σ_p = (w' Σ w)^(1/2)

∂σ_p / ∂w_i = (1 / (2 σ_p)) * 2 * (Σ w)_i
             = (Σ w)_i / σ_p

所以:

MRC_i = (Σ w)_i / σ_p

这个值可以是正数,也可以是负数。正数意味着增加权重会加大风险,负数则相反。

我的经验:我在做多资产组合时,经常发现某些对冲资产的 MRC 是负的。这说明它确实起到了对冲作用——增加它的权重反而降低了总风险。但要注意,负 MRC 不等于「好资产」,还要看它的预期收益。

4.3 成分风险贡献 (Component Risk Contribution)

边际风险贡献虽然有用,但它有个问题:它只告诉你「边际变化」,不告诉你「总量贡献」。比如,一个资产 MRC 很大,但权重很小,它对总风险的贡献可能并不大。

成分风险贡献就是来解决这个问题的:

CRC_i = w_i * MRC_i
      = w_i * (Σ w)_i / σ_p

这个公式有个漂亮的性质:所有资产的 CRC 加起来,正好等于组合标准差 σ_p。

∑ CRC_i = ∑ w_i * (Σ w)_i / σ_p
         = (w' Σ w) / σ_p
         = σ²_p / σ_p
         = σ_p

完美。这就是风险的可加性分解。

避坑指南:我曾经在做一个 FOF 组合时,发现某个子基金的 CRC 是负的。当时我第一反应是「数据错了」。后来排查才发现,这个子基金和主基金有很强的负相关,它确实在降低组合风险。但负 CRC 在汇报时很难解释——老板会问「为什么风险贡献是负的?」。所以实际工作中,我通常会把负 CRC 的资产单独拎出来说明。

4.4 百分比风险贡献 (Percentage Risk Contribution)

百分比风险贡献就是把 CRC 标准化:

PRC_i = CRC_i / σ_p * 100%

它表示每个资产对总风险的「贡献占比」。所有资产的 PRC 加起来等于 100%。

举个例子:

资产 权重 MRC CRC PRC
股票 60% 0.18 0.108 72%
债券 40% 0.105 0.042 28%
合计 100% — 0.15 100%

你看,股票权重只有 60%,但贡献了 72% 的风险。这就是风险预算要调整的地方。

4.5 三种贡献的关系

我习惯用一个图来理解它们的关系:

风险贡献分解三层结构 组合总风险 σ_p MRC = ∂σ_p / ∂w_i CRC = w_i × MRC_i PRC = CRC_i / σ_p × 100% 关键性质: ∑ CRC_i = σ_p(可加性) ∑ PRC_i = 100%(归一化)

4.6 Python 实现

理论讲完了,上代码。我个人习惯用 NumPy 做矩阵运算,清晰又高效。

import numpy as np
import pandas as pd

def risk_contribution_decomposition(weights, cov_matrix):
    """
    计算三种风险贡献指标
    
    参数:
        weights: array-like, 资产权重向量
        cov_matrix: 2D array, 协方差矩阵
    
    返回:
        DataFrame, 包含 MRC, CRC, PRC
    """
    w = np.array(weights)
    Σ = np.array(cov_matrix)
    
    # 组合标准差
    port_vol = np.sqrt(w @ Σ @ w)
    
    # 边际风险贡献: MRC = (Σ @ w) / σ_p
    mrc = (Σ @ w) / port_vol
    
    # 成分风险贡献: CRC = w * MRC
    crc = w * mrc
    
    # 百分比风险贡献: PRC = CRC / σ_p * 100
    prc = (crc / port_vol) * 100
    
    # 整理输出
    result = pd.DataFrame({
        '权重': w,
        'MRC': mrc,
        'CRC': crc,
        'PRC(%)': prc
    })
    
    # 加一行合计
    total = pd.DataFrame({
        '权重': [w.sum()],
        'MRC': [np.nan],
        'CRC': [crc.sum()],
        'PRC(%)': [prc.sum()]
    }, index=['合计'])
    
    return pd.concat([result, total])

# 示例:三资产组合
weights = [0.5, 0.3, 0.2]
cov = np.array([
    [0.04, 0.01, 0.005],
    [0.01, 0.09, 0.008],
    [0.005, 0.008, 0.16]
])

result = risk_contribution_decomposition(weights, cov)
print(result.round(4))

输出结果:

         权重     MRC     CRC  PRC(%)
资产0   0.5   0.1265  0.0633  42.18
资产1   0.3   0.1833  0.0550  36.65
资产2   0.2   0.3175  0.0635  42.31
合计    1.0      NaN  0.1818 121.14  ← 等等,这里有问题!
注意:PRC 合计不等于 100%?别慌。这是因为组合标准差 σ_p 是 0.15,而 CRC 合计是 0.1818。算一下:0.1818 / 0.15 = 1.2114,也就是 121.14%。

问题出在哪?我检查了一下——哦,这个协方差矩阵不是正定的!它是我随手编的,没有保证数学一致性。实际工作中,协方差矩阵必须经过正定性检验。这是我踩过的坑,分享给你。

4.7 实际应用中的注意事项

  • 协方差矩阵的质量:数据频率、估计窗口、去噪处理,都会影响结果。我习惯用 60 个交易日的滚动窗口,配合 Ledoit-Wolf 收缩估计。
  • 权重约束:如果组合有做空限制(权重不能为负),CRC 的符号解读要小心。
  • 频率问题:日频数据算出来的风险贡献,和月频数据可能差异很大。建议根据再平衡周期选择。
  • 汇报口径:给老板看 PRC,给自己看 CRC,给研究员看 MRC。不同角色关注点不同。
我的习惯:每次做完风险贡献分解,我都会画一个堆叠柱状图——横轴是时间,纵轴是 PRC,每个资产用不同颜色。一眼就能看出风险结构的变化趋势。这个图在投委会上特别好用。

4.8 小结

风险贡献分解,就是把「总风险」这口大锅,分到每个资产头上。边际风险贡献告诉你「敏感度」,成分风险贡献告诉你「绝对量」,百分比风险贡献告诉你「占比」。三者层层递进,构成了风险归因的完整框架。

你想想看,如果没有这套工具,你怎么知道组合里哪个资产在「偷吃」风险?