In [1]:
Copied!
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
np.random.seed(42)
print('Libraries loaded')
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
In [2]:
Copied!
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
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 的三种计算方法:
- 历史模拟法:直接取历史收益率分布的分位数
- 参数法(正态假设):假设正态分布,用均值和标准差计算
- 蒙特卡洛法:大量模拟路径取分位数
In [3]:
Copied!
# 模拟股票收益率数据(带肥尾,模拟真实市场)
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')
# 模拟股票收益率数据(带肥尾,模拟真实市场)
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
In [4]:
Copied!
# 可视化 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()
# 可视化 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 相同,但组合 A 坏情况下亏 3%,组合 B 坏情况下亏 30%,VaR 无法区分!
CVaR(Conditional VaR) 解决了这个问题:它计算所有超过 VaR 阈值的损失的平均值:
$$\text{CVaR}_{\alpha} = E[L | L > \text{VaR}_{\alpha}]$$
In [5]:
Copied!
# 计算 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()
# 计算 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
In [ ]:
Copied!