波动率建模(一):历史波动率、SMA与EWMA
各位同学,咱们今天开始聊波动率建模。说实话,波动率这东西,在量化风控里就是个绕不开的坎儿。你想想看,无论是定价、风控还是对冲,最后都得落到波动率头上。我个人习惯把波动率比作市场的「心跳」——太快了不行,太慢了也不行,关键是要摸清它的节奏。
这一章我们先从最基础的三个模型入手:历史波动率、简单移动平均(SMA)和指数加权移动平均(EWMA)。别小看它们,很多实战场景里,这些老家伙依然管用。
1. 历史波动率:最朴素的起点
历史波动率,说白了就是过去一段时间内,资产收益率的标准差。它假设未来的波动会像过去一样——嗯,这个假设当然很粗糙,但作为基准线,它很有用。
计算公式很简单:
σ_hist = std(ln(P_t / P_{t-1})) * sqrt(T)
其中 T 是年化因子(日数据用252,周数据用52)。
核心要点:历史波动率给每个观测值相同的权重。你想想看,30天前的数据和昨天的数据,权重一样。这合理吗?
我在项目中遇到过一件事:有一次做股指期货的对冲回测,用了60天的历史波动率。结果市场突然暴跌,波动率飙升,但模型反应慢了两天——因为那些「平静期」的数据还在拖后腿。这就是等权重的代价。
2. 简单移动平均(SMA):滑动窗口的思路
SMA 其实就是历史波动率的滑动窗口版本。我们固定一个窗口长度 N,每来一个新数据,就丢掉最老的那个,重新算一遍标准差。
公式长这样:
σ_SMA(t) = sqrt( (1/(N-1)) * Σ_{i=0}^{N-1} (r_{t-i} - μ)^2 ) * sqrt(T)
这里 N 是窗口大小,r 是日收益率。
实战建议:我个人习惯用 20 天 SMA 做短期波动率参考,60 天做中期。别用太短的窗口,比如 5 天——噪声太大,你根本分不清是信号还是噪音。
SMA 有个明显的毛病:窗口内的数据权重一样,窗口外的数据直接扔掉。这会导致波动率序列出现「跳跃」。举个例子,如果第 21 天刚好有一个极端收益率被移出窗口,波动率会突然下降——但市场其实没变。
我曾经吃过这个亏。2018年做期权波动率套利,用 SMA 做信号,结果频繁被这种「窗口边界效应」误导。后来我换成了 EWMA,情况好多了。
3. 指数加权移动平均(EWMA):让近期数据说了算
EWMA 的核心思想很简单:越近的数据,权重越大。而且权重是按指数衰减的,所以叫「指数加权」。
公式:
σ²_EWMA(t) = λ * σ²_EWMA(t-1) + (1-λ) * r²_{t-1}
其中 λ 是衰减因子,通常取 0.94(RiskMetrics 的标准推荐值)。
为什么 λ=0.94?这是 JP Morgan 在 90 年代做 RiskMetrics 时,通过对大量资产的历史数据拟合得到的。对于日数据,0.94 意味着半衰期约为 11 天——也就是说,11 天前的数据权重只有今天的一半。
你看,EWMA 只需要记住上一期的方差和今天的收益率,递归计算就行。内存占用极小,计算速度飞快。这在生产环境里特别重要——我当年在期货公司做实时风控系统时,几万个合约的波动率都是靠 EWMA 算的,毫秒级更新。
注意:EWMA 对初始值敏感。如果初始方差设得太高或太低,需要一段「预热期」才能稳定下来。我建议至少用 50 个数据点做预热。
4. 代码实战:Python 实现三种波动率
光说不练假把式。咱们直接上代码。下面这个例子用沪深300指数日数据,分别计算三种波动率。
import numpy as np
import pandas as pd
def historical_volatility(returns, window=252):
"""历史波动率:全样本标准差"""
return np.std(returns) * np.sqrt(252)
def sma_volatility(returns, window=20):
"""简单移动平均波动率"""
rolling_std = returns.rolling(window=window).std()
return rolling_std * np.sqrt(252)
def ewma_volatility(returns, lambda_=0.94):
"""指数加权移动平均波动率"""
# 初始化方差为样本方差
var = np.var(returns)
ewma_var = [var]
for r in returns[1:]:
var = lambda_ * var + (1 - lambda_) * r**2
ewma_var.append(var)
return np.sqrt(ewma_var) * np.sqrt(252)
# 模拟数据
np.random.seed(42)
dates = pd.date_range('2020-01-01', periods=500, freq='D')
returns = pd.Series(np.random.randn(500) * 0.02, index=dates)
# 计算
hv = historical_volatility(returns)
sma = sma_volatility(returns, window=20)
ewma = ewma_volatility(returns, lambda_=0.94)
print(f"历史波动率(全样本): {hv:.2%}")
print(f"SMA(20天)最新值: {sma.iloc[-1]:.2%}")
print(f"EWMA(λ=0.94)最新值: {ewma[-1]:.2%}")
一个小技巧:实际生产中,EWMA 的 λ 参数可以动态调整。比如在波动率突变期,临时降低 λ(比如降到 0.85),让模型更快适应新环境。我管这叫「自适应 EWMA」。
5. 三种模型的对比
| 特性 | 历史波动率 | SMA | EWMA |
|---|---|---|---|
| 权重分配 | 等权重(全样本) | 等权重(窗口内) | 指数衰减 |
| 对近期数据敏感度 | 低 | 中 | 高 |
| 计算复杂度 | O(n) | O(n) 每次 | O(1) 递归 |
| 内存占用 | 高(需全量数据) | 中(需窗口数据) | 低(仅需上一期) |
| 适用场景 | 长期基准 | 中期趋势 | 实时风控、高频 |
你看这个表格就清楚了。没有哪个模型是万能的。我个人在实战中的选择逻辑是这样的:
- 做策略回测:用历史波动率做基准,EWMA 做敏感性分析
- 做实时风控:只用 EWMA,速度快、反应及时
- 做期权定价:SMA 和 EWMA 结合,取两者的加权平均
一句话总结:历史波动率是「过去怎么样」,SMA 是「最近怎么样」,EWMA 是「现在怎么样」。从过去到现在,权重越来越集中在近期——这才是波动率建模的正确方向。
好了,这一章的内容就到这儿。三种模型虽然简单,但它们是所有复杂波动率模型(GARCH、随机波动率等)的基石。下一章我们会聊波动率微笑和偏斜——那才是真正有意思的地方。
公众号:蓝海资料掘金营,微信deep3321