3、低波动因子的度量方法:波动率的计算方式与预测模型
波动率这东西,说白了就是资产价格的「心跳」。心跳太快,你拿不住;心跳太慢,收益又不够看。低波动因子之所以能赚钱,核心就在于我们得先搞清楚——到底什么是「低波动」?
我个人习惯把波动率的度量分成两派:一派是「回头看」的,比如历史波动率、已实现波动率;另一派是「向前看」的,比如隐含波动率、GARCH预测。今天咱们就把这些家伙挨个捋一遍。
3.1 历史波动率:最朴素的算法
历史波动率,说白了就是过去N天收益率的标准差。你打开Excel,用STDEV函数算一下,就是这个东西。但这里有个坑——你算的是对数收益率还是简单收益率?
我建议用对数收益率。为什么呢?因为对数收益率在时间上可加,而且更符合正态分布的假设。简单收益率算出来的波动率,在价格波动大的时候会偏大。
import numpy as np
import pandas as pd
def historical_volatility(prices, window=20, trading_days=252):
"""
计算历史波动率
prices: 价格序列
window: 滚动窗口天数
trading_days: 年化交易日数
"""
# 计算对数收益率
log_returns = np.log(prices / prices.shift(1))
# 滚动标准差
rolling_std = log_returns.rolling(window=window).std()
# 年化
annualized_vol = rolling_std * np.sqrt(trading_days)
return annualized_vol
# 举个栗子
prices = pd.Series([100, 102, 101, 103, 105, 104, 106, 108])
vol = historical_volatility(prices, window=5)
print(vol)
3.2 已实现波动率:高频数据的魅力
历史波动率用的是日频数据,但如果你有日内数据呢?比如5分钟K线。这时候就可以算已实现波动率(Realized Volatility)了。
已实现波动率的公式很简单:把一天内所有高频收益率的平方加起来,再开根号。你想想看,这比只用收盘价算出来的波动率要精确得多。
def realized_volatility(intraday_returns):
"""
计算已实现波动率
intraday_returns: 日内收益率序列(比如5分钟收益率)
"""
# 平方和开根号
rv = np.sqrt(np.sum(intraday_returns ** 2))
return rv
# 假设一天有48个30分钟收益率
intraday_ret = np.random.randn(48) * 0.01
rv = realized_volatility(intraday_ret)
print(f"今日已实现波动率: {rv:.4f}")
3.3 隐含波动率:市场预期的温度计
隐含波动率是从期权价格反推出来的。市场觉得未来会波动大,隐含波动率就高;觉得风平浪静,它就低。这东西有个特点——它自带前瞻性。
我记得在2018年做低波动策略时,发现一个有趣的现象:当隐含波动率远高于历史波动率时,低波动因子往往表现更好。为什么?因为市场在过度恐慌,低波动股票被错杀了。
计算隐含波动率需要用到BS公式的反函数,这里给个简单示例:
from scipy.stats import norm
from scipy.optimize import brentq
def bs_call_price(S, K, T, r, sigma):
"""BS公式计算看涨期权价格"""
d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))
d2 = d1 - sigma*np.sqrt(T)
return S*norm.cdf(d1) - K*np.exp(-r*T)*norm.cdf(d2)
def implied_volatility(market_price, S, K, T, r):
"""用二分法反推隐含波动率"""
objective = lambda sigma: bs_call_price(S, K, T, r, sigma) - market_price
return brentq(objective, 0.01, 2.0)
# 示例
iv = implied_volatility(market_price=5.2, S=100, K=105, T=30/365, r=0.03)
print(f"隐含波动率: {iv:.2%}")
3.4 波动率预测模型:GARCH与EWMA
前面讲的都是「已经发生」的波动率。但做量化投资,我们更关心「未来」的波动率。这时候就需要预测模型了。
3.4.1 EWMA:指数加权移动平均
EWMA的思路很简单:越近的数据权重越大。它只有一个参数λ(衰减因子),通常取0.94(RiskMetrics的标准)。
def ewma_volatility(returns, lambda_=0.94):
"""
EWMA波动率估计
returns: 收益率序列
lambda_: 衰减因子
"""
n = len(returns)
vol = np.zeros(n)
# 初始方差用样本方差
vol[0] = np.var(returns)
for t in range(1, n):
vol[t] = lambda_ * vol[t-1] + (1-lambda_) * returns[t-1]**2
return np.sqrt(vol)
# 使用示例
returns = np.random.randn(100) * 0.02
ewma_vol = ewma_volatility(returns)
EWMA的好处是计算快、参数少。但有个问题——它对极端事件的反应不够灵敏。我曾在2015年股灾时用EWMA做风控,结果波动率上升的速度跟不上市场暴跌的速度。
3.4.2 GARCH:更精细的波动率建模
GARCH模型比EWMA多了一个东西——它考虑了「波动率的波动率」。简单说,GARCH(1,1)有三个参数:ω(长期均值)、α(新信息的影响)、β(旧波动的影响)。
from arch import arch_model
def garch_volatility(returns, p=1, q=1):
"""
GARCH(p,q)波动率预测
"""
model = arch_model(returns, vol='Garch', p=p, q=q)
res = model.fit(disp='off')
# 预测未来5天的波动率
forecasts = res.forecast(horizon=5)
predicted_vol = np.sqrt(forecasts.variance.values[-1])
return predicted_vol, res
# 示例
returns = np.random.randn(500) * 0.015
pred_vol, model_result = garch_volatility(returns)
print(f"未来5天预测波动率: {pred_vol}")
3.5 知识体系总览
说了这么多,咱们用一张图把整个脉络串起来:
3.6 实战中的选择建议
说了这么多方法,到底用哪个?我个人的经验是:
| 场景 | 推荐方法 | 理由 |
|---|---|---|
| 日频因子构建 | 历史波动率(60日窗口) | 简单稳定,数据易得 |
| 高频交易策略 | 已实现波动率 | 反应快,精度高 |
| 期权策略 | 隐含波动率 | 包含市场预期 |
| 风险预测 | GARCH(1,1) | 能捕捉波动率聚集效应 |
| 快速风控 | EWMA (λ=0.94) | 计算快,参数少 |
嗯,波动率的度量方法就聊到这儿。记住一点:没有完美的波动率指标,只有最适合你策略的指标。多试试不同的组合,找到那个让你睡得着觉的度量方式。