【Python量化波动率实战 02】波动率会传染:EWMA 指数加权给近期行情更大话语权

上篇我们算出了 20 日滚动波动率,也看到它在 19.74% 到 42.06% 之间翻倍移动。但固定窗口有个天生缺陷:每一条旧数据和新数据的话语权一样大。三个月前的一场暴跌,和昨天的一次急拉,在窗口里权重相同------市场早变了,指标还在为旧闻纠结。

金融工程里的标准答案是 EWMA(指数加权移动平均)波动率,也是 RiskMetrics 体系推广的方法。今天实测它和固定窗口的差异。


本文你将得到什么

  1. 波动率聚集的量化检验:|收益率| 自相关怎么算、怎么读。
  2. EWMA 波动率的递推公式与 Python 实现(λ=0.94,RiskMetrics 日度推荐值)。
  3. EWMA 与固定 20 日窗口的三维对比:最新估计值、单日反应、对未来的预测力。
  4. 一个反直觉的诚实发现:单日跳变时固定窗口反而跳得更多,但预测力才是终极裁判。

一、波动率聚集:大波动跟大波动

Mandelbrot 在 1963 年就观察到:"大波动倾向于跟随大波动(无论涨跌),小波动倾向于跟随小波动。" 这叫波动率聚集,是几乎所有波动率模型的存在理由。

检验方法很直接:算日收益率绝对值的自相关。如果 |r_t| 和 |r_{t-1}| 显著正相关,说明昨天的动荡确实预示今天可能继续动荡------波动率不是常数,是可预测的。


二、EWMA:给近期数据更大话语权

EWMA 波动率的递推公式只有一行:

text 复制代码
sigma2_t = λ * sigma2_{t-1} + (1 - λ) * r_{t-1}^2

其中 λ 是衰减因子,RiskMetrics 对日度数据的推荐值是 0.94。展开理解:

  • λ=0.94 意味着昨日方差(由昨日收益的平方体现)占 94%,新信息占 6%;
  • 越久远的历史数据,权重按 0.94 的幂次几何衰减------10 天前的数据权重只剩 0.94^10 ≈ 54%,一个月前只剩 0.94^20 ≈ 29%;
  • 对比固定 20 日窗口:一个月前的数据和昨天的数据权重完全相同,而且第 21 天前的数据会"突然出窗",造成估计值跳变。

递推起步需要一粒种子:常见做法是用最初 20 天的样本方差初始化。


可运行代码

bash 复制代码
pip install mairui pandas numpy
python 复制代码
from mairui import Client
import pandas as pd
import numpy as np

LICENCE = "LICENCE-66D8-9F96-0C7F0FBCD073"  # 演示证书,请换成你自己的正式证书
client = Client(LICENCE)

TRADING_DAYS = 244
LAMBDA = 0.94  # RiskMetrics 日度推荐值

bars = client.stock_history("600519", "d", "n", st="20260701", et="20261012")
df = pd.DataFrame(bars).rename(columns={"t": "date"}).sort_values("date").reset_index(drop=True)
df["date"] = pd.to_datetime(df["date"])
df["log_ret"] = np.log(df["c"] / df["c"].shift(1))

# 1. 波动率聚集检验:|日收益率| 自相关
r = df["log_ret"].dropna().reset_index(drop=True)
print("|日收益率| 自相关:")
for lag in (1, 2, 3, 5):
    print(f"  lag {lag}: {r.abs().autocorr(lag):.4f}")

# 2. EWMA 波动率(RiskMetrics 递推),前 20 日做种子
var = np.full(len(df), np.nan)
r0 = df["log_ret"].values
var[20] = np.nanvar(r0[1:21], ddof=1)
for t in range(21, len(df)):
    var[t] = LAMBDA * var[t - 1] + (1 - LAMBDA) * r0[t - 1] ** 2
df["vol_ewma"] = np.sqrt(var * TRADING_DAYS)

# 3. 对照组:等权 20 日窗口
df["vol_ew20"] = df["log_ret"].rolling(20).std(ddof=1) * np.sqrt(TRADING_DAYS)

last = df.iloc[-1]
print(f"最新一日年化波动率: EWMA {last['vol_ewma']*100:.2f}% / 等权20日 {last['vol_ew20']*100:.2f}%")

# 4. 单日反应:最大单日波动前后
idx = df["log_ret"].abs().idxmax()
worst = df.loc[idx]
print(f"最大单日波动 {worst['log_ret']*100:+.2f}% @ {worst['date'].date()}:")
for name in ("vol_ewma", "vol_ew20"):
    before, after = df.loc[idx - 1, name] * 100, df.loc[idx, name] * 100
    print(f"  {name:9s}: {before:.2f}% -> {after:.2f}%({after-before:+.2f}pct)")

# 5. 预测力:与未来 5 日已实现波动率的相关系数
df["fwd_vol5"] = df["log_ret"].shift(-1).rolling(5).std(ddof=1) * np.sqrt(TRADING_DAYS)
sub = df[["vol_ewma", "vol_ew20", "fwd_vol5"]].dropna()
print(f"与未来5日波动率相关: EWMA {sub['vol_ewma'].corr(sub['fwd_vol5']):.4f}"
      f" / 等权20日 {sub['vol_ew20'].corr(sub['fwd_vol5']):.4f}")

运行后你会看到类似结果:

text 复制代码
|日收益率| 自相关:
  lag 1: 0.2348
  lag 2: -0.0283
  lag 3: 0.0048
  lag 5: -0.0786

最新一日年化波动率: EWMA 40.30% / 等权20日 41.85%

最大单日波动 +6.64% @ 2025-10-28:
  vol_ewma : 28.12% -> 32.04%(+3.92pct)
  vol_ew20 : 25.91% -> 35.61%(+9.70pct)

与未来5日波动率相关: EWMA 0.3752 / 等权20日 0.2885

(示例基于 50 个交易日的演示样本测算;演示证书返回就近样例数据,申请正式证书可拉取全市场全量历史。)


怎么读这组数字

  1. 聚集存在的证据有限但方向正确:50 日小样本下,|收益率| 自相关只有 lag1 显著为正(0.2348),更长的滞后归零。真实市场的聚集通常更强------样本越长信号越清晰,这正是"换正式证书拉长样本"的实用场景之一。
  2. 单日反应的意外结果 :最大单日波动 +6.64% 当天,等权窗口的波动率跳升(+9.70pct)反而比 EWMA(+3.92pct)更大。原因是等权窗口同时发生了"旧数据出窗":窗口边缘滑出了一条平静期的旧数据,一进一出叠加放大了跳变。EWMA 的优势不在单日跳得猛,而在追踪平滑、没有出窗悬崖。
  3. 预测力才是终极裁判:EWMA 估计值与"未来 5 日已实现波动率"的相关系数 0.3752,高于等权窗口的 0.2885。加权方式更合理,对未来的信息含量就更高------这才是我们把波动率算得花哨的根本目的。

三个容易踩的坑

  1. λ 不是越小越灵敏越好:λ=0.90 对单日冲击反应剧烈但估计噪声大;0.94 是日度数据的经验平衡点,周度数据 RiskMetrics 推荐 0.97。
  2. 递推方向别搞反:公式里用的是 r_{t-1}(昨日收益)更新今日方差,天然无前视;如果你写成用 r_t 更新 sigma2_t,回测里就偷看了未来。
  3. 种子方差影响初期读数:递推前 20 天用样本方差初始化,这段时间的 EWMA 值受种子影响,分析时跳过热身期更稳妥。

小结

EWMA 用一行递推公式解决了固定窗口"新旧数据同权 + 出窗跳变"两大缺陷,实测预测力也更优。它是从"算波动率"到"用波动率"的桥梁。

下一篇就把波动率用起来:既然波动大的时候风险高,那就波动大的时候少拿点仓位------波动率目标仓位法,回测见真章。

下篇预告:【Python量化波动率实战 03】波动大就少拿点:波动率目标仓位的回测实证。


免责声明

本文仅为技术方法演示,不构成任何投资建议。文中示例数据仅用于教学,历史表现不代表未来收益。投资有风险,决策需谨慎。

代码与文档可参考 GitHub:github.com/MaiRuiApi

相关推荐
GISer_Jing1 小时前
如何学习Agent
后端·langchain
JWASX2 小时前
【Go】map 源码解析 - group
后端·golang·map·源码分析
量化分析码农3 小时前
【Python量化波动率实战 01】波动率是怎么算的:年化、滚动与区间估计一次讲清
后端
特立独行的猫A3 小时前
AI 时代,我为什么推荐用 cj-tauri 开发桌面应用
java·后端·python
金銀銅鐵3 小时前
从一个类名到一张类图:用反射自动生成 PlantUML
java·后端·uml
追逐时光者4 小时前
一款基于 .NET 开源、免费的数据库自治诊断平台
后端·.net
追逐时光者4 小时前
让 GitHub Copilot 更懂你的 .NET 代码风格:一份精简实用的 Copilot Instructions
后端·.net
小满zs5 小时前
Go语言第十八章(架构设计)
后端·go
山甫aa5 小时前
锁一篇搞定:乐观锁、悲观锁、CAS、数据库锁与分布式锁【从零开始的 Web 后端学习】
java·数据库·分布式·后端·学习·web