4. VaR计算方法(二):历史模拟法的原理、滚动窗口计算与Python实现

历史模拟法(Historical Simulation, HS)是计算VaR最直观的非参数方法。它不假设收益率服从任何特定分布,而是直接使用过去N个交易日的实际收益率数据,通过排序找到指定分位数的损失值。该方法天然捕捉了资产收益率的“尖峰厚尾”特征,且无需估计协方差矩阵,在金融风控实践中被广泛用作基准模型。

4.1 核心原理:从历史中寻找“最坏的一天”

历史模拟法的逻辑可以概括为:“过去发生过的损失,在未来同样可能发生”。具体步骤如下:

  1. 收集历史收益率:选取过去T个交易日(如250天、500天)的资产组合日收益率序列。
  2. 排序:将T个收益率从小到大(从最亏损到最盈利)排序。
  3. 确定分位点:对于置信水平α(如95%或99%),VaR对应排序后第 (1-α) × T 个位置的收益率值。
  4. 输出VaR:该收益率即为历史模拟法下的VaR估计值(通常取绝对值表示损失)。
关键区别: 与参数法(如方差-协方差法)不同,历史模拟法不需要假设正态分布,因此能更好地反映极端事件(如2008年金融危机、2020年新冠熔断)在历史数据中留下的“厚尾”痕迹。
历史模拟法VaR计算流程 历史模拟法VaR计算流程 历史收益率序列 升序排列(最差→最好) 定位 (1-α)×T 位置 输出 VaR 值 T = 滚动窗口长度 α = 置信水平(如95%)

4.2 滚动窗口计算:动态捕捉市场变化

固定窗口的历史模拟法存在一个明显缺陷:“一次计算,长期使用”。当市场结构发生改变(如波动率骤升、趋势反转)时,旧数据会稀释新信息的敏感度。滚动窗口(Rolling Window)计算通过定期“滑动”数据窗口,使VaR估计始终基于最新的市场状态。

窗口类型 窗口长度 更新频率 适用场景 优缺点
固定窗口 固定(如250天) 不更新 静态回测、监管报告 简单稳定,但反应滞后
滚动窗口 固定(如250天) 每日滚动 实时风控、交易决策 敏感度高,计算量大
指数加权窗口 无限(衰减因子λ) 每日更新 高波动市场、高频策略 灵活,需调参λ
实战建议: 对于股票组合,通常使用250个交易日(约1年)作为滚动窗口长度。若市场波动剧烈(如VIX指数飙升),可临时缩短至125天或63天,以更快反映近期风险。
滚动窗口计算示意图 滚动窗口计算示意图(窗口长度=5天) t-4 t-3 t-2 t-1 t t+1 t+2 窗口1:计算 VaR_t 窗口2:计算 VaR_{t+1} 滚动 每日丢弃最旧数据 加入最新数据

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模型过滤,再对标准化残差进行历史模拟,以同时捕捉波动率聚类和厚尾特征。