4. VaR计算方法(二):历史模拟法的原理、滚动窗口计算与Python实现
历史模拟法(Historical Simulation, HS)是计算VaR最直观的非参数方法。它不假设收益率服从任何特定分布,而是直接使用过去N个交易日的实际收益率数据,通过排序找到指定分位数的损失值。该方法天然捕捉了资产收益率的“尖峰厚尾”特征,且无需估计协方差矩阵,在金融风控实践中被广泛用作基准模型。
4.1 核心原理:从历史中寻找“最坏的一天”
历史模拟法的逻辑可以概括为:“过去发生过的损失,在未来同样可能发生”。具体步骤如下:
- 收集历史收益率:选取过去T个交易日(如250天、500天)的资产组合日收益率序列。
- 排序:将T个收益率从小到大(从最亏损到最盈利)排序。
- 确定分位点:对于置信水平α(如95%或99%),VaR对应排序后第
(1-α) × T个位置的收益率值。 - 输出VaR:该收益率即为历史模拟法下的VaR估计值(通常取绝对值表示损失)。
关键区别: 与参数法(如方差-协方差法)不同,历史模拟法不需要假设正态分布,因此能更好地反映极端事件(如2008年金融危机、2020年新冠熔断)在历史数据中留下的“厚尾”痕迹。
4.2 滚动窗口计算:动态捕捉市场变化
固定窗口的历史模拟法存在一个明显缺陷:“一次计算,长期使用”。当市场结构发生改变(如波动率骤升、趋势反转)时,旧数据会稀释新信息的敏感度。滚动窗口(Rolling Window)计算通过定期“滑动”数据窗口,使VaR估计始终基于最新的市场状态。
| 窗口类型 | 窗口长度 | 更新频率 | 适用场景 | 优缺点 |
|---|---|---|---|---|
| 固定窗口 | 固定(如250天) | 不更新 | 静态回测、监管报告 | 简单稳定,但反应滞后 |
| 滚动窗口 | 固定(如250天) | 每日滚动 | 实时风控、交易决策 | 敏感度高,计算量大 |
| 指数加权窗口 | 无限(衰减因子λ) | 每日更新 | 高波动市场、高频策略 | 灵活,需调参λ |
实战建议: 对于股票组合,通常使用250个交易日(约1年)作为滚动窗口长度。若市场波动剧烈(如VIX指数飙升),可临时缩短至125天或63天,以更快反映近期风险。
4.3 Python实现:从零构建滚动历史模拟VaR
以下代码展示了如何用Python实现滚动窗口历史模拟法。我们使用pandas进行数据操作,numpy进行排序和分位点计算。
import numpy as np
import pandas as pd
def rolling_hs_var(returns, window=250, confidence=0.95):
"""
滚动窗口历史模拟法计算VaR
参数:
returns (pd.Series): 日收益率序列
window (int): 滚动窗口长度,默认250天
confidence (float): 置信水平,默认95%
返回:
pd.Series: 每日VaR估计值(正数表示损失)
"""
var_series = []
# 从第window个数据点开始计算
for i in range(window, len(returns) + 1):
# 提取滚动窗口内的收益率
window_data = returns.iloc[i - window : i]
# 升序排列
sorted_returns = np.sort(window_data.values)
# 计算分位点索引(注意:损失在左侧尾部)
index = int((1 - confidence) * window)
# 取对应位置的收益率(负值),取绝对值作为VaR
var_value = abs(sorted_returns[index])
var_series.append(var_value)
# 返回与原始数据对齐的时间序列
return pd.Series(var_series, index=returns.index[window:])
# 示例用法
if __name__ == "__main__":
# 生成模拟收益率数据(1000个交易日)
np.random.seed(42)
dates = pd.date_range(start="2020-01-01", periods=1000, freq="B")
# 模拟具有厚尾特征的收益率(t分布)
simulated_returns = pd.Series(
np.random.standard_t(df=4, size=1000) * 0.01,
index=dates
)
# 计算95% VaR
var_95 = rolling_hs_var(simulated_returns, window=250, confidence=0.95)
print("最近5个交易日的VaR(95%):")
print(var_95.tail())
# 输出示例:
# 2023-12-18 0.0215
# 2023-12-19 0.0198
# 2023-12-20 0.0223
# 2023-12-21 0.0201
# 2023-12-22 0.0210
⚠️ 常见陷阱:
- 数据对齐:滚动窗口计算后,返回的VaR序列长度比原始收益率短
window个点,务必检查索引对齐。 - 分位点索引:当
(1-confidence) * window不是整数时,建议使用线性插值(如np.percentile),而非直接取整。 - 窗口长度选择:窗口过短(如<100天)会导致VaR估计噪声过大;窗口过长(如>500天)会降低对近期波动的敏感度。
4.4 历史模拟法的优缺点对比
| 维度 | 优势 | 劣势 |
|---|---|---|
| 分布假设 | 无参数假设,自然捕捉厚尾 | 依赖历史数据质量,无法预测“从未发生”的极端事件 |
| 计算复杂度 | 实现简单,无需估计协方差矩阵 | 滚动窗口下需重复排序,大数据集计算效率低 |
| 市场适应性 | 滚动窗口可动态调整 | 对结构性突变(如政策转向)反应滞后 |
| 可解释性 | 结果直观,可直接对应历史某一天 | 无法提供风险因子归因(如利率、汇率贡献) |
进阶方向: 当历史模拟法无法满足实时性要求时,可引入“衰减加权历史模拟法”(Age-Weighted HS),对近期数据赋予更高权重;或使用“过滤历史模拟法”(Filtered HS),先对收益率进行GARCH模型过滤,再对标准化残差进行历史模拟,以同时捕捉波动率聚类和厚尾特征。