主题切换
6.5 压力测试与VaR
概念详解
风险价值(VaR)和压力测试是投资组合风险管理的两大核心工具。VaR回答"在正常市场条件下,我的组合在最坏的N天可能亏多少钱?",而压力测试回答"如果历史极端事件重演(或更糟),我的组合会亏多少钱?"两者相辅相成——VaR覆盖日常风险管理,压力测试覆盖尾部极端风险管理。
风险管理工具的层次结构:
| 工具 | 解决的问题 | 时间维度 | 方法 |
|---|---|---|---|
| VaR | 正常情况的最差损失 | 1天到10天 | 参数法、历史法、蒙特卡洛 |
| CVaR | VaR之外的损失有多大 | 同VaR | 尾部均值 |
| 压力测试 | 极端事件的损失 | 瞬时/事件窗口 | 历史情景、假设情景 |
| 敏感性分析 | 单因子变化的影响 | 瞬时 | 偏导数(Greeks) |
名词解释:VaR (在险价值)
在一定的置信水平和持有期内,投资组合可能遭受的最大损失。例如95%置信度下的1天VaR表示有95%的把握损失不会超过该值。数学上:
名词解释:CVaR (条件在险价值)
当损失超过VaR阈值时,损失的平均值,衡量尾部风险的大小。CVaR =
名词解释:压力测试
将投资组合置于历史上曾发生过的极端市场情景(如2008年金融危机、2020年3月新冠崩盘)或假设的极端情景下,评估组合可能遭受的损失。压力测试弥补了VaR/CVaR的不足——历史数据可能不包含某些极端情景,而压力测试直接模拟这些情景。
数学原理
VaR的三种计算方法
1. 参数法(方差-协方差法):假设收益率服从正态分布
其中
2. 历史模拟法:对历史收益率排序,取第
3. 蒙特卡洛法:从拟合的分布中随机抽样生成大量情景,对模拟收益排序取分位数。
CVaR
压力测试的数学框架
压力情景定义为一系列市场变量的冲击向量
其中
📉 蒙特卡洛VaR计算器
📉 蒙特卡洛VaR计算器
Python实战
📌 案例:完整的VaR/CVaR计算
此处有展示代码展开 ▼
python
import numpy as np
import pandas as pd
from scipy import stats
class RiskMetrics:
def __init__(self, returns, confidence_levels=[0.95, 0.99]):
"""
Parameters:
returns: pd.Series, 日收益率
confidence_levels: 置信水平列表
"""
self.returns = returns
self.alpha_levels = confidence_levels
def var_parametric(self, horizon=1):
"""参数法VaR(假设正态分布)"""
mu = self.returns.mean()
sigma = self.returns.std()
results = {}
for alpha in self.alpha_levels:
z_score = stats.norm.ppf(1 - alpha)
var = -(mu * horizon + sigma * np.sqrt(horizon) * z_score)
results[f'VaR_{int(alpha*100)}%'] = var
return results
def var_historical(self, horizon=1):
"""历史模拟法VaR"""
horizon_returns = self.returns.rolling(horizon).sum().dropna()
results = {}
for alpha in self.alpha_levels:
var = -np.percentile(horizon_returns, (1 - alpha) * 100)
results[f'VaR_{int(alpha*100)}%'] = var
return results
def var_cvar_monte_carlo(self, n_simulations=10000, horizon=1):
"""蒙特卡洛法VaR和CVaR"""
# 用t分布拟合(比正态分布更好地捕捉肥尾)
t_df, t_loc, t_scale = stats.t.fit(self.returns)
simulated_returns = stats.t.rvs(t_df, t_loc, t_scale,
size=(n_simulations, horizon))
if horizon > 1:
simulated_returns = simulated_returns.sum(axis=1)
results = {}
for alpha in self.alpha_levels:
# VaR
var = -np.percentile(simulated_returns, (1 - alpha) * 100)
results[f'VaR_{int(alpha*100)}%'] = var
# CVaR (Expected Shortfall)
tail = simulated_returns[simulated_returns <= -var]
cvar = -tail.mean() if len(tail) > 0 else var
results[f'CVaR_{int(alpha*100)}%'] = cvar
return results
def full_risk_report(self):
"""完整的风险报告"""
report = {}
# 基础统计
report['mean_daily'] = self.returns.mean()
report['std_daily'] = self.returns.std()
report['skewness'] = self.returns.skew()
report['kurtosis'] = self.returns.kurtosis()
report['min_daily'] = self.returns.min()
# VaR/CVaR (1天)
report.update({f'Parametric_{k}': v for k, v in self.var_parametric().items()})
report.update({f'Historical_{k}': v for k, v in self.var_historical().items()})
report.update({f'MC_{k}': v for k, v in self.var_cvar_monte_carlo().items()})
# 10天VaR (sqrt(T)缩放)
report.update({
f'10d_Parametric_{k}': v * np.sqrt(10)
for k, v in self.var_parametric().items()
})
return pd.Series(report)点击展开可浏览运行结果
95%置信度, 30天VaR: $10,192.95 CVaR (尾部期望损失): $12,738.61 占初始资金: 10.19%
📌 案例:压力测试框架
此处有展示代码展开 ▼
python
def stress_test_portfolio(weights, factor_sensitivities, scenarios):
"""
对投资组合进行多情景压力测试
Parameters:
weights: 资产权重向量 (N,)
factor_sensitivities: 各资产对各风险因子的敏感度 (N, K)
scenarios: dict, {scenario_name: {factor_name: shock}}
shock: 因子变化(如利率+100bp, 指数-20%)
"""
results = {}
for scenario_name, shocks in scenarios.items():
# 将情景冲击转换为每个资产的收益冲击
asset_shocks = np.zeros(len(weights))
for factor_name, shock in shocks.items():
if factor_name in factor_sensitivities.columns:
asset_shocks += factor_sensitivities[factor_name] * shock
# 组合层面的损失
portfolio_loss = -np.sum(weights * asset_shocks)
results[scenario_name] = portfolio_loss
return pd.Series(results, name='Portfolio Loss')
# 定义压力情景
scenarios = {
'2008金融危机': {
'equity': -0.45, # 股票跌45%
'credit_spread': 0.05, # 信用利差扩大5%
'volatility': 0.40, # 波动率上升40%
'liquidity': -0.30, # 流动性下降30%
},
'2020新冠崩盘': {
'equity': -0.34,
'credit_spread': 0.03,
'volatility': 0.50,
'liquidity': -0.20,
},
'利率急升': {
'equity': -0.15,
'interest_rate': 0.02, # 利率+200bp
'credit_spread': 0.01,
},
'通胀飙升': {
'equity': -0.10,
'commodity': 0.20,
'interest_rate': 0.025,
'volatility': 0.15,
},
'自定极端': {
'equity': -0.30,
'credit_spread': 0.05,
'volatility': 0.40,
'liquidity': -0.40,
'interest_rate': 0.03,
}
}点击展开可浏览运行结果
--- 结果 ---
scenario_name = array([ 0.35778736, 0.56078453, 1.08305124, 1.05380205, -1.37766937,
-0.93782504, 0.51503527, 0.51378595, 0.51504769, 3.85273149,
0.57089051, 1.13556564, 0.95400176, 0.6513912
factor_name = array([-0.82899501, -0.56018104, 0.74729361, 0.61037027, -0.02090159,
0.11732738, 1.2776649 , -0.59157139, 0.54709738, -0.20219265,
-0.2176812 , 1.09877685, 0.82541635, 0.8135096
shock = array([-1.59442766, -0.59937502, 0.0052437 , 0.04698059, -0.45006547,
0.62284993, -1.06762043, -0.14237949, 0.12029563, 0.51443883,
0.71161488, -1.12464209, -1.53411417, 1.2776768
scenarios = {'2008金融危机': {'equity': -0.45, 'credit_spread': 0.05, 'volatility': 0.4, 'liquidity': -0.3}, '2020新冠崩盘': {'equity': -0.34, 'credit_spread': 0.03, 'volatility': 0.5, 'liquidity': -0.2}, '利率急升': {'equity': -0.15, 'interest_rate': 0.02, 'credit_spread': 0.01}, '通胀飙升': {'equity': -0.1, 'commodity': 0.2, 'interest_rate': 0.025, 'volatility': 0.15}, '自定极端': {'equity': -0.3, 'credit_spread': 0.05, 'volatility': 0.4, 'liquidity': -0.4, 'interest_rate': 0.03}}
--- stress_test_portfolio(...) ---📌 案例:滚动VaR回测
此处有展示代码展开 ▼
python
def rolling_var_backtest(returns, window=252, confidence=0.95):
"""
滚动计算VaR并与实际损失对比,做VaR有效性检验
理想情况下:实际损失超过VaR的比例应该接近 (1-confidence)
如:95% VaR下,平均每20个交易日有1次被突破
"""
roll_var = returns.rolling(window).apply(
lambda x: -np.percentile(x, (1 - confidence) * 100)
)
# 实际损失(负收益的绝对值)
losses = -returns
# VaR突破:损失 > VaR(注意VaR通常为正数表示损失)
breaches = (losses > roll_var.shift(1)).dropna()
breach_rate = breaches.mean()
# Kupiec检验的统计量
n = len(breaches)
n_breaches = breaches.sum()
expected_rate = 1 - confidence
# 似然比检验
if n_breaches > 0 and n_breaches < n:
LR = -2 * np.log(
((1 - expected_rate) ** (n - n_breaches) * expected_rate ** n_breaches) /
((1 - n_breaches/n) ** (n - n_breaches) * (n_breaches/n) ** n_breaches)
)
p_value = 1 - stats.chi2.cdf(LR, df=1)
else:
p_value = np.nan
print(f"VaR突破率: {breach_rate:.2%} (期望: {expected_rate:.2%})")
print(f"VaR突破次数: {n_breaches}/{n}")
print(f"Kupiec检验p值: {p_value:.4f}")
if p_value < 0.05:
print("⚠️ VaR模型不准确(在5%水平下拒绝原假设)")
else:
print("✅ VaR模型通过回测检验")
return roll_var, breaches点击展开可浏览运行结果
📘 本段代码定义了 1 个函数/类:函数 `rolling_var_backtest`(滚动计算VaR并与实际损失对比,做VaR有效性检验)。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。
常见误区
VaR只告诉你"门槛",不告诉你"门槛后有多惨":VaR=100万意味着"有5%的日子亏超100万",但超了之后是亏101万还是500万,VaR不回答。这正是CVaR存在的意义。
参数法VaR假设正态分布:正态分布严重低估了金融数据的肥尾特征。2008年金融危机时,基于正态分布假设的VaR模型几乎全部失效。应使用考虑肥尾的方法(t分布、GARCH、EVT极值理论)。
VaR不具备次可加性:VaR不是一致风险度量(coherent risk measure)——它不具备次可加性。两个组合的VaR之和可能小于合并组合的VaR,这意味着VaR不惩罚集中风险。CVaR具备次可加性,是一致风险度量。
忽略波动率聚集:风险管理中最大的错误之一是使用固定窗口的历史波动率。在平静期之后VaR大幅下降,紧跟着的就是极端的VaR突破——这正是波动率聚集的体现。GARCH类模型可以更好地捕捉波动率的时变特征。
压力测试情景过于温和:人们倾向于只测试"曾经发生过的事"。但"从未发生的事"才是真正的尾部风险。压力测试应该包括假设性的极端情景,而不只是历史重演。
实战练习
用一个包含股票和债券的组合(60/40),分别用参数法、历史模拟法和蒙特卡洛法计算95%和99%的1天VaR和CVaR。比较三种方法的结果差异,特别是99%水平下。
VaR回测:使用滚动2年窗口计算历史模拟法VaR(95%),记录过去5年中VaR被实际损失突破的日期。计算突破率并执行Kupiec检验。观察VaR突破是否在时间上聚集。
压力测试实验:定义3-5个压力情景(如2008年金融危机、2015年A股崩盘、2020年新冠、利率急升、自定极端情景),计算你的组合在各情景下的预期损失。哪些情景对你的组合最危险?如何调整组合以降低对这些情景的脆弱性?
延伸阅读
- Jorion, P. (2006). Value at Risk: The New Benchmark for Managing Financial Risk. McGraw-Hill. VaR最经典全面的教科书。
- Artzner, P., Delbaen, F., Eber, J. M., & Heath, D. (1999). "Coherent Measures of Risk." Mathematical Finance. 提出了一致风险度量(CVaR满足,VaR不满足)。
- Kupiec, P. H. (1995). "Techniques for Verifying the Accuracy of Risk Measurement Models." 讨论了VaR的回测检验方法。
- McNeil, A. J., Frey, R., & Embrechts, P. (2015). Quantitative Risk Management. Princeton University Press. 风险管理的数学标准教材。
- Taleb, N. N. (2007). The Black Swan. 深刻讨论了尾部风险和不可预测的极端事件。
本章要点
- VaR衡量正常市场条件下的最差损失,CVaR衡量进入尾部后的平均损失——两者互补,构成完整的风险度量体系。
- 三种VaR计算方法:参数法(快速但假设强)、历史模拟法(无分布假设但依赖历史重复)、蒙特卡洛法(最灵活但计算量大)。
- VaR不具备次可加性(不是一致风险度量),CVaR具备次可加性(是一致风险度量),因此巴塞尔协议III已推荐用CVaR替代VaR。
- 压力测试弥补VaR/CVaR的盲区——测试历史上未发生或极少发生的极端情景。
- VaR模型需要定期回测检验(Kupiec检验、Christoffersen检验),突破率显著偏离理论水平意味着模型需要重新校准。
- 波动率聚集意味着VaR的估计在平静期和动荡期不应相同——GARCH等时变波动率模型可以更好地捕捉这一特征。