4. 历史波动率模型:简单移动平均(SMA)波动率、指数加权移动平均(EWMA)波动率、参数选择与对比
波动率这东西,说白了就是资产价格的「心跳」。你想想看,一个股票每天上下蹦跶1%,跟一个每天只动0.1%的股票,风险完全不是一个量级。历史波动率模型,就是通过过去的数据,来估算这个「心跳」有多剧烈。
我个人习惯把历史波动率模型分成两类:一类是「一视同仁」的简单移动平均(SMA),另一类是「喜新厌旧」的指数加权移动平均(EWMA)。这两兄弟各有各的脾气,咱们今天就来好好盘一盘。
4.1 简单移动平均(SMA)波动率
SMA波动率,是最朴素、最直观的方法。它的逻辑很简单:取过去N天的收益率,算个标准差,然后年化一下。每一天的权重都一样,谁也不比谁特殊。
公式长这样:
σ_SMA = sqrt( (1/(N-1)) * Σ (r_t - r̄)² ) * sqrt(252)
其中 r_t 是每日对数收益率,r̄ 是N天内的平均收益率,252是年化交易日数。
嗯,这里要注意:为什么用N-1而不是N?因为样本标准差的无偏估计要用N-1。我在项目中遇到过有人直接用N,结果算出来的波动率总是偏小一点点,虽然差别不大,但做高频策略时这点误差就够你喝一壶的。
代码实现也很直接:
import numpy as np
import pandas as pd
def sma_volatility(returns, window=20):
"""
计算简单移动平均波动率
returns: 日收益率序列
window: 滚动窗口大小
"""
# 滚动计算标准差,ddof=1 表示使用样本标准差
rolling_std = returns.rolling(window=window, ddof=1).std()
# 年化处理
annualized_vol = rolling_std * np.sqrt(252)
return annualized_vol
# 示例
np.random.seed(42)
daily_returns = pd.Series(np.random.randn(500) * 0.02) # 模拟日收益率
sma_vol = sma_volatility(daily_returns, window=20)
print(f"SMA波动率(最后一天): {sma_vol.iloc[-1]:.4f}")
SMA最大的优点是简单、透明。你一眼就能看明白它是怎么算的。但缺点也很明显:它对所有数据一视同仁。5天前的数据和昨天的数据权重一样,这合理吗?
我曾经在做一个股指期货的波动率交易策略时,发现SMA对突发事件的反应特别迟钝。比如某天出了个黑天鹅,波动率瞬间飙升,但SMA要等到这个极端数据慢慢「滚出」窗口后,波动率才会降下来。这中间的时间差,足够让你亏掉底裤。
4.2 指数加权移动平均(EWMA)波动率
EWMA的出现,就是为了解决SMA「一视同仁」的问题。它的核心思想是:越近的数据,权重越大;越远的数据,权重越小。权重按指数衰减,所以叫指数加权。
公式如下:
σ²_t = λ * σ²_{t-1} + (1 - λ) * r²_{t-1}
其中 λ 是衰减因子(0 < λ < 1),r_{t-1} 是上一期的收益率。你看,这个公式是递归的,当前方差由上一期方差和上一期收益率共同决定。
你想想看,λ 越大,历史数据衰减得越慢,模型越「平滑」;λ 越小,模型越「敏感」,对近期数据反应越快。
代码实现:
def ewma_volatility(returns, lambda_=0.94):
"""
计算指数加权移动平均波动率
returns: 日收益率序列
lambda_: 衰减因子,通常取0.94或0.97
"""
# 初始化方差序列
variance = np.zeros(len(returns))
# 初始方差用前几个样本的方差
variance[0] = returns.iloc[:20].var()
# 递归计算
for t in range(1, len(returns)):
variance[t] = lambda_ * variance[t-1] + (1 - lambda_) * returns.iloc[t-1]**2
# 标准差并年化
volatility = np.sqrt(variance) * np.sqrt(252)
return pd.Series(volatility, index=returns.index)
# 示例
ewma_vol = ewma_volatility(daily_returns, lambda_=0.94)
print(f"EWMA波动率(最后一天): {ewma_vol.iloc[-1]:.4f}")
EWMA在金融界有个经典参数:λ=0.94。这是RiskMetrics(摩根大通的风险管理系统)当年用的默认值。为什么是0.94?
我记得有次跟一个老交易员聊天,他说这个参数是「用钱试出来的」。0.94意味着半衰期大约是11天(半衰期 = ln(0.5)/ln(λ)),也就是说,11天前的数据权重只有最新数据的一半。这个衰减速度,对大多数金融时间序列来说,刚刚好。
4.3 参数选择与对比
好了,两个模型都讲完了。现在问题来了:到底用哪个?参数怎么选?
我画了一张图,帮你理清思路:
从这张图你可以看到,选择哪个模型,取决于你的数据特征和交易目标。
我整理了一个对比表格,方便你快速决策:
| 对比维度 | SMA | EWMA |
|---|---|---|
| 权重分配 | 等权重 | 指数衰减权重 |
| 对近期数据敏感度 | 低(所有数据一视同仁) | 高(近期数据权重更大) |
| 对异常值的反应 | 迟钝(异常值在窗口内持续影响) | 快速(异常值权重迅速衰减) |
| 参数数量 | 1个(窗口N) | 1个(衰减因子λ) |
| 计算复杂度 | 低(直接滚动计算) | 中(递归计算) |
| 金融行业默认参数 | N=20(一个月交易日) | λ=0.94(RiskMetrics标准) |
| 适合的数据频率 | 日线、周线 | 日线、高频数据 |
最后,关于参数选择,我有个小建议:不要迷信默认参数。我在项目中遇到过好几次,用0.94的λ跑出来的结果还不如自己调参的SMA。为什么?因为不同的资产、不同的市场环境,对波动率的「记忆长度」是不一样的。
比如,大盘蓝筹股的波动率相对稳定,λ可以取大一点(0.97左右);而小盘妖股的波动率变化剧烈,λ取小一点(0.90左右)才能跟上节奏。这个道理,你想想看,是不是很直观?
嗯,关于历史波动率模型,今天就聊到这儿。记住一句话:模型是死的,市场是活的。别让参数把你框死了。