第四章:指数加权移动平均(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天 月频数据,长期波动
避坑指南:我曾经在回测中直接用λ=0.94跑股指期货,结果发现波动率估计总是滞后。后来检查才发现,股指期货的日内波动结构跟股票不一样,需要把λ调到0.92左右才能跟上节奏。所以,别迷信0.94,要根据数据特征调参。

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是递推的,初始值怎么设?

常见做法有两种:

  1. 用第一个样本的平方收益率作为初始方差——简单粗暴,但前几个估计值偏差大
  2. 用前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
核心要点:EWMA的精髓在于“自适应”。市场波动大的时候,它快速跟上;市场平静的时候,它慢慢回归。这种特性让它特别适合做风险监控和动态调仓。

4.5 知识体系总览

下面这张图是我自己整理的EWMA知识框架,你可以把它当作学习路线图:

EWMA波动率模型知识体系 核心原理:指数加权递推 λ参数选择 Python计算实现 初始值处理 RiskMetrics推荐:λ=0.94 半衰期 = ln(0.5)/ln(λ) 根据资产特性调参 pandas.ewm方法 alpha = 1 - λ min_periods参数 方法1:首样本初始化 方法2:前N天样本方差 推荐:60天窗口 应用场景:风险监控、动态调仓、波动率择时

说实话,EWMA是我在量化投资中用得最多的波动率模型之一。它不像GARCH那么复杂,但效果往往不差。你想想看,一个参数就能控制整个模型的记忆长度,这种简洁性本身就是一种美。

最后提醒一句:EWMA假设收益率服从正态分布,但实际数据往往有厚尾特征。所以用EWMA估计出的波动率,在极端行情下可能会偏小。这时候,可以结合其他模型做修正,或者直接上GARCH。不过那是后面章节的内容了。


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