第四章:指数加权移动平均(EWMA)波动率模型
波动率这东西,说白了就是市场的心跳。你想想看,做量化投资,如果连市场心跳都摸不准,那策略再花哨也是白搭。今天我要聊的EWMA,就是捕捉这个心跳最经典的工具之一。
4.1 EWMA的原理:为什么是“加权”?
传统的简单移动平均(SMA)有个问题——它给过去每一天同样的权重。这合理吗?我个人觉得不太合理。上周五的暴跌对今天的影响,跟三个月前某天的波动,能一样吗?
EWMA的核心思想很简单:越近的数据,权重越大;越远的数据,权重越小。而且这个权重是按指数形式衰减的,所以叫“指数加权”。
数学上,EWMA波动率的递推公式长这样:
σ²_t = λ * σ²_{t-1} + (1 - λ) * r²_{t-1}
其中:
- σ²_t 是今天的方差估计
- σ²_{t-1} 是昨天的方差估计
- r²_{t-1} 是昨天的收益率平方
- λ 是衰减因子(0 < λ < 1)
嗯,这里要注意:这个公式是递推的。也就是说,今天的方差依赖于昨天的方差,昨天的方差又依赖于前天的...一直追溯到初始值。我在项目中遇到过一个问题——如果初始值设得不对,前几百个数据基本是废的。后面我会讲怎么处理。
4.2 λ参数的选择:0.94是金标准吗?
聊到λ,就绕不开RiskMetrics。摩根大通在1994年发布的技术文档里,推荐日频数据用λ=0.94。这个数字后来成了行业基准。
为什么是0.94?说白了,它对应着一个“半衰期”的概念。半衰期就是权重衰减到一半所需的天数:
半衰期 = ln(0.5) / ln(λ)
当λ=0.94时,半衰期大约是11天。这意味着10天前的数据,权重只剩下一半左右。我个人习惯用这个值做快速原型,但实盘时我会根据资产特性调整。
| λ值 | 半衰期(天) | 适用场景 |
|---|---|---|
| 0.94 | 约11天 | 日频数据,通用推荐 |
| 0.97 | 约23天 | 周频数据,趋势性资产 |
| 0.99 | 约69天 | 月频数据,长期波动 |
4.3 EWMA波动率计算的Python实现
好了,理论讲完,咱们直接上代码。我习惯用pandas的ewm方法,一行搞定:
import pandas as pd
import numpy as np
def ewma_volatility(returns, lambda_=0.94):
"""
计算EWMA波动率
returns: 收益率序列(pandas Series)
lambda_: 衰减因子,默认0.94
"""
# 计算平方收益率
squared_returns = returns ** 2
# 使用ewm计算指数加权方差
# adjust=False 保证使用递推公式
variance = squared_returns.ewm(alpha=1-lambda_, adjust=False).mean()
# 取平方根得到波动率
volatility = np.sqrt(variance)
return volatility
# 示例用法
# 假设我们有沪深300的日收益率数据
# returns = pd.Series([...])
# vol = ewma_volatility(returns)
# print(vol.tail())
这里有个细节:alpha=1-lambda_。因为pandas的ewm用的是alpha参数,而我们的公式里用的是lambda。两者关系是alpha = 1 - lambda。我刚开始用的时候也搞混过,后来干脆写了个注释贴在代码里。
min_periods参数。比如min_periods=20,这样前20天不会输出结果,避免初始值污染。
4.4 初始值处理:一个容易被忽视的坑
前面提到过,EWMA是递推的,初始值怎么设?
常见做法有两种:
- 用第一个样本的平方收益率作为初始方差——简单粗暴,但前几个估计值偏差大
- 用前N天的样本方差作为初始值——更稳健,但需要多等N天
我个人推荐第二种。在实盘系统中,我会用前60个交易日的样本方差作为初始值。为什么是60?差不多一个季度,够稳定了。
def ewma_volatility_robust(returns, lambda_=0.94, init_window=60):
"""
带稳健初始值的EWMA波动率
"""
# 用前init_window天的样本方差作为初始值
init_var = returns.iloc[:init_window].var()
# 构建完整的方差序列
variance = pd.Series(index=returns.index, dtype=float)
variance.iloc[init_window-1] = init_var
# 递推计算
for i in range(init_window, len(returns)):
variance.iloc[i] = lambda_ * variance.iloc[i-1] + (1-lambda_) * returns.iloc[i-1]**2
volatility = np.sqrt(variance)
return volatility
4.5 知识体系总览
下面这张图是我自己整理的EWMA知识框架,你可以把它当作学习路线图:
说实话,EWMA是我在量化投资中用得最多的波动率模型之一。它不像GARCH那么复杂,但效果往往不差。你想想看,一个参数就能控制整个模型的记忆长度,这种简洁性本身就是一种美。
最后提醒一句:EWMA假设收益率服从正态分布,但实际数据往往有厚尾特征。所以用EWMA估计出的波动率,在极端行情下可能会偏小。这时候,可以结合其他模型做修正,或者直接上GARCH。不过那是后面章节的内容了。
公众号:蓝海资料掘金营,微信deep3321