1. 这节究竟解决什么实际问题?¶
5.6 风险度量:VaR 与 CVaR (Value at Risk & Conditional VaR) 的具体任务是:比较 VaR 与 CVaR 对尾部损失的描述能力和盲区。 本节将明确输入数据、计算过程、输出及其在后续研究中的用途。
2. 前置知识¶
理解收益率、协方差、波动率和矩阵的基本含义。 对公式的理解应包括变量定义、单位、时间索引和适用条件。
3. 数据说明¶
固定多资产价格样本用于演示;不把少量资产、单一时期的最优权重当作配置建议。 运行前先检查 shape、日期范围、列名与缺失值。数据看起来能画图,不代表它适合回答研究问题。
4. 学习目标与完成标准¶
完成本节后,你应能用自己的话解释 5.6 风险度量:VaR 与 CVaR (Value at Risk & Conditional VaR) 的输入、计算步骤、输出和局限;尤其能说明:比较 VaR 与 CVaR 对尾部损失的描述能力和盲区。 你还应能独立改动一个参数并解释结果为什么变化,指出至少一个会让结论失真的假设。
5. 核心概念与公式¶
组合配置同时考虑单项资产风险和资产之间的协方差,目标是控制整体风险暴露。
组合收益是权重加权和;风险由协方差矩阵决定,权重之和、杠杆和做空限制必须写清楚。 看到公式时依次确认:每个符号的单位是什么?它属于哪个时点?分母会不会为零?它是描述性统计量、预测量,还是交易规则?
6. 从零实现¶
原始代码按照“准备输入 → 计算中间量 → 得到结果 → 解释输出”的顺序组织。每个单元应检查变量类型、索引、维度和代表性数值。中间变量用于核对公式实现和定位计算错误。
7. 成熟库实现¶
优化器可以求满足约束的数值解;它不会告诉你输入的预期收益是否可信。 使用库函数前先阅读参数含义、默认值和返回对象;库输出一个数字,并不说明这个数字适合你的交易假设。
8. 两种实现的差异¶
从零实现便于核对公式、调试和识别隐含假设;成熟库适合在计算逻辑已验证后复用。两种实现依赖相同的数据口径与时点假设,使用库函数不会改变输入数据的质量。
9. 预期结果与检查范围¶
你应看到维度正确的 Series/DataFrame、日期单调递增的索引,以及能支持“比较 VaR 与 CVaR 对尾部损失的描述能力和盲区。”这一任务的表格或图形。金融日收益率均值通常接近 0;若出现极大的连续收益、空表、全 NaN 或索引错位,应先排查数据和对齐逻辑,而不是立即相信结果。
10. 常见错误与诊断¶
协方差矩阵不可逆、权重未归一化、用样本内最优权重宣称可投资、忽略再平衡成本。
诊断顺序:先打印类型与 shape,再看 head()/tail(),接着检查日期和 NaN,最后才比较数值。这样能避免在错误的对象上反复改参数。
11. 研究偏差检查¶
均值估计噪声极大;需要样本外比较、参数敏感性和等权等简单基线。 结果至少应与一个简单基线比较,并明确哪些部分只是教学演示。
12. 三层练习与答案¶
- 基础练习:验证一个中间变量、补全一段小计算,并用一句话解释输出。
- 研究练习:改变窗口、样本区间、约束或标的,记录哪些结论仍成立。
- 开放项目:把本节方法嵌入模块项目,报告基线、失败结果和限制。
题目在 ../exercises/05_portfolio/06_var_cvar_exercises.ipynb;完整推导、可运行参考实现与结果检查在 ../solutions/05_portfolio/06_var_cvar_solutions.ipynb。请先独立作答,再打开答案。
13. 延伸阅读¶
延伸材料:Portfolio Selection(Markowitz, 1952)。阅读记录应包括研究假设、数据频率、样本范围和结论适用条件。
14. 下一节¶
下一节是 07_covariance_estimation。本节的概念会成为理解它的输入,而不是孤立的名词。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
np.random.seed(42)
print('Libraries loaded')
Libraries loaded
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
1. VaR 的定义¶
VaR (Value at Risk) 回答了这个问题:
在给定置信水平(如 95%)下,持有某投资组合一段时间内,最坏情况下的最大预期损失是多少?
$$\text{VaR}_{\alpha} = \inf\{l : P(L > l) \le 1-\alpha\}$$
例如:日 95% VaR = -2% 意味着在 95% 的交易日里,单日亏损不会超过 2%(但有 5% 的天数会超过)。
VaR 的三种计算方法:
- 历史模拟法:直接取历史收益率分布的分位数
- 参数法(正态假设):假设正态分布,用均值和标准差计算
- 蒙特卡洛法:大量模拟路径取分位数
# 模拟股票收益率数据(带肥尾,模拟真实市场)
true_returns = np.concatenate([
np.random.normal(0.0003, 0.012, 800), # 正常交易日
np.random.normal(-0.025, 0.03, 200), # 危机期间(肥尾)
])
np.random.shuffle(true_returns)
confidence = 0.95
# 方法 1:历史模拟法
var_historical = -np.percentile(true_returns, (1 - confidence) * 100)
# 方法 2:参数法(正态假设)
mu = true_returns.mean()
sigma = true_returns.std()
var_parametric = -(mu + stats.norm.ppf(1 - confidence) * sigma)
# 方法 3:蒙特卡洛法
mc_returns = np.random.normal(mu, sigma, 100_000)
var_mc = -np.percentile(mc_returns, (1 - confidence) * 100)
print(f'95% 置信水平日 VaR:')
print(f' 历史模拟法: {var_historical:.4f} ({var_historical:.2%})')
print(f' 参数法(正态): {var_parametric:.4f} ({var_parametric:.2%})')
print(f' 蒙特卡洛法: {var_mc:.4f} ({var_mc:.2%})')
print(f'\n注意:由于肥尾效应,参数法(正态分布)低估了真实 VaR')
95% 置信水平日 VaR: 历史模拟法: 0.0409 (4.09%) 参数法(正态): 0.0347 (3.47%) 蒙特卡洛法: 0.0347 (3.47%) 注意:由于肥尾效应,参数法(正态分布)低估了真实 VaR
# 可视化 VaR
fig, ax = plt.subplots(figsize=(12, 6))
# 绘制收益率分布
ax.hist(true_returns, bins=80, density=True, alpha=0.6, color='steelblue', label='收益率分布')
# 标注 VaR
ax.axvline(-var_historical, color='red', linewidth=2,
label=f'95% VaR (历史法) = {var_historical:.2%}')
ax.axvline(-var_parametric, color='orange', linewidth=2, linestyle='--',
label=f'95% VaR (参数法) = {var_parametric:.2%}')
# 填充尾部损失区域
x_fill = np.linspace(true_returns.min(), -var_historical, 100)
y_fill = np.interp(x_fill, sorted(true_returns),
np.linspace(0, 1, len(true_returns)))
ax.axvspan(true_returns.min(), -var_historical, alpha=0.3, color='red', label='VaR 损失尾部区域')
ax.set_xlabel('日收益率')
ax.set_ylabel('概率密度')
ax.set_title('收益率分布与 VaR 可视化(肥尾分布)')
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()
2. CVaR(条件风险价值)= 期望损失 (Expected Shortfall)¶
VaR 的最大缺陷:它只告诉你在 5% 的坏情况下损失"超过"了多少,但不告诉你超过了多少。
若两个组合的 95% VaR 相同,但超过该阈值后的平均损失分别接近 3% 和 30%,VaR 本身不能区分两者的尾部严重程度;CVaR 可以补充这一信息。
CVaR(Conditional VaR) 解决了这个问题:它计算所有超过 VaR 阈值的损失的平均值:
$$\text{CVaR}_{\alpha} = E[L | L > \text{VaR}_{\alpha}]$$
# 计算 CVaR(期望损失)
var_threshold = -var_historical # 历史法 VaR 对应的收益率阈值
tail_losses = true_returns[true_returns < var_threshold]
cvar = -tail_losses.mean()
print(f'95% VaR: {var_historical:.4f} ({var_historical:.2%})')
print(f'95% CVaR: {cvar:.4f} ({cvar:.2%})')
print(f'CVaR/VaR 比率: {cvar/var_historical:.2f}x (CVaR 更能反映极端情景下的真实损失)')
# 对比 VaR vs CVaR 的可视化
fig, ax = plt.subplots(figsize=(12, 5))
ax.hist(true_returns, bins=80, density=True, alpha=0.5, color='steelblue')
ax.axvline(-var_historical, color='red', linewidth=2, label=f'95% VaR = {var_historical:.2%}')
ax.axvline(-cvar, color='darkred', linewidth=2, linestyle='--', label=f'95% CVaR = {cvar:.2%}')
ax.axvspan(true_returns.min(), var_threshold, alpha=0.3, color='red')
ax.set_xlabel('日收益率')
ax.set_title('VaR vs CVaR(CVaR 捕捉尾部损失的平均水平)')
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()
95% VaR: 0.0409 (4.09%) 95% CVaR: 0.0561 (5.61%) CVaR/VaR 比率: 1.37x (CVaR 更能反映极端情景下的真实损失)
练习¶
- 分别计算 90%、95%、99% 三种置信水平下的 VaR 和 CVaR,绘制成表格进行对比。
- 对比正态分布样本与真实 AAPL 历史收益率的 VaR(两者有什么区别?肥尾效应影响多大)。
- 基于蒙特卡洛方法,对含有 3 只股票(有相关性)的投资组合计算组合层面的 VaR。
下一节 → 07_covariance_estimation.ipynb