4. 波动率估计方法(上):简单移动平均(SMA)、指数加权移动平均(EWMA)、GARCH(1,1)模型
做市策略里,波动率这东西,说白了就是市场的「脾气」。
脾气暴躁的时候,你价差得拉大,不然容易被抽脸。脾气温和的时候,价差收窄,多赚点周转的钱。我刚开始做做市那会儿,就吃过这个亏——固定价差跑了一整天,结果下午一波剧烈波动,库存全砸手里了。
所以,怎么实时、准确地估计波动率,是自适应调整的核心。今天咱们先聊三种最经典的方法:SMA、EWMA 和 GARCH(1,1)。
核心观点:波动率估计不是越复杂越好,关键看你的策略对「反应速度」和「平滑度」的取舍。
4.1 简单移动平均(SMA)—— 最朴素的估计
SMA 的思路很简单:取过去 N 期的收益率,算个标准差,完事。
公式长这样:
σ²_sma = (1/N) * Σ (r_t - μ)²
其中 r_t 是每期的对数收益率,μ 是均值。N 是窗口长度。
我个人习惯用 20 期做日内 SMA。为什么是 20?因为差不多对应一个月的交易天数,能过滤掉一些随机噪音。
小技巧:如果你做的是 tick 级数据,N 别选太大。我试过 100 期 SMA,结果波动率曲线平得像一条直线,完全跟不上行情变化。
代码实现也很直白:
import numpy as np
def sma_volatility(returns, window=20):
"""
简单移动平均波动率
returns: 收益率序列
window: 窗口大小
"""
rolling_std = np.zeros_like(returns)
for i in range(window, len(returns)):
rolling_std[i] = np.std(returns[i-window:i])
return rolling_std
优点:好理解,计算快,回测时用着很顺手。
缺点:所有数据权重一样。你想想看,10 天前的价格波动和 1 分钟前的波动,对当前的影响能一样吗?显然不能。但 SMA 偏要一视同仁。
注意:SMA 对异常值非常敏感。如果某根 K 线出现极端跳空,SMA 会在窗口期内一直「记住」这个异常,导致波动率被高估。我曾经因为这个在回测里亏了不少虚拟利润。
4.2 指数加权移动平均(EWMA)—— 给近期数据更多权重
EWMA 解决了 SMA 的痛点:近期的数据更重要。
公式:
σ²_t = λ * σ²_{t-1} + (1 - λ) * r²_{t-1}
λ 是衰减因子,通常取 0.94 或 0.97。λ 越大,历史数据衰减得越慢,曲线越平滑。
我建议高频做市用 λ=0.94。为什么?因为日内波动变化快,0.94 能让模型更快响应。如果是日线级别的策略,0.97 更合适。
def ewma_volatility(returns, lambda_=0.94):
"""
指数加权移动平均波动率
"""
sigma2 = np.zeros_like(returns)
sigma2[0] = returns[0]**2
for t in range(1, len(returns)):
sigma2[t] = lambda_ * sigma2[t-1] + (1 - lambda_) * returns[t-1]**2
return np.sqrt(sigma2)
实际效果对比:同样一段行情,SMA 的波动率曲线像被磨平了棱角,而 EWMA 能更敏锐地捕捉到波动率的突变。做市策略里,我更喜欢 EWMA,因为它能让我在波动率刚抬头时就调整价差。
不过 EWMA 也有坑。λ 的选取很主观。你想想看,0.94 和 0.97 之间差了 0.03,但长期运行下来,对策略收益的影响可能差好几个百分点。
避坑指南:我曾经在回测里用 EWMA 跑得风生水起,实盘一上线就崩了。后来发现是 λ 选得太小,模型对噪音过度反应。实盘时建议先用历史数据做参数扫描,找到最优 λ。
4.3 GARCH(1,1) 模型—— 让波动率自己「说话」
GARCH 比前两个要复杂一些。它假设今天的波动率不仅取决于昨天的收益率,还取决于昨天的波动率本身。
说白了,GARCH 认为波动率有「记忆」和「聚集」效应。大波动后面往往跟着大波动,小波动后面跟着小波动。这个特性在金融市场里非常常见。
GARCH(1,1) 的公式:
σ²_t = ω + α * r²_{t-1} + β * σ²_{t-1}
参数含义:
- ω:长期平均波动率的基准
- α:对近期收益率的敏感度(越大越敏感)
- β:对历史波动率的记忆程度(越大越平滑)
通常要求 α + β < 1,否则模型不稳定。
from arch import arch_model
def garch_volatility(returns):
"""
使用 arch 库拟合 GARCH(1,1)
"""
model = arch_model(returns * 100, vol='Garch', p=1, q=1)
res = model.fit(disp='off')
# 返回条件波动率
return res.conditional_volatility / 100
我的经验:GARCH(1,1) 在预测未来 1-2 步的波动率时表现不错,但计算开销大。做市策略里,我通常只在分钟级别或更高时间框架上用 GARCH,tick 级数据用 EWMA 就够了。
GARCH 的缺点也很明显:参数估计需要大量数据,而且对初始值敏感。如果数据量不够,拟合出来的参数可能毫无意义。
警告:别在数据量少于 500 个样本时用 GARCH。我曾经在 200 个 tick 上跑 GARCH,结果 α 和 β 估计值完全不合理,波动率预测值忽高忽低,差点把策略搞崩。
三种方法怎么选?
我直接给个参考表:
| 方法 | 计算速度 | 反应速度 | 适用场景 |
|---|---|---|---|
| SMA | 极快 | 慢 | 回测、低频策略、波动率基准线 |
| EWMA | 快 | 中等 | 中高频做市、实时风控 |
| GARCH(1,1) | 慢 | 快(预测能力强) | 波动率预测、分钟级策略 |
嗯,这里要注意:没有万能的方法。我个人的习惯是,先用 SMA 搭个基线,然后用 EWMA 做实时调整,最后用 GARCH 做辅助预测。三者结合,效果最好。
最后一个小建议:不管你用哪种方法,记得定期重新校准参数。市场结构会变,去年的最优参数今年可能就是毒药。我每季度都会跑一次参数扫描,确保模型还活着。