4、历史模拟法:非参数方法的原理、排序与分位数计算、滚动窗口与衰减因子、优缺点分析、与参数法的对比
历史模拟法,说白了就是「让历史替我们回答」。
我刚开始做风控那会儿,领导让我算一个股票组合的VaR。当时手头数据不多,参数法算出来总觉得心里没底。后来一个老同事跟我说:「你试试历史模拟法,简单粗暴,但有时候比那些花里胡哨的模型管用。」
嗯,这一试,就是好几年。
4.1 非参数方法的原理
历史模拟法的核心思想很简单:过去发生过的损失,未来也可能发生。
它不假设收益率服从任何分布(正态、t分布什么的统统不要),直接拿历史数据说话。你想想看,市场暴跌的时候,收益率分布往往有厚尾,参数法很容易低估风险。但历史模拟法不会——只要历史上出现过,它就能捕捉到。
核心逻辑:
- 收集过去N天的收益率数据
- 对收益率进行排序(从最差到最好)
- 取第5%分位数(95%置信水平)或第1%分位数(99%置信水平)
- 这个分位数就是VaR值
举个例子。假设我们有过去500天的日收益率数据,要算95%置信水平的VaR。那就把500个收益率从小到大排好,第25个(500×5%)就是VaR值。简单吧?
我的经验:历史模拟法对数据质量要求很高。我曾经遇到过数据里混入了「异常值」——比如某天因为数据录入错误,收益率显示-99%。如果不做清洗,VaR直接崩掉。所以,数据预处理这一步千万别省。
4.2 排序与分位数计算
排序这一步,看起来简单,但细节里藏着坑。
假设我们有1000个历史收益率,要算99% VaR。理论上应该取第10个(1000×1%)最差的收益率。但问题是:如果数据量不是整数倍怎么办?
常见的做法有两种:
- 位置法:直接取第⌈n×(1-α)⌉个值(向上取整)
- 插值法:在两个相邻值之间做线性插值
我个人习惯用插值法。为什么?因为位置法有时候会「跳变」——数据稍微多一点或少一点,VaR值可能突然变化。插值法平滑一些,结果更稳定。
# Python示例:历史模拟法计算VaR
import numpy as np
def historical_var(returns, confidence_level=0.95):
"""
历史模拟法计算VaR
returns: 历史收益率数组
confidence_level: 置信水平,默认95%
"""
sorted_returns = np.sort(returns)
index = int(np.ceil(len(returns) * (1 - confidence_level))) - 1
var = -sorted_returns[index] # VaR通常取正值
return var
# 示例数据
np.random.seed(42)
returns = np.random.normal(0, 0.02, 1000) # 模拟1000天收益率
var_95 = historical_var(returns, 0.95)
print(f"95% VaR: {var_95:.4f}")
注意:排序时一定要确认方向。收益率从最差到最好排,VaR取的是最差的那一端。我曾经见过有人排反了,算出来的VaR是负的——那其实是收益,不是风险。
4.3 滚动窗口与衰减因子
历史模拟法有个天然的问题:该用多长的历史窗口?
窗口太短(比如100天),样本量不够,分位数估计不稳定。窗口太长(比如1000天),早期的数据可能已经不能反映当前市场状况了。
常见的做法是滚动窗口:固定窗口长度(比如250个交易日,约一年),每天滚动更新。
但这里有个进阶技巧——衰减因子。
衰减因子的思路是:越近的数据,权重越大;越远的数据,权重越小。这样既保留了足够的历史数据,又让模型对近期变化更敏感。
# 带衰减因子的历史模拟法
def weighted_historical_var(returns, confidence_level=0.95, lambda_factor=0.97):
"""
带指数衰减权重的历史模拟法
lambda_factor: 衰减因子,通常取0.94-0.99
"""
n = len(returns)
weights = np.array([(1 - lambda_factor) * (lambda_factor ** (n - 1 - i))
for i in range(n)])
weights = weights / weights.sum() # 归一化
# 按收益率排序,同时调整权重顺序
sorted_indices = np.argsort(returns)
sorted_weights = weights[sorted_indices]
sorted_returns = returns[sorted_indices]
# 计算累积权重,找到分位点
cum_weights = np.cumsum(sorted_weights)
target_weight = 1 - confidence_level
idx = np.searchsorted(cum_weights, target_weight)
var = -sorted_returns[idx]
return var
避坑指南:衰减因子λ的取值很关键。λ=0.94时,半衰期约11天;λ=0.97时,半衰期约23天。我一般建议:如果是做日频风控,λ取0.94-0.97之间;如果是做周频或月频,λ可以取大一点。具体选多少?回测一下,选历史表现最好的那个。
4.4 优缺点分析
历史模拟法不是万能的。我用它这么多年,总结一下它的优缺点。
| 优点 | 缺点 |
|---|---|
| 无需假设分布,能捕捉厚尾 | 对历史数据依赖性强,数据量要求大 |
| 实现简单,容易理解 | 对市场结构变化不敏感(比如突然换了交易规则) |
| 可以处理非线性风险(如期权) | 计算效率低(每次都要排序) |
| 结果直观,容易向管理层解释 | 极端事件外推能力差(没发生过的事,算不出来) |
我记得有一次,某只股票因为财报造假,一天跌了80%。历史模拟法完全没捕捉到这个风险——因为过去500天里,最大跌幅才15%。这就是历史模拟法的局限性:它只能告诉你「历史上发生过的最坏情况」,但无法告诉你「未来可能发生但还没发生过的情况」。
4.5 与参数法的对比
参数法(比如方差-协方差法)和历史模拟法,是VaR计算的两大流派。我经常被问到:「到底该用哪个?」
我的回答是:看场景。
| 对比维度 | 参数法 | 历史模拟法 |
|---|---|---|
| 分布假设 | 需要(通常假设正态分布) | 不需要 |
| 计算速度 | 快(O(n)) | 慢(O(n log n)) |
| 厚尾处理 | 差(正态分布低估厚尾) | 好(只要历史数据中有) |
| 数据需求 | 少(几十个样本即可) | 多(至少250个样本) |
| 极端事件预测 | 差(正态分布尾部太薄) | 有限(只能预测已发生过的) |
| 实现复杂度 | 中等(需要估计协方差矩阵) | 低(排序即可) |
我个人习惯是:日常监控用历史模拟法,压力测试用参数法。为什么?因为历史模拟法更贴近实际,而参数法可以模拟「极端但尚未发生」的场景——比如假设波动率突然翻倍。
一句话总结:历史模拟法告诉你「过去最差的情况是什么」,参数法告诉你「如果市场疯了,最差可能是什么」。两者结合,才是完整的风险管理。
4.6 知识体系图
下面这张图,是我自己梳理的历史模拟法知识体系。每次做培训,我都会先画一遍这张图——它帮你把零散的知识点串起来。
这张图把历史模拟法的核心脉络都串起来了。从上到下,从左到右,你可以看到:原理是什么、怎么算、窗口怎么选、优缺点在哪、和参数法怎么对比。
嗯,内容就这些。历史模拟法虽然简单,但用好它并不容易。数据清洗、窗口选择、衰减因子调参——每一步都有坑。我踩过的坑,上面都提到了,希望能帮你少走弯路。
公众号:蓝海资料掘金营,微信deep3321