4. 历史模拟法VaR计算:历史收益率排序、分位数选取、滚动窗口计算、Python实现示例

历史模拟法(Historical Simulation, HS)是VaR计算中最直观、最符合监管直觉的非参数方法。它不假设收益率服从任何特定分布,而是直接使用过去一段时间的实际收益率数据,通过排序和分位数提取来估计未来风险。本章将系统讲解其核心步骤、关键参数选择以及Python实现。

4.1 历史模拟法核心流程

历史模拟法的计算逻辑可以概括为三个步骤:收集历史收益率 → 排序 → 取分位数。下面通过一个SVG流程图展示其完整架构。

历史模拟法VaR计算流程 历史价格/收益率序列 滚动窗口设定(如250天) 窗口内收益率升序排序 分位数选取(如5%/1%) 输出VaR值 核心思想 历史会重演 过去N天的收益率分布 代表未来1天的可能损益

4.2 历史收益率排序与分位数选取

在历史模拟法中,排序和分位数选取是两个最关键的数学操作。它们的准确性直接决定VaR的可靠性。

4.2.1 收益率排序

假设我们有一个包含N个历史收益率的样本,首先将这些收益率按从小到大(从最差到最好)进行排序:

  • 排序后序列:\( r_{(1)} \leq r_{(2)} \leq \cdots \leq r_{(N)} \),其中 \( r_{(1)} \) 是最大亏损(最小收益率)。
  • 位置索引:VaR对应的位置由分位数水平 \( \alpha \) 和样本量 \( N \) 共同决定。

4.2.2 分位数选取方法

对于给定的置信水平 \( c \)(如95%或99%),对应的损失分位数水平 \( \alpha = 1 - c \)。分位数的具体取值有两种常见处理方法:

方法 计算公式 说明 适用场景
位置索引法 \( k = \lfloor \alpha \times N \rfloor \) 取排序后第k个收益率作为VaR 样本量较大时快速近似
线性插值法 \( k = \alpha \times N \),在相邻两个收益率之间线性插值 更精确,避免离散误差 样本量较小或监管要求精确
💡 实践建议: 当滚动窗口长度为250天(约1个交易日年)时,95% VaR对应第12.5个最差收益率(250×5%)。使用线性插值法在排序后的第12和第13个收益率之间取值,结果更平滑。

4.3 滚动窗口计算机制

滚动窗口(Rolling Window)是历史模拟法在时间序列上的应用方式。它确保每次计算只使用最新的固定长度历史数据,随着时间推移不断更新样本。

4.3.1 滚动窗口参数

参数 典型值 影响
窗口长度 \( W \) 250天(1年)、500天(2年) 越长越稳定,但对市场变化反应越慢
滚动步长 \( S \) 1天(每日滚动)、5天(周度滚动) 步长越小,VaR序列越平滑
样本起始点 固定起点或滑动起点 固定起点用于回测,滑动起点用于实时监控

4.3.2 滚动计算过程

以下SVG图展示了滚动窗口在时间轴上的移动过程,帮助理解“窗口滑动、数据更新、VaR重算”的循环机制。

滚动窗口计算机制(时间轴视角) t₁ t₂ t₃ t₄ t₅ tₙ 窗口1(第1~250天) 窗口2(第2~251天) 窗口3(第3~252天) 滚动规则 每次剔除最早1天 加入最新1天 窗口长度保持W不变

4.4 Python实现示例

下面给出一个完整的历史模拟法VaR计算Python实现。代码包含数据生成、滚动窗口计算、分位数选取和结果可视化。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# ==================== 1. 生成模拟收益率数据 ====================
np.random.seed(42)
n_days = 1000
returns = np.random.normal(loc=0.0005, scale=0.015, size=n_days)  # 日收益率,均值0.05%,标准差1.5%

# ==================== 2. 历史模拟法VaR计算函数 ====================
def historical_var(returns, window=250, confidence=0.95):
    """
    滚动窗口历史模拟法VaR计算
    
    参数:
        returns: 收益率序列(numpy数组)
        window: 滚动窗口长度
        confidence: 置信水平(如0.95表示95% VaR)
    
    返回:
        var_series: 每日VaR值序列(长度 = len(returns) - window + 1)
    """
    alpha = 1 - confidence
    n = len(returns)
    var_series = []
    
    for i in range(window, n + 1):
        # 提取当前窗口数据
        window_data = returns[i - window:i]
        # 升序排序
        sorted_returns = np.sort(window_data)
        # 计算分位数位置(线性插值法)
        k = alpha * window
        k_low = int(np.floor(k))
        k_high = int(np.ceil(k))
        # 边界保护
        if k_low < 0:
            k_low = 0
        if k_high >= window:
            k_high = window - 1
        # 线性插值
        if k_low == k_high:
            var_value = sorted_returns[k_low]
        else:
            frac = k - k_low
            var_value = sorted_returns[k_low] * (1 - frac) + sorted_returns[k_high] * frac
        var_series.append(var_value)
    
    return np.array(var_series)

# ==================== 3. 执行计算 ====================
var_95 = historical_var(returns, window=250, confidence=0.95)
var_99 = historical_var(returns, window=250, confidence=0.99)

# ==================== 4. 结果展示 ====================
print("=" * 60)
print("历史模拟法VaR计算结果(滚动窗口=250天)")
print("=" * 60)
print(f"95% VaR(均值): {np.mean(var_95):.4f}  ({np.mean(var_95)*100:.2f}%)")
print(f"95% VaR(标准差): {np.std(var_95):.4f}")
print(f"99% VaR(均值): {np.mean(var_99):.4f}  ({np.mean(var_99)*100:.2f}%)")
print(f"99% VaR(标准差): {np.std(var_99):.4f}")
print(f"VaR序列长度: {len(var_95)} 天")
print("=" * 60)

# ==================== 5. 可视化 ====================
plt.figure(figsize=(12, 6))
plt.plot(var_95, label='95% VaR', color='#e74c3c', linewidth=1.5)
plt.plot(var_99, label='99% VaR', color='#9b59b6', linewidth=1.5)
plt.axhline(y=np.mean(var_95), color='#e74c3c', linestyle='--', alpha=0.5)
plt.axhline(y=np.mean(var_99), color='#9b59b6', linestyle='--', alpha=0.5)
plt.title('历史模拟法VaR滚动计算结果', fontsize=14, fontweight='bold')
plt.xlabel('时间(天)', fontsize=12)
plt.ylabel('VaR(日收益率)', fontsize=12)
plt.legend(loc='best')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
📊 代码输出解读:
  • 95% VaR均值:约 -0.0245(-2.45%),表示在95%置信水平下,未来1天最大可能亏损不超过2.45%。
  • 99% VaR均值:约 -0.0348(-3.48%),比95% VaR更保守,反映尾部风险更大。
  • VaR序列波动:随着市场环境变化,VaR值会上下波动,体现风险水平的时变性。

4.5 关键参数对比与选择建议

参数 选项 优点 缺点 推荐场景
窗口长度 250天 vs 500天 250天更敏感;500天更稳定 250天噪声大;500天反应慢 日常风控用250天;监管报送用500天
分位数方法 位置索引 vs 线性插值 位置索引简单;插值更精确 索引法有离散误差;插值法计算略复杂 快速估算用索引;精确计算用插值
置信水平 95% vs 99% 95%更稳定;99%更保守 95%低估尾部;99%数据需求更大 内部风控用95%;监管资本用99%
⚠️ 历史模拟法局限性:
  • 假设历史会重演:如果市场结构发生突变(如2008年金融危机),历史数据无法反映新风险。
  • 窗口长度选择困境:窗口太短样本不足,太长则包含过时信息。
  • 无法处理极端事件:如果历史样本中从未出现过极端损失,VaR会低估真实风险。
在实际应用中,建议结合加权历史模拟法或蒙特卡洛模拟进行交叉验证。

4.6 本章小结

  • 历史模拟法的核心是“用过去N天的收益率分布代表未来1天的可能损益”。
  • 排序+分位数是计算的关键步骤,线性插值法比分位索引法更精确。
  • 滚动窗口机制确保VaR随市场变化动态更新,窗口长度通常设为250天。
  • Python实现中,通过 np.sort() 排序和线性插值公式即可高效计算。
  • 该方法简单透明、易于监管沟通,但对市场突变不敏感,需配合其他方法使用。