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要等到这个极端数据慢慢「滚出」窗口后,波动率才会降下来。这中间的时间差,足够让你亏掉底裤。

避坑指南: 我曾经因为SMA窗口选得太长(60天),导致策略在波动率突变时完全没跟上节奏。后来我改成20天窗口,效果好了不少。但窗口太短(比如5天)又容易过度反应。这个度,得根据你的交易频率来调。

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天前的数据权重只有最新数据的一半。这个衰减速度,对大多数金融时间序列来说,刚刚好。

个人经验: 我建议你在使用EWMA时,不要死磕0.94。对于高频数据(比如5分钟K线),λ可以取0.97甚至0.99,因为噪音更多,需要更平滑。对于日线数据,0.94是个不错的起点,但最好做一下回测验证。

4.3 参数选择与对比

好了,两个模型都讲完了。现在问题来了:到底用哪个?参数怎么选?

我画了一张图,帮你理清思路:

历史波动率模型选择决策树 历史波动率模型 SMA(简单移动平均) EWMA(指数加权) 适用场景 • 数据平稳,无明显趋势 • 需要简单透明的模型 适用场景 • 波动率聚集效应明显 • 需要快速响应市场变化 参数选择:窗口N 短窗口(10-20): 敏感 | 长窗口(60-120): 平滑 参数选择:衰减因子λ λ=0.94(日线) | λ=0.97(高频) | λ=0.99(超高频)

从这张图你可以看到,选择哪个模型,取决于你的数据特征和交易目标。

我整理了一个对比表格,方便你快速决策:

对比维度 SMA EWMA
权重分配 等权重 指数衰减权重
对近期数据敏感度 低(所有数据一视同仁) 高(近期数据权重更大)
对异常值的反应 迟钝(异常值在窗口内持续影响) 快速(异常值权重迅速衰减)
参数数量 1个(窗口N) 1个(衰减因子λ)
计算复杂度 低(直接滚动计算) 中(递归计算)
金融行业默认参数 N=20(一个月交易日) λ=0.94(RiskMetrics标准)
适合的数据频率 日线、周线 日线、高频数据
核心结论: 如果你做的是中长线投资,对波动率变化不敏感,SMA就够用了。但如果你做的是短线交易或期权做市,EWMA几乎是必须的——它能让你在波动率突变时第一时间做出反应。

最后,关于参数选择,我有个小建议:不要迷信默认参数。我在项目中遇到过好几次,用0.94的λ跑出来的结果还不如自己调参的SMA。为什么?因为不同的资产、不同的市场环境,对波动率的「记忆长度」是不一样的。

比如,大盘蓝筹股的波动率相对稳定,λ可以取大一点(0.97左右);而小盘妖股的波动率变化剧烈,λ取小一点(0.90左右)才能跟上节奏。这个道理,你想想看,是不是很直观?

嗯,关于历史波动率模型,今天就聊到这儿。记住一句话:模型是死的,市场是活的。别让参数把你框死了。

专注资料整理