01-市场风险度量

预计学习时间:2.5 小时

难度:⭐⭐⭐

核心问题:如何量化投资组合面临的市场风险?不同度量方法各有什么优劣?


风险度量全景

┌────────────────────────────────────────────────────────────────┐
│                     市场风险度量体系                             │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│                      市场风险度量                               │
│                          │                                     │
│       ┌──────────────────┼──────────────────┐                 │
│       │                  │                  │                  │
│   波动率度量         损失分布度量        协方差结构              │
│       │                  │                  │                  │
│  · 历史波动率       · VaR              · Ledoit-Wolf          │
│  · EWMA            · CVaR             · 因子协方差            │
│  · Parkinson       · 蒙特卡洛          · Copula               │
│  · Garman-Klass    · EVT/GPD          · 因子暴露              │
│                                                                │
│                    ┌───┴───┐                                  │
│                    │压力测试│                                  │
│                    └───────┘                                  │
└────────────────────────────────────────────────────────────────┘

一、波动率度量

波动率是风险度量的基石。不同估计方法利用不同的信息集合,各有适用场景。

1.1 历史波动率(Close-to-Close)

最简单的波动率估计,仅使用收盘价。

年化处理:

import numpy as np
import pandas as pd
from scipy import stats
from scipy.optimize import minimize
 
def historical_volatility(returns, annualize=True, trading_days=252):
    """
    计算历史波动率(收盘价对数收益率标准差)
 
    参数
    ----
    returns : Series
      日收益率序列
    annualize : bool
      是否年化
    trading_days : int
      年交易日数
 
    返回
    ----
    vol : float
      波动率
    """
    daily_vol = returns.std(ddof=1)
    if annualize:
        return daily_vol * np.sqrt(trading_days)
    return daily_vol
 
# 示例:生成模拟收益并计算历史波动率
np.random.seed(42)
returns = pd.Series(np.random.normal(0.0005, 0.02, 252))
 
print(f"日波动率: {historical_volatility(returns, annualize=False):.4f}")
print(f"年化波动率: {historical_volatility(returns):.2%}")

1.2 指数加权移动平均(EWMA)

历史波动率对每期数据赋予相同权重,EWMA 则对近期数据赋予更高权重,更贴近真实市场的波动聚集特征。

其中 为衰减因子,RiskMetrics 推荐使用

def ewma_volatility(returns, lam=0.94, annualize=True, trading_days=252):
    """
    计算 EWMA 波动率(RiskMetrics 方法)
 
    参数
    ----
    returns : Series
      日收益率序列
    lam : float
      衰减因子,RiskMetrics 推荐 0.94
    annualize : bool
      是否年化
    trading_days : int
      年交易日数
 
    返回
    ----
    ewma_var : Series
      EWMA 方差序列
    ewma_vol : Series
      EWMA 波动率序列
    """
    # 初始化为收益率平方的方差
    var_t = returns.var()
    ewma_var = [var_t]  # 存储方差序列
 
    for r in returns.values:
        var_t = lam * var_t + (1 - lam) * r ** 2
        ewma_var.append(var_t)
 
    ewma_var = pd.Series(ewma_var[1:], index=returns.index)
    ewma_vol = np.sqrt(ewma_var)
 
    if annualize:
        ewma_vol = ewma_vol * np.sqrt(trading_days)
 
    return ewma_var, ewma_vol
 
# 示例
_, ewma_vol = ewma_volatility(returns)
print(f"最新 EWMA 年化波动率: {ewma_vol.iloc[-1]:.2%}")
print(f"历史波动率: {historical_volatility(returns):.2%}")

1.3 Parkinson 波动率

利用日内最高价和最低价估计波动率,信息量更大。假设价格服从几何布朗运动。

def parkinson_volatility(high, low, annualize=True, trading_days=252):
    """
    计算 Parkinson 波动率(基于日内高低价)
 
    参数
    ----
    high : Series
      日最高价序列
    low : Series
      日最低价序列
    annualize : bool
      是否年化
 
    返回
    ----
    vol : float
      Parkinson 波动率
    """
    factor = 1.0 / (4 * len(high) * np.log(2))
    log_hl = np.log(high / low)
    daily_var = factor * (log_hl ** 2).sum()
 
    vol = np.sqrt(daily_var)
    if annualize:
        vol *= np.sqrt(trading_days)
    return vol
 
# 示例:模拟 OHLC 数据
np.random.seed(42)
close = 100 * np.cumprod(1 + np.random.normal(0.0005, 0.02, 252))
high = close * (1 + np.abs(np.random.normal(0.01, 0.005, 252)))
low = close * (1 - np.abs(np.random.normal(0.01, 0.005, 252)))
high = pd.Series(np.maximum(high, close))
low = pd.Series(np.minimum(low, close))
 
print(f"Parkinson 年化波动率: {parkinson_volatility(high, low):.2%}")

1.4 Garman-Klass 波动率

综合利用 OHLC 四价信息,估计效率高于 Parkinson。

def garman_klass_volatility(open_, high, low, close,
                             annualize=True, trading_days=252):
    """
    计算 Garman-Klass 波动率(基于 OHLC 四价)
 
    参数
    ----
    open_ : Series
      开盘价
    high : Series
      最高价
    low : Series
      最低价
    close : Series
      收盘价
    annualize : bool
      是否年化
 
    返回
    ----
    vol : float
      Garman-Klass 波动率
    """
    log_hl = np.log(high / low)
    log_co = np.log(close / open_)
 
    daily_var = (0.5 * log_hl ** 2 - (2 * np.log(2) - 1) * log_co ** 2).mean()
    vol = np.sqrt(daily_var)
 
    if annualize:
        vol *= np.sqrt(trading_days)
    return vol
 
# 示例
open_ = pd.Series(close * (1 + np.random.normal(0, 0.005, 252)))
print(f"Garman-Klass 年化波动率: "
      f"{garman_klass_volatility(open_, high, low, pd.Series(close)):.2%}")

1.5 波动率方法对比

方法使用数据优势劣势
Close-to-Close收盘价简单直观仅用收盘信息
EWMA收盘价捕捉波动聚集需要选择
Parkinson高低价利用日内极值忽略跳空
Garman-KlassOHLC信息量最大假设连续交易

二、VaR(在险价值)

2.1 定义

VaR(Value at Risk):在给定置信水平和持有期下,投资组合可能遭受的最大损失。

例如,99% 置信水平的日 VaR 为 100 万,意味着”有 99% 的把握,日损失不超过 100 万”。

损失分布

    概率密度
      │
      │       ╱╲
      │      ╱  ╲
      │     ╱    ╲
      │    ╱      ╲
      │   ╱        ╲
      │  ╱          ╲___
      │ ╱               ╲___
      │╱                    ╲____
      └─────────────────────────── 损益
          ↑
        VaR_α    ← α 概率在此右侧

2.2 历史模拟法

直接使用历史收益率的分位数,无需分布假设。

def var_historical(returns, confidence=0.99, portfolio_value=1e6):
    """
    历史模拟法计算 VaR
 
    参数
    ----
    returns : Series
      日收益率序列
    confidence : float
      置信水平(如 0.99)
    portfolio_value : float
      投资组合市值
 
    返回
    ----
    var_value : float
      VaR 绝对金额
    var_pct : float
      VaR 百分比
    """
    var_pct = np.percentile(returns, (1 - confidence) * 100)
    var_value = portfolio_value * abs(var_pct)
    return var_value, var_pct
 
# 示例
var_val, var_pct = var_historical(returns)
print(f"99% 历史模拟 VaR: {var_val:,.0f} 元 (占比 {var_pct:.2%})")

2.3 参数法(方差-协方差法)

假设收益率服从正态分布,用均值和标准差计算 VaR。

其中 是标准正态分布的 分位数。

def var_parametric(returns, confidence=0.99, portfolio_value=1e6):
    """
    参数法计算 VaR(假设正态分布)
 
    参数
    ----
    returns : Series
      日收益率序列
    confidence : float
      置信水平
    portfolio_value : float
      投资组合市值
 
    返回
    ----
    var_value : float
      VaR 绝对金额
    var_pct : float
      VaR 百分比
    """
    mu = returns.mean()
    sigma = returns.std(ddof=1)
    z = stats.norm.ppf(1 - confidence)
 
    var_pct = abs(mu + z * sigma)
    var_value = portfolio_value * var_pct
    return var_value, var_pct
 
# 示例
var_val, var_pct = var_parametric(returns)
print(f"99% 参数法 VaR: {var_val:,.0f} 元 (占比 {var_pct:.2%})")

2.4 蒙特卡洛模拟法

通过随机模拟大量场景,构建收益分布后取分位数。

def var_monte_carlo(returns, n_simulations=10000, confidence=0.99,
                    portfolio_value=1e6):
    """
    蒙特卡洛模拟法计算 VaR
 
    参数
    ----
    returns : Series
      历史日收益率
    n_simulations : int
      模拟次数
    confidence : float
      置信水平
    portfolio_value : float
      投资组合市值
 
    返回
    ----
    var_value : float
      VaR 绝对金额
    var_pct : float
      VaR 百分比
    simulated_returns : ndarray
      模拟的收益率
    """
    mu = returns.mean()
    sigma = returns.std(ddof=1)
 
    # 生成模拟收益率
    simulated_returns = np.random.normal(mu, sigma, n_simulations)
 
    # 取分位数
    var_pct = abs(np.percentile(simulated_returns, (1 - confidence) * 100))
    var_value = portfolio_value * var_pct
    return var_value, var_pct, simulated_returns
 
# 示例
var_val, var_pct, _ = var_monte_carlo(returns)
print(f"99% 蒙特卡洛 VaR: {var_val:,.0f} 元 (占比 {var_pct:.2%})")

2.5 三种 VaR 方法对比

方法优点缺点适用场景
历史模拟无分布假设,简单依赖历史数据代表性中低频策略
参数法计算快,易于理解假设正态,低估尾部仓位快速估算
蒙特卡洛灵活,可模拟复杂场景计算量大,模型依赖复杂衍生品组合

三、CVaR(条件在险价值)

CVaR 也称 Expected Shortfall(ES),度量超过 VaR 阈值后的平均损失,解决了 VaR 不满足次可加性的问题。

def cvar(returns, confidence=0.99, portfolio_value=1e6):
    """
    计算 CVaR / Expected Shortfall
 
    参数
    ----
    returns : Series
      日收益率序列
    confidence : float
      置信水平
    portfolio_value : float
      投资组合市值
 
    返回
    ----
    cvar_value : float
      CVaR 绝对金额
    cvar_pct : float
      CVaR 百分比
    var_pct : float
      对应的 VaR 百分比
    """
    threshold = np.percentile(returns, (1 - confidence) * 100)
    # 取尾部收益率的均值
    tail_returns = returns[returns <= threshold]
    cvar_pct = abs(tail_returns.mean())
    cvar_value = portfolio_value * cvar_pct
    return cvar_value, cvar_pct, abs(threshold)
 
# 示例
cvar_val, cvar_pct, var_pct = cvar(returns)
print(f"99% CVaR: {cvar_val:,.0f} 元 (占比 {cvar_pct:.2%})")
print(f"99% VaR: 对应 {var_pct:.2%}")
print(f"CVaR / VaR 比值: {cvar_val / (var_pct * 1e6):.2f}")

四、极值理论(EVT)

4.1 峰度阈值法(POT)

EVT 不假设整体分布形状,只对尾部(极端损失)建模。POT(Peaks Over Threshold)方法对超过阈值 的损失进行建模。

其中 是形状参数(尾指数), 是尺度参数。

def fit_gpd(losses, threshold, min_excess=30):
    """
    对超过阈值的损失拟合广义帕累托分布(GPD)
 
    参数
    ----
    losses : Series
      损失序列(正值表示亏损)
    threshold : float
      阈值
    min_excess : int
      超阈值最少样本数
 
    返回
    ----
    xi : float
      形状参数(尾指数)
    beta : float
      尺度参数
    excesses : Series
      超阈值损失
    """
    excesses = losses[losses > threshold] - threshold
 
    if len(excesses) < min_excess:
        raise ValueError(f"超阈值样本不足: {len(excesses)} < {min_excess}")
 
    # 使用 scipy 拟合 GPD
    # GPD 的 cdf: F(x) = 1 - (1 + xi * x / beta)^(-1/xi)
    shape, loc, scale = stats.genpareto.fit(excesses.values, floc=0)
 
    return shape, scale, excesses
 
def evt_var_cvar(losses, confidence=0.99, threshold_pct=0.9):
    """
    使用 EVT/POT 方法计算 VaR 和 CVaR
 
    参数
    ----
    losses : Series
      损失序列
    confidence : float
      置信水平
    threshold_pct : float
      阈值分位数
 
    返回
    ----
    evt_var : float
      EVT VaR
    evt_cvar : float
      EVT CVaR
    """
    threshold = np.quantile(losses, threshold_pct)
    xi, beta, excesses = fit_gpd(losses, threshold)
 
    # 超过阈值的比例
    n_exceed = len(excesses)
    n_total = len(losses)
    exceedance_prob = n_exceed / n_total
 
    # EVT VaR
    z_alpha = (1 - confidence) / exceedance_prob
    evt_var = threshold + (beta / xi) * (z_alpha ** (-xi) - 1)
 
    # EVT CVaR
    evt_cvar = (evt_var + beta - xi * threshold) / (1 - xi)
 
    return evt_var, evt_cvar, xi, beta
 
# 示例:使用正态分布模拟损失
np.random.seed(42)
losses = pd.Series(np.abs(np.random.normal(0.01, 0.02, 1000)))
 
evt_var, evt_cvar, xi, beta = evt_var_cvar(losses)
print(f"阈值: {np.quantile(losses, 0.9):.4f}")
print(f"GPD 形状参数 xi: {xi:.4f}, 尺度参数 beta: {beta:.4f}")
print(f"EVT 99% VaR: {evt_var:.4f}")
print(f"EVT 99% CVaR: {evt_cvar:.4f}")

4.2 形状参数解读

尾部类型含义
厚尾(Fréchet)极端损失概率高于正态,如金融市场
薄尾(Gumbel)类似指数分布
短尾(Weibull)有上界,如均匀分布

五、压力测试

VaR 和 CVaR 度量的是”常态”下的风险。压力测试则考察极端但合理的情景下组合的损失。

5.1 历史情景法

选取历史上的极端事件,将当时的收益率变化应用到当前组合。

def stress_test_historical(portfolio_weights, historical_scenarios,
                           scenario_names=None):
    """
    历史情景压力测试
 
    参数
    ----
    portfolio_weights : dict
      资产 -> 权重,如 {'stock': 0.6, 'bond': 0.4}
    historical_scenarios : dict
      情景名 -> 资产日收益率 dict
    scenario_names : list
      要测试的情景列表
 
    返回
    ----
    results : DataFrame
      各情景下的组合损失
    """
    assets = list(portfolio_weights.keys())
    results = []
 
    for name, scenario in historical_scenarios.items():
        # 计算组合收益率
        port_return = sum(
            portfolio_weights[a] * scenario.get(a, 0)
            for a in assets
        )
        results.append({
            '情景': name,
            '组合收益率': port_return,
            '损失金额(百万)': port_return * 100  # 假设 1 亿组合
        })
 
    return pd.DataFrame(results).set_index('情景')
 
# 示例:历史极端事件
scenarios = {
    '2008 雷曼倒闭': {'stock': -0.089, 'bond': 0.005, 'commodity': -0.06},
    '2015 股灾':    {'stock': -0.085, 'bond': 0.003, 'commodity': -0.04},
    '2020 疫情闪崩': {'stock': -0.104, 'bond': 0.010, 'commodity': -0.08},
    '2022 加息冲击': {'stock': -0.035, 'bond': -0.020, 'commodity': 0.02},
}
 
weights = {'stock': 0.6, 'bond': 0.3, 'commodity': 0.1}
results = stress_test_historical(weights, scenarios)
print(results)

5.2 因子冲击法

对风险因子施加特定冲击,观察组合响应。

def stress_test_factor(portfolio, factor_shocks):
    """
    因子冲击压力测试
 
    参数
    ----
    portfolio : DataFrame
      资产 -> 因子暴露矩阵(每个资产对各因子的 beta)
    factor_shocks : dict
      因子 -> 冲击幅度
 
    返回
    ----
    asset_impacts : Series
      各资产预期冲击收益率
    """
    # 将冲击转为 Series
    shocks = pd.Series(factor_shocks)
 
    # 逐资产计算冲击影响
    impacts = portfolio.dot(shocks)
    return impacts
 
# 示例
assets = ['AAPL', 'MSFT', 'JPM', 'GS']
factors = ['market', 'size', 'value', 'momentum']
 
# 资产因子暴露
portfolio = pd.DataFrame(
    np.random.randn(4, 4) * 0.5,
    index=assets, columns=factors
)
 
# 因子冲击(标准差倍数)
shocks = {
    'market': -3.0,     # 市场下跌 3 个标准差
    'size': 0.0,
    'value': -2.0,      # 价值因子下跌 2 个标准差
    'momentum': -1.5
}
 
impacts = stress_test_factor(portfolio, shocks)
print("各资产预期冲击收益率:")
print(impacts.round(4))

六、因子风险暴露

量化组合的风险不仅来自市场整体,还来自各种风格因子。

def factor_exposure_analysis(returns, factor_returns, assets=None):
    """
    计算组合的因子风险暴露
 
    参数
    ----
    returns : DataFrame
      资产收益率矩阵 (T x N)
    factor_returns : DataFrame
      因子收益率矩阵 (T x K)
    assets : list
      要分析的资产列表
 
    返回
    ----
    exposures : DataFrame
      各资产对各因子的 beta 暴露
    r_squared : Series
      各资产的 R²(因子解释力)
    """
    if assets is None:
        assets = returns.columns
 
    exposures = {}
    r_squared = {}
 
    for asset in assets:
        y = returns[asset]
        X = factor_returns
 
        # 添加截距项
        X_aug = np.column_stack([np.ones(len(X)), X])
 
        # OLS 回归
        betas = np.linalg.lstsq(X_aug, y, rcond=None)[0]
 
        # R² 计算
        y_pred = X_aug @ betas
        ss_res = np.sum((y - y_pred) ** 2)
        ss_tot = np.sum((y - y.mean()) ** 2)
        r2 = 1 - ss_res / ss_tot
 
        exposures[asset] = betas[1:]  # 去掉截距
        r_squared[asset] = r2
 
    exposures_df = pd.DataFrame(exposures, index=factor_returns.columns).T
    return exposures_df, pd.Series(r_squared)
 
# 示例
np.random.seed(42)
n_days, n_assets, n_factors = 252, 5, 3
asset_names = [f'Asset_{i}' for i in range(n_assets)]
factor_names = ['Market', 'Size', 'Value']
 
factor_ret = pd.DataFrame(
    np.random.normal(0.001, 0.01, (n_days, n_factors)),
    columns=factor_names
)
# 资产收益 = 因子暴露 * 因子收益 + 残差
true_betas = np.array([[1.0, 0.3, -0.2],
                        [1.2, -0.4, 0.5],
                        [0.8, 0.6, 0.1],
                        [1.1, 0.0, 0.3],
                        [0.9, -0.2, -0.4]])
asset_ret = pd.DataFrame(
    factor_ret.values @ true_betas.T + np.random.normal(0, 0.005, (n_days, n_assets)),
    columns=asset_names
)
 
exposures, r2 = factor_exposure_analysis(asset_ret, factor_ret)
print("因子暴露矩阵:")
print(exposures.round(3))
print(f"\nR² (因子解释力):")
print(r2.round(3))

七、协方差矩阵估计

高维协方差矩阵的估计是组合优化的核心难题。当资产数 接近样本数 时,样本协方差矩阵的条件数急剧增大。

7.1 Ledoit-Wolf 收缩估计

将样本协方差矩阵向结构化目标(如恒等矩阵)收缩,以降低估计误差。

其中 是样本协方差矩阵, 是目标矩阵, 是最优收缩强度。

def ledoit_wolf_shrinkage(returns, target='constant_correlation'):
    """
    Ledoit-Wolf 收缩协方差矩阵估计
 
    参数
    ----
    returns : DataFrame
      资产收益率矩阵 (T x N)
    target : str
      收缩目标: 'identity'(单位矩阵), 'constant_correlation'(常相关),
      'single_factor'(单因子模型)
 
    返回
    ----
    shrunk_cov : ndarray
      收缩后的协方差矩阵
    shrinkage : float
      收缩强度 delta
    """
    X = returns.values
    T, N = X.shape
 
    # 样本协方差矩阵
    S = np.cov(X, rowvar=False, ddof=1)
 
    # 样本均值
    mean = X.mean(axis=0)
 
    if target == 'identity':
        # 目标:sigma^2 * I
        F = np.trace(S) / N * np.eye(N)
 
    elif target == 'constant_correlation':
        # 目标:常相关矩阵
        var = np.diag(S)
        # 常相关系数
        sum_r = 0
        for i in range(N):
            for j in range(i + 1, N):
                sum_r += S[i, j] / np.sqrt(var[i] * var[j])
        r_bar = sum_r / (N * (N - 1) / 2)
        F = r_bar * np.sqrt(np.outer(var, var))
        np.fill_diagonal(F, var)
 
    elif target == 'single_factor':
        # 目标:单因子模型
        market = X.mean(axis=1)
        beta = S[:, 0] / S[0, 0] if N > 1 else np.ones(N)
        F = np.outer(beta, beta) * S[0, 0]
        np.fill_diagonal(F, np.diag(S))
 
    else:
        raise ValueError(f"未知的收缩目标: {target}")
 
    # 计算 Ledoit-Wolf 收缩强度
    # 使用 Oracle Approximating Shrinkage 公式
    X_centered = X - mean
 
    # 计算收缩参数的分子和分母
    sum_sq = 0
    for t in range(T):
        x_t = X_centered[t:t + 1, :]  # 行向量
        sum_sq += (x_t.T @ x_t - S) ** 2
    sum_sq /= T
 
    numerator = np.sum(sum_sq)
 
    # S - F 的平方和
    denominator = np.sum((S - F) ** 2)
 
    if denominator == 0:
        shrinkage = 1.0
    else:
        shrinkage = max(0, min(1, numerator / denominator))
 
    shrunk_cov = shrinkage * F + (1 - shrinkage) * S
    return shrunk_cov, shrinkage
 
# 示例
np.random.seed(42)
n_days, n_assets = 120, 30
asset_returns = pd.DataFrame(
    np.random.normal(0.001, 0.02, (n_days, n_assets))
)
 
sample_cov = np.cov(asset_returns, rowvar=False, ddof=1)
lw_cov, delta = ledoit_wolf_shrinkage(asset_returns, target='constant_correlation')
 
print(f"收缩强度 delta: {delta:.4f}")
print(f"样本协方差条件数: {np.linalg.cond(sample_cov):.1f}")
print(f"LW 协方差条件数: {np.linalg.cond(lw_cov):.1f}")

7.2 协方差估计方法对比

方法偏差方差适用场景
样本协方差高(高维时极大)
Ledoit-Wolf通用,推荐首选
因子模型因子结构已知时
核密度估计非线性依赖

八、Copula 与尾部依赖

8.1 什么是 Copula?

Copula 将联合分布分解为边缘分布依赖结构

Sklar 定理保证了对于任意连续联合分布,都存在唯一的 Copula。

8.2 尾部依赖

在金融危机中,资产间的相关性会急剧上升——这就是尾部依赖

  • 上尾依赖
  • 下尾依赖
def tail_dependence(returns_x, returns_y, q=0.05):
    """
    估计经验尾部依赖系数
 
    参数
    ----
    returns_x, returns_y : Series
      两个资产的收益率序列
    q : float
      尾部分位数
 
    返回
    ----
    lower_td : float
      下尾依赖系数
    upper_td : float
      上尾依赖系数
    """
    # 转换为均匀分布(经验 CDF)
    from scipy.stats import rankdata
    u_x = rankdata(returns_x) / (len(returns_x) + 1)
    u_y = rankdata(returns_y) / (len(returns_y) + 1)
 
    # 下尾依赖:两个资产同时处于 q 分位数以下的概率
    both_lower = ((u_x <= q) & (u_y <= q)).sum()
    lower_td = both_lower / (len(returns_x) * q)
 
    # 上尾依赖
    both_upper = ((u_x >= 1 - q) & (u_y >= 1 - q)).sum()
    upper_td = both_upper / (len(returns_x) * q)
 
    return lower_td, upper_td
 
# 示例:模拟两个资产
np.random.seed(42)
r1 = pd.Series(np.random.normal(0.001, 0.02, 500))
# r2 与 r1 有尾部依赖(通过 t-Copula 思想的模拟)
r2 = pd.Series(r1 + np.random.normal(0, 0.01, 500))
 
lower_td, upper_td = tail_dependence(r1, r2)
print(f"下尾依赖系数: {lower_td:.4f}")
print(f"上尾依赖系数: {upper_td:.4f}")

8.3 常见 Copula 类型

Copula特点尾部依赖
Gaussian线性相关无(
t-Copula对称厚尾有(
Clayton下尾厚
Gumbel上尾厚

实战要点:Gaussian Copula 会低估联合极端事件概率,这也是 2008 年金融危机中被忽视的风险——CDO 定价过度依赖 Gaussian Copula。


九、RiskMetrics 综合类

下面将上述方法整合为一个完整的风险度量工具类。

class RiskMetrics:
    """
    市场风险度量工具类
 
    功能:波动率估计、VaR/CVaR 计算、压力测试、因子暴露分析
    """
 
    def __init__(self, returns, portfolio_value=1e6, trading_days=252):
        """
        初始化
 
        参数
        ----
        returns : DataFrame
          资产收益率矩阵 (T x N),列为资产名
        portfolio_value : float
          投资组合市值
        trading_days : int
          年交易日数
        """
        self.returns = returns
        self.portfolio_value = portfolio_value
        self.trading_days = trading_days
        self.assets = returns.columns.tolist()
 
    def portfolio_returns(self, weights):
        """计算组合收益率序列"""
        return self.returns.values @ np.array(weights)
 
    # ---- 波动率 ----
    def volatility(self, method='historical', **kwargs):
        """
        计算波动率
 
        参数
        ----
        method : str
          'historical' | 'ewma' | 'parkinson' | 'garman_klass'
        """
        if method == 'historical':
            return historical_volatility(
                self.returns.iloc[:, 0], **kwargs)
        elif method == 'ewma':
            _, vol = ewma_volatility(
                self.returns.iloc[:, 0], **kwargs)
            return vol.iloc[-1]
        else:
            raise ValueError(f"不支持的方法: {method}")
 
    # ---- VaR ----
    def var(self, weights, confidence=0.99, method='historical'):
        """
        计算组合 VaR
 
        参数
        ----
        weights : list
          资产权重
        confidence : float
          置信水平
        method : str
          'historical' | 'parametric' | 'monte_carlo'
        """
        port_ret = self.portfolio_returns(weights)
        port_ret = pd.Series(port_ret)
 
        if method == 'historical':
            val, pct = var_historical(
                port_ret, confidence, self.portfolio_value)
        elif method == 'parametric':
            val, pct = var_parametric(
                port_ret, confidence, self.portfolio_value)
        elif method == 'monte_carlo':
            val, pct, _ = var_monte_carlo(
                port_ret, confidence=confidence,
                portfolio_value=self.portfolio_value)
        else:
            raise ValueError(f"不支持的 VaR 方法: {method}")
 
        return {'method': method, 'confidence': confidence,
                'var_value': val, 'var_pct': pct}
 
    # ---- CVaR ----
    def cvar(self, weights, confidence=0.99):
        """计算组合 CVaR"""
        port_ret = self.portfolio_returns(weights)
        port_ret = pd.Series(port_ret)
        val, pct, var_pct = cvar(
            port_ret, confidence, self.portfolio_value)
        return {'cvar_value': val, 'cvar_pct': pct, 'var_pct': var_pct}
 
    # ---- 协方差矩阵 ----
    def covariance(self, method='ledoit_wolf'):
        """计算协方差矩阵"""
        if method == 'sample':
            return np.cov(self.returns, rowvar=False, ddof=1)
        elif method == 'ledoit_wolf':
            cov, delta = ledoit_wolf_shrinkage(self.returns)
            return cov, delta
        else:
            raise ValueError(f"不支持的协方差方法: {method}")
 
    # ---- 综合报告 ----
    def risk_report(self, weights, confidence=0.99):
        """
        生成综合风险报告
 
        参数
        ----
        weights : list
          资产权重
        confidence : float
          置信水平
 
        返回
        ----
        report : dict
          综合风险指标
        """
        port_ret = self.portfolio_returns(weights)
        port_ret = pd.Series(port_ret)
 
        # 各类 VaR
        var_hist = self.var(weights, confidence, 'historical')
        var_param = self.var(weights, confidence, 'parametric')
        var_mc = self.var(weights, confidence, 'monte_carlo')
 
        # CVaR
        cvar_info = self.cvar(weights, confidence)
 
        # 协方差
        cov_lw, shrinkage = self.covariance('ledoit_wolf')
 
        # 组合波动率
        w = np.array(weights)
        port_vol = np.sqrt(w @ cov_lw @ w) * np.sqrt(self.trading_days)
 
        report = {
            '组合年化波动率': f"{port_vol:.2%}",
            '历史模拟 VaR': f"{var_hist['var_value']:,.0f}",
            '参数法 VaR': f"{var_param['var_value']:,.0f}",
            '蒙特卡洛 VaR': f"{var_mc['var_value']:,.0f}",
            'CVaR': f"{cvar_info['cvar_value']:,.0f}",
            'LW 收缩强度': f"{shrinkage:.4f}",
            '协方差条件数': f"{np.linalg.cond(cov_lw):.1f}",
        }
        return report
 
# 使用示例
np.random.seed(42)
n_days, n_assets = 500, 3
asset_names = ['股票', '债券', '商品']
returns_df = pd.DataFrame(
    np.random.normal(0.001, 0.02, (n_days, n_assets)),
    columns=asset_names
)
 
rm = RiskMetrics(returns_df, portfolio_value=1e7)
weights = [0.6, 0.3, 0.1]
 
report = rm.risk_report(weights)
print("=== 综合风险报告 ===")
for k, v in report.items():
    print(f"  {k}: {v}")

总结

度量维度核心工具关键要点
波动率EWMA / GK近期波动 > 历史平均
损失风险VaR + CVaRCVaR > VaR,关注尾部
极端事件EVT / GPD尾指数 判断厚尾程度
情景分析历史情景 / 因子冲击VaR 的补充,而非替代
依赖结构Copula危机时相关性飙升
协方差Ledoit-Wolf高维必收缩

下一步:学习02-组合风控与资金管理,将风险度量结果转化为可执行的仓位管理和资金分配方案。