Skip to content

16.1 风险分解与归因 ​

名词解释:风险归因(Risk Attribution)

风险归因是将投资组合的总体风险分解为不同来源(因子风险、特异性风险、行业风险、国家风险等)的过程。与收益归因关注"赚了多少"不同,风险归因回答"风险从哪里来"和"哪些头寸贡献了最大风险"的问题。

一、风险归因:因子风险 + 特异风险 ​

1.1 因子模型下的风险分解 ​

在多因子模型中,组合的总风险可以精确分解:

σP2=wTBΣFBTw⏟因子风险+wTDw⏟特异风险

其中 w 是组合权重向量,B 是因子暴露矩阵,ΣF 是因子协方差矩阵,D 是对角特异风险矩阵。

1.2 边际风险贡献(MRC) ​

每个资产对组合风险的边际贡献为:

MRCi=∂σP∂wi=(Σw)iσP

风险贡献(RC)为权重乘以边际贡献:

RCi=wi×MRCi=wi×(Σw)iσP

所有资产的风险贡献之和等于组合总风险(欧拉定理保证)。

PYTHON97 行 · 3.2 KB
📄此处有展示代码97 行 · 3.2 KB展开 ▼
python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

def factor_risk_decomposition(weights: np.ndarray,
                                factor_exposures: np.ndarray,
                                factor_cov: np.ndarray,
                                specific_var: np.ndarray) -> dict:
    """
    将组合风险分解为因子风险和特异风险。

    参数:
        weights: (N,) 资产权重
        factor_exposures: (N, K) 因子暴露矩阵
        factor_cov: (K, K) 因子协方差矩阵
        specific_var: (N,) 各资产的特异方差
    返回:
        风险分解结果的字典
    """
    # 因子风险
    portfolio_factor_var = weights @ factor_exposures @ factor_cov @ \
                            factor_exposures.T @ weights

    # 特异风险
    D = np.diag(specific_var)
    portfolio_specific_var = weights @ D @ weights

    # 总风险
    total_var = portfolio_factor_var + portfolio_specific_var
    total_vol = np.sqrt(total_var)

    # 风险贡献分解
    cov_matrix = factor_exposures @ factor_cov @ factor_exposures.T + D
    portfolio_cov = cov_matrix @ weights

    # 边际风险贡献
    mrc = portfolio_cov / total_vol

    # 风险贡献
    rc = weights * mrc

    # 百分比风险贡献
    rc_pct = rc / total_vol * 100

    return {
        'total_volatility': total_vol,
        'factor_var': portfolio_factor_var,
        'specific_var': portfolio_specific_var,
        'factor_var_pct': portfolio_factor_var / total_var * 100,
        'specific_var_pct': portfolio_specific_var / total_var * 100,
        'marginal_risk_contribution': mrc,
        'risk_contribution': rc,
        'risk_contribution_pct': rc_pct
    }


def factor_level_risk_decomp(weights: np.ndarray,
                               factor_exposures: np.ndarray,
                               factor_cov: np.ndarray) -> pd.DataFrame:
    """
    将因子风险进一步分解到每个因子的贡献。

    参数:
        weights: (N,) 资产权重
        factor_exposures: (N, K) 因子暴露
        factor_cov: (K, K) 因子协方差
    返回:
        各因子的风险贡献DataFrame
    """
    portfolio_exposure = weights @ factor_exposures  # (K,) 组合因子暴露

    # 每个因子的方差贡献和协方差贡献
    factor_rc = np.zeros(len(portfolio_exposure))
    for k in range(len(portfolio_exposure)):
        factor_rc[k] = portfolio_exposure[k] * \
                       (factor_cov @ portfolio_exposure)[k]

    factor_rc_pct = factor_rc / np.sum(factor_rc) * 100

    return pd.DataFrame({
        'Factor': [f'F{i+1}' for i in range(len(factor_rc))],
        'Portfolio_Exposure': portfolio_exposure,
        'Risk_Contribution': factor_rc,
        'Risk_Contribution_Pct': factor_rc_pct
    }).sort_values('Risk_Contribution_Pct', ascending=False)

# ===== 合成数据最小示例(数据为模拟,机制真实)=====
np.random.seed(161)
_w = np.array([0.30, 0.25, 0.20, 0.15, 0.10])
_exp = np.random.uniform(-0.5, 1.5, size=(5, 3))
_A = np.random.randn(3, 200)
_fcov = np.cov(_A)
_svar = np.full(5, 0.02)
_fr = factor_risk_decomposition(_w, _exp, _fcov, _svar)
for _k, _v in _fr.items():
    print(f'{_k}: {_v if np.isscalar(_v) else np.round(_v, 4)}')
▶ 点击展开可浏览运行结果
total_volatility: 0.980232194045783
factor_var: 0.9563551542438097
specific_var: 0.0045
factor_var_pct: 99.53166718416145
specific_var_pct: 0.4683328158385628
marginal_risk_contribution: [1.2309 0.1398 2.0642 0.1482 1.4096]
risk_contribution: [0.3693 0.035  0.4128 0.0222 0.141 ]
risk_contribution_pct: [37.6705  3.5661 42.116   2.2672 14.3802]

二、Brinson 归因模型 ​

2.1 Brinson 模型的框架 ​

Brinson 归因将组合的超额收益分解为三个来源:

  • 配置效应(Allocation Effect):超配/低配某类资产带来的超额收益
  • 选择效应(Selection Effect):在某类资产内选股带来的超额收益
  • 交互效应(Interaction Effect):配置和选择的交叉影响
RP−RB=∑i(wiP−wiB)(RiB−RB)+∑iwiB(RiP−RiB)+∑i(wiP−wiB)(RiP−RiB)

2.2 Python 实现 ​

PYTHON81 行 · 3.0 KB
📄此处有展示代码81 行 · 3.0 KB展开 ▼
python
import numpy as np
import pandas as pd

def brinson_attribution(portfolio_weights: np.ndarray,
                         benchmark_weights: np.ndarray,
                         portfolio_sector_returns: np.ndarray,
                         benchmark_sector_returns: np.ndarray,
                         sector_names: list = None) -> pd.DataFrame:
    """
    Brinson 绩效归因模型。

    参数:
        portfolio_weights: (S,) 组合在各板块的权重
        benchmark_weights: (S,) 基准在各板块的权重
        portfolio_sector_returns: (S,) 组合在各板块的收益
        benchmark_sector_returns: (S,) 基准在各板块的收益
        sector_names: 板块名称列表
    返回:
        归因结果DataFrame
    """
    S = len(portfolio_weights)
    if sector_names is None:
        sector_names = [f'Sector {i+1}' for i in range(S)]

    # 基准总收益
    R_B = np.sum(benchmark_weights * benchmark_sector_returns)
    R_P = np.sum(portfolio_weights * portfolio_sector_returns)

    # 各效应
    allocation_effect = (portfolio_weights - benchmark_weights) * \
                        (benchmark_sector_returns - R_B)

    selection_effect = benchmark_weights * \
                       (portfolio_sector_returns - benchmark_sector_returns)

    interaction_effect = (portfolio_weights - benchmark_weights) * \
                         (portfolio_sector_returns - benchmark_sector_returns)

    results = pd.DataFrame({
        'Sector': sector_names,
        'Portfolio_Weight': portfolio_weights,
        'Benchmark_Weight': benchmark_weights,
        'Active_Weight': portfolio_weights - benchmark_weights,
        'Portfolio_Return': portfolio_sector_returns,
        'Benchmark_Return': benchmark_sector_returns,
        'Allocation_Effect': allocation_effect,
        'Selection_Effect': selection_effect,
        'Interaction_Effect': interaction_effect,
        'Total_Effect': allocation_effect + selection_effect + interaction_effect
    })

    # 加总行
    total_row = pd.DataFrame({
        'Sector': ['TOTAL'],
        'Portfolio_Weight': [np.sum(portfolio_weights)],
        'Benchmark_Weight': [np.sum(benchmark_weights)],
        'Active_Weight': [0],
        'Portfolio_Return': [R_P],
        'Benchmark_Return': [R_B],
        'Allocation_Effect': [np.sum(allocation_effect)],
        'Selection_Effect': [np.sum(selection_effect)],
        'Interaction_Effect': [np.sum(interaction_effect)],
        'Total_Effect': [R_P - R_B]
    })

    results = pd.concat([results, total_row], ignore_index=True)

    # 验证
    assert abs(results.iloc[-1]['Total_Effect'] - (R_P - R_B)) < 1e-10, \
        "归因加总与超额收益不一致"

    return results

# ===== 合成数据最小示例(数据为模拟,机制真实)=====
_pw = np.array([0.3, 0.3, 0.2, 0.2])
_bw = np.array([0.25, 0.35, 0.25, 0.15])
_psr = np.array([0.05, 0.02, -0.01, 0.03])
_bsr = np.array([0.03, 0.025, -0.005, 0.02])
_ba = brinson_attribution(_pw, _bw, _psr, _bsr, sector_names=['科技', '消费', '金融', '能源'])
print(_ba.round(4).to_string())
▶ 点击展开可浏览运行结果
Sector  Portfolio_Weight  Benchmark_Weight  Active_Weight  Portfolio_Return  Benchmark_Return  Allocation_Effect  Selection_Effect  Interaction_Effect  Total_Effect
0     科技               0.3              0.25           0.05             0.050             0.030             0.0006            0.0050              0.0010        0.0066
1     消费               0.3              0.35          -0.05             0.020             0.025            -0.0003           -0.0018              0.0002       -0.0019
2     金融               0.2              0.25          -0.05            -0.010            -0.005             0.0012           -0.0012              0.0002        0.0001
3     能源               0.2              0.15           0.05             0.030             0.020             0.0001            0.0015              0.0005        0.0021
4  TOTAL               1.0              1.00           0.00             0.025             0.018             0.0015            0.0035              0.0020        0.0070

三、Barra 风险模型因子结构 ​

3.1 Barra 模型的因子体系 ​

Barra 是业界最广泛使用的多因子风险模型,其因子体系包括:

因子类别因子示例说明
风格因子(Style Factors)价值、动量、规模、波动率、质量、成长、杠杆、流动性、分红横截面标准化
行业因子(Industry Factors)GICS 行业分类(68个行业)虚拟变量
国家因子(Country Factors)各国家/地区全球模型中使用

3.2 Barra 风格因子的构建 ​

PYTHON48 行 · 1.7 KB
📄此处有展示代码48 行 · 1.7 KB展开 ▼
python
def barra_style_factors(fundamentals: pd.DataFrame) -> pd.DataFrame:
    """
    简化版 Barra 风格因子的构建。

    参数:
        fundamentals: 包含公司基本面数据的DataFrame
            列包括: size_log, book_to_price, momentum_12m1m,
                   beta, volatility, roe, eps_growth, leverage, dividend_yield
    返回:
        标准化后的风格因子暴露矩阵
    """
    factors = pd.DataFrame(index=fundamentals.index)

    # 1. 规模因子(Size):市值的自然对数
    factors['Size'] = fundamentals['size_log']

    # 2. 价值因子(Value):账面市值比
    factors['Value'] = fundamentals['book_to_price']

    # 3. 动量因子(Momentum):12月-1月动量
    factors['Momentum'] = fundamentals['momentum_12m1m']

    # 4. 波动率因子(Volatility):历史贝塔 + 残差波动率
    factors['Volatility'] = 0.6 * fundamentals['beta'] + \
                             0.4 * fundamentals['residual_volatility']

    # 5. 质量因子(Quality):ROE + 盈利增长
    factors['Quality'] = 0.5 * fundamentals['roe'] + \
                          0.5 * fundamentals['eps_growth']

    # 6. 杠杆因子(Leverage)
    factors['Leverage'] = fundamentals['leverage']

    # 7. 成长因子(Growth)
    factors['Growth'] = fundamentals['eps_growth']

    # 8. 流动性因子(Liquidity)
    factors['Liquidity'] = fundamentals['turnover']

    # 9. 分红因子(Dividend Yield)
    factors['DividendYield'] = fundamentals['dividend_yield']

    # 标准化:使每个因子横截面均值为0,标准差为1
    for col in factors.columns:
        factors[col] = (factors[col] - factors[col].mean()) / \
                        factors[col].std()

    return factors
▶ 点击展开可浏览运行结果
--- 结果 ---
========================================================
自动调用 barra_style_factors(...) — 运行结果:
========================================================
DataFrame shape=(100, 9)
                Size     Value  Momentum  Volatility   Quality  Leverage    Growth  Liquidity  DividendYield
2024-01-01  0.681801  0.344843  1.474192   -0.991988 -0.279508 -0.470123 -0.355527  -0.976113       1.002648
2024-01-02 -0.593584  1.210055 -0.751998   -0.508654 -1.297586  0.347203 -1.322506   1.204687      -1.724489
2024-01-03 -0.944073  0.278066 -0.601524   -0.736505  0.408572 -1.057180  0.373794   1.001753      -0.311021
2024-01-04  1.172688 -1.460407 -0.263181   -0.519702 -0.175084 -0.019483  0.590584   0.432852      -0.553642
2024-01-05  1.041449  0.728082  1.310956   -0.110096  1.478517 -0.520446  0.990010  -0.553244       1.410495
2024-01-06 -1.795537  0.488874  0.279299    0.025226  0.306760 -0.013797  0.767423   0.210315      -0.845864
...(共 100 行,仅显示前 6 行)

四、风险预算分配 ​

4.1 风险预算的概念 ​

风险预算(Risk Budgeting)是将组合的风险按比例分配给各个子组合或策略。与传统的资本权重分配不同,风险预算关注每个组成部分的风险贡献比例。

PYTHON51 行 · 1.6 KB
📄此处有展示代码51 行 · 1.6 KB展开 ▼
python
import numpy as np

def risk_budget_optimization(cov_matrix: np.ndarray,
                              risk_budgets: np.ndarray,
                              max_iter: int = 1000,
                              tol: float = 1e-8) -> np.ndarray:
    """
    风险预算优化:找到使风险贡献等于指定预算的权重。

    风险贡献 RC_i = w_i * (Σw)_i / σ_P

    参数:
        cov_matrix: (N, N) 协方差矩阵
        risk_budgets: (N,) 目标风险预算比例(总和为1)
        max_iter: 最大迭代次数
        tol: 收敛容忍度
    返回:
        最优权重向量
    """
    N = len(risk_budgets)
    weights = np.ones(N) / N  # 初始等权

    for iteration in range(max_iter):
        # 当前组合
        portfolio_std = np.sqrt(weights @ cov_matrix @ weights)
        marginal_risk = cov_matrix @ weights / portfolio_std
        risk_contribution = weights * marginal_risk
        current_budgets = risk_contribution / np.sum(risk_contribution)

        # 检查收敛
        max_deviation = np.max(np.abs(current_budgets - risk_budgets))
        if max_deviation < tol:
            break

        # 调整权重:风险预算不足的资产增加权重
        adjustment = risk_budgets / (current_budgets + 1e-10)
        weights = weights * adjustment

        # 归一化
        weights = weights / np.sum(weights)

    return weights

# ===== 合成数据最小示例(数据为模拟,机制真实)=====
np.random.seed(163)
_A4 = np.random.randn(4, 200)
_cov = np.cov(_A4)
_budgets = np.array([0.4, 0.3, 0.2, 0.1])
_w = risk_budget_optimization(_cov, _budgets)
print('最优权重:', np.round(_w, 4))
▶ 点击展开可浏览运行结果
最优权重: [ 0.3116 -0.0905  0.6721  0.1067]

4.2 风险预算的可视化 ​

有效的风险预算管理需要持续监控实际风险贡献与目标预算的偏差:

PYTHON39 行 · 1.3 KB
📄此处有展示代码39 行 · 1.3 KB展开 ▼
python
import numpy as np

def risk_budget_dashboard(current_rc_pct: np.ndarray,
                           target_budgets: np.ndarray,
                           labels: list) -> dict:
    """
    风险预算监控仪表板:比较实际 vs 目标风险贡献。

    返回:
        包含偏差分析和预警信号的字典
    """
    deviations = current_rc_pct - target_budgets * 100
    abs_deviations = np.abs(deviations)

    # 预警:偏差超过阈值
    threshold = 5  # 5个百分点
    alerts = []
    for i, dev in enumerate(deviations):
        if abs(dev) > threshold:
            direction = 'over' if dev > 0 else 'under'
            alerts.append(f"{labels[i]}: {direction}-allocated "
                          f"({dev:+.1f}pp vs budget)")

    return {
        'current_contribution': dict(zip(labels, current_rc_pct)),
        'target_budget': dict(zip(labels, target_budgets * 100)),
        'deviations': dict(zip(labels, deviations)),
        'max_deviation': np.max(abs_deviations),
        'alerts': alerts,
        'rebalance_needed': len(alerts) > 0
    }

# ===== 合成数据最小示例(数据为模拟,机制真实)=====
_rc = np.array([38, 22, 27, 13])
_tb = np.array([0.4, 0.3, 0.2, 0.1])
_db = risk_budget_dashboard(_rc, _tb, labels=['股票', '债券', '商品', '现金'])
for _k, _v in _db.items():
    print(f'{_k}: {_v}')
▶ 点击展开可浏览运行结果
current_contribution: {'股票': np.int64(38), '债券': np.int64(22), '商品': np.int64(27), '现金': np.int64(13)}
target_budget: {'股票': np.float64(40.0), '债券': np.float64(30.0), '商品': np.float64(20.0), '现金': np.float64(10.0)}
deviations: {'股票': np.float64(-2.0), '债券': np.float64(-8.0), '商品': np.float64(7.0), '现金': np.float64(3.0)}
max_deviation: 8.0
alerts: ['债券: under-allocated (-8.0pp vs budget)', '商品: over-allocated (+7.0pp vs budget)']
rebalance_needed: True

风险分解与归因为量化投资者提供了理解组合风险结构的透镜。接下来我们将讨论一个特别关键的专题:如何量化和管理极端事件带来的尾部风险——这正是极值理论(EVT)所解决的问题。

常见误区 ​

误区1:把 Brinson 收益归因当风险归因。 收益归因回答"赚在哪",风险归因回答"亏的风险来自哪",二者框架不同不可混用。

误区2:因子暴露线性外推。 风险模型对极端行情、风格急转的预测力显著下降,暴露度需要做非线性压力放大。

误区3:假设特异风险相互独立。 同行业小市值股票的特异风险高度相关,独立性假设会系统性低估组合风险。

误区4:风险预算按市值比例分配。 市值等权不等于风险等权,波动率和相关性会让真实风险贡献严重失衡。

本章要点 ​

  • 组合风险 = 因子风险 + 特异风险两层结构,Barra 类模型刻画第一层
  • 风险贡献 = 权重 × 边际风险,等风险贡献是风险预算的起点
  • Brinson 归因用于收益分解,与风险模型互补而非替代
  • 风险预算应定期回检,偏离超阈值需要再平衡

跨模块延伸:组合优化、归因分析