历史模拟法(一):原理与步骤、数据准备与排序

各位同学,今天我们正式进入历史模拟法的世界。

说实话,在所有的VaR计算方法里,历史模拟法是我个人最偏爱的一种。为什么?因为它简单、直观,而且不需要你对市场分布做任何假设。你想想看,很多模型都在假设收益率服从正态分布,但真实市场哪有那么乖?

我在项目中遇到过好几次,用参数法算出来的VaR漂亮得很,结果一遇到极端行情,模型直接崩了。反而是历史模拟法,虽然看起来有点「笨」,但关键时刻特别靠谱。

历史模拟法的核心思想

说白了,历史模拟法就一句话:过去发生过的,未来也可能发生

我们不假设收益率服从什么分布,也不去估计均值方差。我们直接拿过去N天的实际收益率数据,排序,然后取第p百分位数作为VaR值。

举个例子:

假设你有过去1000天的日收益率数据。你想算95%置信水平下的VaR。那你把这1000个收益率从小到大排序,取第50个(也就是5%分位点)的数值。这个数值就是VaR。

核心公式:

VaR(α) = -Percentile(R₁, R₂, ..., Rₙ, 1-α)

其中R是历史收益率序列,α是置信水平

嗯,这里要注意:我们通常取负值,因为VaR表示的是损失。如果第5%分位点的收益率是-2.3%,那VaR就是2.3%。

数据准备:这一步做不好,后面全白搭

我在做风控系统的时候,见过太多人一上来就急着算VaR,结果数据都没洗干净。我建议你按照以下步骤来:

第一步:确定时间窗口

时间窗口选多大?这其实是个trade-off。

  • 窗口太小(比如250天):样本量不够,尾部估计不稳定
  • 窗口太大(比如5年):太久远的数据可能已经不能反映当前市场结构

我个人习惯用500-1000个交易日,大概2-4年的数据。当然,如果你做的是高频交易,可能用更短的窗口。

第二步:数据清洗

这一步我吃过不少亏。曾经有一次,数据里混入了停牌日的收益率(全是0),结果VaR被严重低估。从那以后,我养成了几个习惯:

避坑指南:

  • 检查缺失值:停牌日、节假日的数据要处理
  • 检查异常值:数据录入错误导致的极端值要剔除
  • 检查复权问题:股票分红、送股要复权处理
  • 检查时间对齐:不同资产的时间戳要统一

第三步:计算收益率

通常我们用对数收益率:

# Python代码示例
import numpy as np
import pandas as pd

def calculate_log_returns(prices):
    """
    计算对数收益率
    prices: DataFrame, 列名为资产名称,行为日期
    """
    # 我习惯用shift(1)来对齐
    log_returns = np.log(prices / prices.shift(1))
    # 删除第一行的NaN
    log_returns = log_returns.dropna()
    return log_returns

# 读取数据
prices = pd.read_csv('asset_prices.csv', index_col=0, parse_dates=True)
returns = calculate_log_returns(prices)
print(f"收益率数据形状: {returns.shape}")
print(f"时间范围: {returns.index[0]} 到 {returns.index[-1]}")

排序:最朴素也最有效的方法

数据准备好了,接下来就是排序。这一步没什么技术含量,但有个细节要注意。

假设我们算的是单资产VaR,那很简单:

def historical_var(returns, confidence_level=0.95):
    """
    历史模拟法计算VaR
    returns: 收益率序列(一维数组)
    confidence_level: 置信水平,默认95%
    """
    # 排序
    sorted_returns = np.sort(returns)
    
    # 计算分位点位置
    n = len(sorted_returns)
    index = int(np.ceil(n * (1 - confidence_level)))
    
    # 取对应位置的收益率
    var = -sorted_returns[index - 1]  # 取负值表示损失
    
    return var

# 使用示例
var_95 = historical_var(returns['股票A'], 0.95)
print(f"95%置信水平下的VaR: {var_95:.4f} 或 {var_95*100:.2f}%")

这里有个小坑:分位点的位置怎么取?

我见过有人用 int(n * (1 - confidence_level)),也有人用 ceil。我个人建议用 ceil,因为这样更保守一些。你想想看,如果n=1000,95%置信水平,那我们要取第50个最小的值。用 ceil 保证我们至少取到第50个,而不是第49个。

多资产组合的历史模拟

单资产太简单了,我们来看看组合的情况。

对于投资组合,历史模拟法的思路是一样的:

  1. 计算每个资产的历史收益率
  2. 用当前权重加权得到组合的历史收益率序列
  3. 排序,取分位点
def portfolio_historical_var(returns, weights, confidence_level=0.95):
    """
    投资组合的历史模拟VaR
    returns: DataFrame, 每列是一个资产的收益率
    weights: 数组, 各资产的权重
    confidence_level: 置信水平
    """
    # 计算组合的历史收益率
    portfolio_returns = returns.dot(weights)
    
    # 排序并计算VaR
    sorted_returns = np.sort(portfolio_returns)
    n = len(sorted_returns)
    index = int(np.ceil(n * (1 - confidence_level)))
    var = -sorted_returns[index - 1]
    
    return var

# 假设我们有3个资产,权重分别为0.4, 0.3, 0.3
weights = np.array([0.4, 0.3, 0.3])
portfolio_var = portfolio_historical_var(returns[['股票A', '股票B', '股票C']], weights)
print(f"组合95% VaR: {portfolio_var:.4f}")

经验之谈:

我在做组合VaR时,发现一个有趣的现象:如果组合里资产的相关性很低,历史模拟法算出来的VaR往往比参数法更准确。因为参数法假设相关性是稳定的,但实际中相关性会变。历史模拟法直接用了历史数据中的相关性模式,反而更真实。

核心逻辑流程图

下面我用一张图来总结历史模拟法的完整流程:

历史模拟法核心流程 步骤1:获取历史价格数据 步骤2:数据清洗与复权 步骤3:计算对数收益率 步骤4:收益率从小到大排序 步骤5:取第(1-α)分位点 关键要点 • 时间窗口:500-1000天 • 收益率类型:对数收益率 • 排序方式:升序排列 • 分位点取法:ceil向上取整 • VaR = -收益率(分位点) 常见错误 1. 未处理缺失值 2. 未复权导致收益率偏差 3. 分位点索引计算错误 4. 忘记取负值 5. 窗口选择不合理

一个完整的例子

我们来跑一个完整的例子,从数据到结果:

import yfinance as yf
import numpy as np
import pandas as pd
from datetime import datetime, timedelta

# 下载数据
ticker = 'AAPL'
end_date = datetime.now()
start_date = end_date - timedelta(days=1000)  # 约4年数据

data = yf.download(ticker, start=start_date, end=end_date)
prices = data['Adj Close']  # 使用复权价格

# 计算收益率
returns = np.log(prices / prices.shift(1)).dropna()

# 计算不同置信水平的VaR
confidence_levels = [0.90, 0.95, 0.99]
for cl in confidence_levels:
    var = historical_var(returns.values, cl)
    print(f"{cl*100:.0f}% VaR: {var*100:.2f}%")

# 输出结果示例:
# 90% VaR: 2.15%
# 95% VaR: 3.08%
# 99% VaR: 5.42%

看到没?随着置信水平提高,VaR值也在增大。99%的VaR是5.42%,意味着在99%的情况下,单日最大损失不会超过5.42%。但反过来,还有1%的可能性损失会更大。

重要提醒:

历史模拟法有个隐含假设:历史会重演。但真实市场里,黑天鹅事件往往是没有历史先例的。所以历史模拟法在正常市场环境下表现很好,但在极端行情下可能会低估风险。

我个人的做法是:用历史模拟法做日常风控,同时搭配压力测试来应对极端情况。两者结合,效果更好。

好了,这一章的内容就到这里。历史模拟法的原理其实不复杂,但数据准备和细节处理才是真正的功夫。下一章我们会深入讨论历史模拟法的优缺点,以及如何改进它。


公众号:蓝海资料掘金营,微信deep3321