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-Klass | OHLC | 信息量最大 | 假设连续交易 |
二、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 + CVaR | CVaR > VaR,关注尾部 |
| 极端事件 | EVT / GPD | 尾指数 判断厚尾程度 |
| 情景分析 | 历史情景 / 因子冲击 | VaR 的补充,而非替代 |
| 依赖结构 | Copula | 危机时相关性飙升 |
| 协方差 | Ledoit-Wolf | 高维必收缩 |
下一步:学习02-组合风控与资金管理,将风险度量结果转化为可执行的仓位管理和资金分配方案。