第四章 历史模拟法VaR:非参数方法、历史数据重采样、排序与分位数计算
说到VaR计算,很多新手第一反应就是“正态分布假设”。但说实话,金融市场哪有那么乖?我见过太多肥尾分布的例子了——2008年、2020年,哪次黑天鹅事件不是把正态分布的脸打得啪啪响?
所以这一章,我们来聊聊历史模拟法。它不假设任何分布,完全靠历史数据说话。说白了,就是“历史会重演”这个朴素逻辑。
4.1 历史模拟法的核心思想
历史模拟法(Historical Simulation)是非参数方法里最经典的一种。它不做任何分布假设,直接拿过去N天的收益率数据,排序后取分位数。
举个例子:你手里有过去1000天的日收益率数据。想算95%置信水平下的VaR?那就把这1000个收益率从小到大排个序,取第50个(也就是5%分位数)的值。就这么简单粗暴。
为什么我喜欢这个方法? 因为它在项目中真的省心。有一次我给某券商做压力测试,对方风控总监上来就问:“你们用的是什么分布?”我说“历史模拟法”,他愣了一下,然后说“好,这个我们审计能过”。你看,监管机构其实也认可这种透明的方法。
核心公式:
VaRα = -Percentile(R1, R2, ..., RN, 1-α)
其中Ri是历史收益率,α是置信水平(如95%),N是历史窗口长度。
4.2 数据重采样:别让坏数据毁了你的模型
历史模拟法有个致命弱点:它对数据质量极其敏感。我踩过这个坑——有一次用某交易所的日频数据,结果发现里面有三天连续停牌的数据没处理,算出来的VaR直接偏了20%。
所以数据重采样这一步,我建议你认真对待。常见的做法有:
- 等间隔重采样:把不规则的时间序列变成固定频率(比如日频、周频)
- 滚动窗口重采样:用过去250个交易日作为窗口,每天滚动计算
- Bootstrap重采样:有放回地抽取历史数据,生成新的样本路径
我的经验: 做Bootstrap时,记得保持时间序列的自相关性。我曾经直接随机打乱数据,结果VaR被严重低估了。后来改用“块状Bootstrap”(Block Bootstrap),才解决了这个问题。
4.3 排序与分位数计算:Python实现
好了,理论说完了,咱们直接上代码。这部分我习惯用NumPy和Pandas,效率高、代码也干净。
import numpy as np
import pandas as pd
# 模拟生成1000天的收益率数据(实际项目中从数据库读取)
np.random.seed(42)
returns = np.random.normal(0.001, 0.02, 1000)
# 历史模拟法计算VaR
def historical_var(returns, confidence_level=0.95):
"""
历史模拟法VaR计算
:param returns: 收益率序列
:param confidence_level: 置信水平,默认95%
:return: VaR值(正数表示损失)
"""
sorted_returns = np.sort(returns)
index = int((1 - confidence_level) * len(sorted_returns))
var = -sorted_returns[index]
return var
# 计算95% VaR
var_95 = historical_var(returns, 0.95)
print(f"95% VaR (历史模拟法): {var_95:.4f}")
# 计算99% VaR
var_99 = historical_var(returns, 0.99)
print(f"99% VaR (历史模拟法): {var_99:.4f}")
这段代码看着简单,但有几个细节要注意:
- 排序方向:我习惯从小到大排,这样取左边尾部分位数就是损失
- 索引计算:95%置信水平对应5%分位数,所以索引是 (1-0.95)*N
- 负号处理:VaR通常表示为正数,所以收益率是负值时要取反
曾经踩过的坑: 有一次我直接用Pandas的quantile()函数,没注意它默认是线性插值。对于小样本数据,线性插值会平滑掉极端值,导致VaR被低估。后来我改用numpy.percentile()并设置method='lower',才得到正确结果。
4.4 滚动窗口VaR:动态风险监控
静态VaR只能告诉你“过去N天的风险水平”,但市场是动态的。我建议你做一个滚动窗口VaR,每天更新。
def rolling_historical_var(returns, window=250, confidence_level=0.95):
"""
滚动窗口历史模拟法VaR
:param returns: 收益率序列
:param window: 滚动窗口大小,默认250个交易日(约1年)
:param confidence_level: 置信水平
:return: 每日VaR序列
"""
var_series = []
for i in range(window, len(returns)):
window_returns = returns[i-window:i]
var = historical_var(window_returns, confidence_level)
var_series.append(var)
return pd.Series(var_series, index=returns.index[window:])
# 计算滚动VaR
rolling_var = rolling_historical_var(returns, window=250, confidence_level=0.95)
print(f"滚动VaR均值: {rolling_var.mean():.4f}")
print(f"滚动VaR最大值: {rolling_var.max():.4f}")
你想想看,如果滚动VaR突然飙升,说明市场波动在加剧。这时候就该启动压力测试了。
4.5 历史模拟法的优缺点
| 优点 | 缺点 |
|---|---|
| 无需分布假设,完全基于数据 | 对数据质量要求极高 |
| 实现简单,代码量少 | 无法捕捉未发生过的极端事件 |
| 监管机构认可度高 | 历史窗口长度选择主观性强 |
| 可以捕捉非线性风险 | 对市场结构变化不敏感 |
我的建议: 历史模拟法适合作为基准模型。在实际项目中,我通常用它做第一道防线,再配合参数法或蒙特卡洛模拟做交叉验证。这样既能保证透明度,又能覆盖极端风险。
4.6 知识体系图
下面这张图展示了本章的核心逻辑。从原始数据开始,经过重采样、排序、分位数计算,最终得到VaR值。每一步都有坑,每一步也都有优化空间。
嗯,这张图把整个流程串起来了。你从左边开始,一步步走到右边,每一步都有对应的Python代码实现。我个人建议你把这张图打印出来贴在工位上,写代码时对照着看,不容易出错。
实战小技巧: 在做历史模拟法时,我习惯同时计算多个置信水平(90%、95%、99%)的VaR,然后画成曲线图。如果三条曲线突然发散,说明市场结构可能发生了变化,这时候就要警惕了。
好了,历史模拟法就讲到这里。它虽然简单,但却是很多金融机构的标配。下一章我们会讲参数法VaR,到时候你会看到,正态分布假设到底有多大的局限性。