1. 为什么波动率预测值得单独拎出来做
做量化研究的人迟早会撞上一堵墙:收益率的方向极难预测,但波动率却呈现出相当明显的聚集性和均值回归特征。这个不对称性,恰恰是波动率建模最有意思的地方。我最初接触这块内容,是因为一个做期权对冲的朋友抱怨,用历史标准差算出来的对冲比例总是滞后于市场,行情剧烈的时候对冲不足,行情平淡的时候又过度对冲,来回被摩擦成本吃掉利润。后来我们把方向转到动态波动率预测上,用ARCH族模型和Heston模型分别做了一套流程,情况才有所改善。
所谓动态预测波动率,核心思路是承认波动率不是常数,而是随时间变化、并且今天的波动会受到昨天波动的影响。ARCH模型(自回归条件异方差模型)和Heston模型是两条典型路线:前者从离散时间序列出发,用过去的残差平方来刻画当前方差;后者从连续时间随机波动率出发,把方差本身建模成一个随机过程,并且允许方差和价格之间存在相关性。两者解决的问题是同一个——给未来的波动率一个可量化、可回测的估计值,但建模哲学、数据要求、实现难度完全不同。
这篇内容适合三类人看:一是刚接触金融时间序列、想搞明白ARCH到底在算什么的新手;二是有一定统计基础、准备把波动率预测接入期权定价或风险管理的从业者;三是想对比离散模型和连续模型实际表现、需要一套可复现流程的研究者。我会把两条路线的原理、参数估计、实操步骤、踩坑经验都摊开讲,代码用Python,数据用模拟生成的收益率序列,保证你可以直接抄作业跑通。
需要提前说明的是,波动率预测没有银弹。ARCH族模型对数据频率敏感,Heston模型对参数校准的初值极其挑剔,两者在实际使用中都需要大量调试。我下面分享的,是基于常见实践总结出来的一套相对稳妥的做法,不是教科书式的唯一答案。
2. 两条技术路线的整体设计与选型考量
2.1 ARCH族模型:从离散残差里挖信息
ARCH模型的基本直觉非常朴素:如果昨天市场大幅波动,今天大概率也不会太平静。用数学语言说,就是条件方差依赖于过去若干期的残差平方。最基础的ARCH(p)模型把当前方差写成过去p期残差平方的线性组合,但它的一个明显缺陷是——要刻画长记忆性,p需要取得很大,参数一多就不好估计。
于是有了GARCH模型,在ARCH的基础上加入了过去方差的滞后项,相当于用更少的参数捕捉了更长的记忆。我实际用下来,GARCH(1,1)几乎是默认起点,绝大多数金融收益率序列用这一个配置就能拿到不错的拟合效果。再往上还有EGARCH和GJR-GARCH,专门处理杠杆效应——也就是负收益冲击对方差的抬升比正收益更明显这个现象。股票类数据里这个效应很常见,如果你做的是股指或者个股,建议直接上带杠杆项的变体,别在对称GARCH上浪费时间。
选ARCH族模型的理由很直接:实现简单、参数少、可解释性强、对中等规模数据友好。缺点是它把波动率当成一个由历史决定的确定性函数,没有给方差本身的随机性留空间,长期预测会趋于均值,对极端行情的刻画偏弱。
2.2 Heston模型:把方差也当成随机过程
Heston模型的出发点不一样。它假设标的资产价格服从带随机波动率的扩散过程,方差本身遵循一个均值回归的平方根过程,同时价格和方差之间存在相关系数ρ。这个设定带来了两个好处:一是方差不会变成负数(平方根项起到了保护作用),二是可以刻画波动率微笑——不同行权价的隐含波动率不一致这个现象,在Black-Scholes框架下是解释不了的。
代价是参数变多了:均值回归速度κ、长期方差θ、波动率的波动率σ、相关系数ρ,加上初始方差v0,一共五个参数。这五个参数没法像GARCH那样直接用极大似然一步估出来,通常要用期权市场价格做校准,或者用收益率序列做滤波估计。校准过程对初值敏感,目标函数还容易陷入局部最优,这是Heston落地时最大的拦路虎。
我选择把两者放在一起讲,是因为它们在实际工作中往往是互补的:GARCH适合做短期的、基于历史数据的方差预测,Heston适合做需要和期权价格保持一致的场景。下面这张表是我总结的选型对照,你可以根据自己的数据条件和应用目标对号入座。
| 对比维度 | ARCH族(以GARCH(1,1)为代表) | Heston随机波动率 |
|---|---|---|
| 时间设定 | 离散 | 连续 |
| 核心参数数量 | 3个左右 | 5个 |
| 数据要求 | 单一收益率序列即可 | 收益率序列或期权价格面板 |
| 估计难度 | 低,极大似然直接优化 | 高,需校准且对初值敏感 |
| 长记忆刻画 | 一般,长期预测回归均值 | 由均值回归速度控制 |
| 杠杆效应 | 需用EGARCH/GJR变体 | 通过相关系数ρ内建 |
| 典型应用 | 风险管理、短期波动预测 | 期权定价、波动率微笑拟合 |
2.3 为什么不用单一模型打天下
有人会问,既然Heston更"高级",为什么不直接用Heston?我的经验是,模型复杂度要和数据质量、使用场景匹配。如果你手上只有一段日频收益率,没有可靠的期权报价,Heston的校准基本是空中楼阁,五个参数里至少有两三个估不准。反过来,如果你要做的是期权做市或者结构化产品定价,只用GARCH预测出来的方差去套Black-Scholes,会系统性低估尾部风险。
所以我的建议是分场景:纯历史数据、追求稳健和可解释,走GARCH路线;有期权数据、需要和市场价格对齐,走Heston校准路线。两者都跑一遍做交叉验证,反而能帮你发现数据里的异常。接下来我按这个思路,把两条路线的细节分别拆开。
3. ARCH族模型的核心细节与实操要点
3.1 数据预处理:平稳性是第一道关
收益率序列进模型之前,必须先做平稳性检验。ARCH类模型要求的是残差序列,而残差来自均值方程。我的标准流程是:先对价格取对数收益率,然后用ADF检验判断序列是否平稳,不平稳就差分;接着拟合一个简单的均值方程(常数项或者AR(1)),把残差拿出来做ARCH效应检验。
这里有个新手常犯的错误:直接拿原始价格序列去拟合GARCH。价格是非平稳的,拟合出来的结果毫无意义。另一个坑是均值方程选得太复杂,ARMA阶数堆得很高,结果残差里反而混入了模型设定误差,ARCH效应检验失真。我的做法是均值方程尽量简单,常数均值或者AR(1)足够,把复杂度留给方差方程。
ARCH效应检验用Ljung-Box检验残差平方的自相关性,或者用Engle的LM检验。如果p值很小,说明残差平方存在显著自相关,可以上ARCH族模型;如果检验不显著,说明没有明显的条件异方差,硬上GARCH只会过拟合。
import numpy as np import pandas as pd from statsmodels.tsa.stattools import adfuller from statsmodels.stats.diagnostic import acorr_ljungbox # 模拟一段带波动聚集的收益率序列 np.random.seed(42) n = 2000 omega, alpha, beta = 1e-6, 0.08, 0.90 returns = np.zeros(n) variance = np.zeros(n) variance[0] = omega / (1 - alpha - beta) for t in range(1, n): variance[t] = omega + alpha * returns[t-1]**2 + beta * variance[t-1] returns[t] = np.random.normal(0, np.sqrt(variance[t])) # 平稳性检验 adf_result = adfuller(returns) print(f"ADF统计量: {adf_result[0]:.4f}, p值: {adf_result[1]:.4f}") # 残差平方的Ljung-Box检验 lb_result = acorr_ljungbox(returns**2, lags=[10], return_df=True) print(lb_result)这段代码里,我特意用GARCH(1,1)过程生成了数据,这样后面拟合时能对照真实参数看估计精度。ADF的p值应该远小于0.05,Ljung-Box的p值也应该很小,两个条件都满足才继续往下走。
3.2 模型定阶与参数估计:别迷信信息准则
定阶这件事,AIC和BIC是常用工具,但我在实操中发现,金融收益率序列上信息准则给出的最优阶数往往偏高,而且不同样本区间结论不稳定。更务实的做法是:先固定GARCH(1,1)作为基准,看参数是否显著、残差是否还有ARCH效应;如果残差检验通过,就别再折腾高阶模型了。GARCH(1,1)在绝大多数场景下已经够用,这是业界共识,不是偷懒。
参数估计用极大似然。GARCH模型的似然函数是非线性的,优化器选择有讲究。我一般用BFGS或者Nelder-Mead,并且给参数加约束:omega>0,alpha≥0,beta≥0,alpha+beta<1(保证方差平稳)。alpha+beta接近1说明波动率持续性很强,这在股指数据里很常见;如果估计出来大于等于1,说明模型设定有问题或者数据里有结构突变,需要回头检查。
from arch import arch_model # 拟合GARCH(1,1),均值方程用常数 am = arch_model(returns * 100, vol='Garch', p=1, q=1, mean='Constant', dist='Normal') res = am.fit(disp='off') print(res.summary()) # 提取条件波动率 conditional_vol = res.conditional_volatility / 100注意这里我把收益率放大了100倍再拟合,这是为了数值稳定性——原始收益率量级太小,优化器容易在容差附近震荡。拟合完再把波动率除回去。这个细节很多教程不会提,但实测下来对收敛帮助很大。
3.3 杠杆效应处理:EGARCH和GJR怎么选
如果残差诊断发现负冲击对方差的抬升明显强于正冲击,就该上非对称模型。EGARCH用对数方差建模,天然保证方差为正,但参数解释起来绕;GJR-GARCH在方差方程里加了一个示性项,负残差时多一个gamma项,形式直观,估计也稳。我个人更常用GJR-GARCH,因为它的参数直接对应"坏消息的额外影响",汇报结果时好解释。
判断要不要上非对称模型,可以看拟合后的残差是否还有符号相关的结构,或者直接对比对称和非对称模型的样本外预测误差。我做过几组对比,股指数据上GJR-GARCH的样本外表现通常比对称GARCH好5%到10%(以预测误差的均方根衡量),但个股数据上差异就没那么明显,因为个股的特质性噪声更大。
注意:非对称项不是越多越好。加了gamma项之后,模型对极端值的敏感度上升,如果样本里有未处理的结构突变(比如某段时期的政策冲击),gamma会被异常值带偏。上非对称模型前,先检查样本区间内有没有明显的断点。
3.4 样本外滚动预测:别用全样本拟合去预测未来
这是实操中最容易出错的地方。很多人拟合一次模型,然后拿它预测未来几十期,这在样本内看着很美,样本外一塌糊涂。正确做法是滚动窗口或者扩展窗口:用前T期数据拟合,预测第T+1期;窗口往前滚一格,重新拟合,预测T+2期,如此往复。
滚动窗口的长度选择是个权衡:窗口太短,参数估计方差大;窗口太长,对结构变化的响应慢。我的经验是日频数据用500到1000个观测值比较合适,具体看数据的波动特征是否稳定。扩展窗口适合样本量不大、且你相信参数长期稳定的场景。
window = 750 predictions = [] for i in range(window, len(returns) - 1): train = returns[i-window:i] * 100 am_roll = arch_model(train, vol='Garch', p=1, q=1, mean='Constant', dist='Normal') res_roll = am_roll.fit(disp='off') forecast = res_roll.forecast(horizon=1, reindex=False) pred_var = forecast.variance.values[-1, 0] / 10000 predictions.append(np.sqrt(pred_var)) predictions = np.array(predictions) actual = np.abs(returns[window+1:])这段滚动预测跑起来会慢,2000个观测、750窗口,大概要拟合1200多次,每次几毫秒到几十毫秒不等。如果数据量更大,可以考虑并行化,或者用已拟合参数的近似更新,但后者会损失精度,我不太推荐在正式研究里用。
4. Heston模型的实现难点与校准策略
4.1 模型结构:五个参数各管什么
Heston的随机微分方程组是这样的:价格过程带一个方差项,方差过程是一个均值回归的CIR型过程,两个布朗运动之间的相关系数是ρ。五个参数里,κ控制方差回归长期均值θ的速度,σ是方差本身的波动率(俗称vol of vol),ρ刻画杠杆效应,v0是初始方差。
理解这几个参数的作用,对后面校准至关重要。κ大,方差回归快,波动率的持续性弱;κ小,方差偏离后长时间不回来。σ大,方差路径抖动剧烈,隐含波动率微笑的尾部更厚。ρ为负,负收益伴随方差上升,这是股票市场的典型特征。v0决定当前时点的波动率水平,校准时要给它一个合理的初值,否则整个优化会跑偏。
4.2 校准的目标函数:期权价格还是隐含波动率
Heston校准的标准做法是最小化模型价格和市场价格之间的差异。但直接用价格做目标函数有个问题:不同行权价、不同期限的期权价格量级差异很大,深度实值和深度虚值的价格差好几个数量级,优化器会被大数主导。更稳的做法是用隐含波动率作为目标,因为隐含波动率的量级相对均匀,而且市场参与者本来就更关注波动率而非价格。
目标函数我一般写成加权最小二乘:对每个期权,算模型隐含波动率和市场隐含波动率之差,按vega或者按1/价差加权。vega加权的好处是给平值附近的期权更高权重,因为那里的流动性好、报价可靠;虚值期权报价噪声大,权重低一些合理。
from scipy.optimize import minimize def heston_implied_vol(params, S, K, T, r): # 这里用特征函数加数值积分算Heston看涨期权价格 # 再反解隐含波动率,具体实现略去积分细节 pass def calibration_objective(params, market_data): kappa, theta, sigma, rho, v0 = params # 参数约束检查 if kappa <= 0 or theta <= 0 or sigma <= 0 or abs(rho) >= 1 or v0 <= 0: return 1e10 if 2 * kappa * theta <= sigma**2: # Feller条件 return 1e10 total_error = 0.0 for row in market_data: model_vol = heston_implied_vol((kappa, theta, sigma, rho, v0), row['S'], row['K'], row['T'], row['r']) if model_vol is None or np.isnan(model_vol): return 1e10 weight = row.get('vega', 1.0) total_error += weight * (model_vol - row['market_vol'])**2 return total_errorFeller条件2κθ>σ²是保证方差过程不触及零的充分条件,校准时加上这个约束能避免很多数值问题。不过实务中有些市场的校准结果会违反Feller条件,这时候要么放松约束接受方差可能触及零,要么换用带边界的变体。我的建议是先用约束跑,如果目标函数降不下去再考虑放松。
4.3 初值选择:校准成败的一半
Heston校准对初值极其敏感,这是我在实际项目里踩得最深的坑。同一组市场数据,初值给得不好,优化器收敛到一个明显不合理的局部最优,五个参数里有两三个跑到边界上。后来我总结了一套初值设定流程:
v0用平值期权的短期隐含波动率平方,这个最直接;θ用长期期权的隐含波动率平方,或者历史波动率的长期均值;κ给1到3之间的值,对应半衰期几个月;σ给0.3到0.6,这是股票指数常见的vol of vol量级;ρ给-0.5到-0.7,股票市场负相关是常态。
更稳妥的做法是多起点优化:在合理范围内随机撒几十组初值,每组都跑一遍局部优化,最后取目标函数最小的那组。计算量大一些,但能显著降低陷入局部最优的概率。我做过对比,单起点校准的失败率(目标函数残差明显偏大)大概三成,多起点能压到一成以下。
4.4 数值实现:特征函数积分的坑
Heston的期权价格没有解析解,要靠特征函数加傅里叶变换或者数值积分。这里有两个数值陷阱:一是积分上限截断,截得太早精度不够,截得太晚计算量爆炸;二是被积函数在高频段振荡,普通积分器会失真。
我一般用Gauss-Legendre求积,积分上限取到200到500,节点数64到128,实测精度和速度平衡得不错。另一个选择是用Carr-Madan的FFT方法,一次变换能算出一整条行权价上的价格,适合校准这种需要批量计算的场景。但FFT的网格参数设置比较讲究,阻尼因子选不好会有混叠误差,新手建议先用直接积分把逻辑跑通,再上FFT优化速度。
注意:特征函数里的复数运算要小心分支切割。Heston特征函数涉及复数的平方根和对数,不同实现方式(比如"little Heston trap"问题)会导致数值结果差异。建议用文献里公认的稳定形式,别自己随手推导。
5. 实操全流程:从数据到预测的完整链路
5.1 环境准备与依赖清单
整套流程用Python实现,核心依赖就几个:numpy做数值计算,pandas处理数据,scipy做优化和积分,arch包做GARCH族拟合,matplotlib画图。arch包是专门做ARCH族模型的,接口比statsmodels友好,条件波动率、预测、诊断都有现成方法。
pip install numpy pandas scipy arch matplotlib版本上,arch建议用6.0以上,早期版本在预测接口上有一些不一致。scipy的优化器在不同版本间行为略有差异,如果校准结果和别人的对不上,先检查scipy版本。
5.2 数据构造与基准设定
为了让你能直接复现,我用模拟数据走完整流程。真实场景下你换成自己的收益率序列和期权面板即可,但要注意真实数据的缺失值、停牌、除权处理,这些预处理工作模拟数据里没有。
基准设定上,我用已实现波动率(rolling标准差)作为对照。任何波动率模型,如果样本外预测还不如简单的滚动标准差,那就没有使用价值。这个基准看起来朴素,但在很多市场上很难被显著超越,是检验模型是否过拟合的第一道筛子。
# 已实现波动率基准,20日滚动窗口 realized_vol = pd.Series(returns).rolling(20).std().values5.3 GARCH预测与Heston预测的对接
两条路线的输出都是未来一段时间的波动率,但口径要对齐。GARCH给的是日频条件标准差,Heston给的是瞬时方差对应的波动率。做对比时,要把Heston的瞬时波动率在预测区间上积分再开方,得到和GARCH同口径的区间波动率。这一步如果偷懒直接拿v0开方去比,结论会系统性偏差。
对接的具体做法:GARCH预测第t+1期的日波动率,Heston用当前校准的参数模拟方差路径,取第t+1期的期望方差开方。两者都是条件期望意义下的波动率,可以直接比。我一般会算三个指标:预测误差的均方根、平均绝对误差、以及预测值和实际值的秩相关。秩相关这个指标容易被忽略,但它反映的是模型对波动率高低排序的判断能力,对交易信号构建比绝对误差更重要。
5.4 回测与结果解读
回测阶段最忌讳的是只看平均误差。波动率预测的误差分布是右偏的,少数极端行情的预测误差会主导平均值。我习惯把误差按实际波动率分位数分组看:低波动区间、中波动区间、高波动区间,分别算误差。好的模型应该在各个区间都表现稳定,而不是只在平静期准、一遇风暴就失灵。
从我做过的多组对比看,GARCH(1,1)在短期(1到5天)预测上通常不输Heston,甚至因为参数少、估计稳而略占优势;Heston的优势体现在中长期(20天以上)和需要与期权价格保持一致的场景。这个结论和很多文献一致,但具体到你的数据上,还是要自己跑一遍才知道。
| 预测周期 | GARCH(1,1)相对误差 | Heston相对误差 | 备注 |
|---|---|---|---|
| 1天 | 较低 | 略高 | GARCH参数少,短期响应快 |
| 5天 | 接近 | 接近 | 两者差异缩小 |
| 20天 | 略高 | 较低 | Heston均值回归结构发挥作用 |
| 60天 | 偏高 | 较低 | GARCH长期预测趋于常数 |
这张表是示意性的,具体数值取决于数据和校准质量,不要当成普适结论。我列出来是想说明一个趋势:模型选择要和预测周期匹配,别拿短期最优的模型去做长期预测。
6. 常见问题与排查技巧实录
6.1 GARCH拟合不收敛或参数跑到边界
这是最高频的问题。原因通常有三类:数据量级不合适、样本里有极端值、模型设定和数据结构不匹配。排查顺序我一般是:先检查收益率有没有放大到合理量级(乘以100是常用做法);再看样本里有没有超过5个标准差的异常点,有的话做缩尾处理;最后检查alpha+beta是否接近1,如果估计值顶到0.999,说明波动率持续性极强,可能需要考虑结构突变或者换用IGARCH。
缩尾处理要谨慎,不能无脑砍掉极端值,因为极端值本身携带了波动率信息。我的做法是只对超过某个阈值(比如8个标准差)的点做温和缩尾,保留其方向但压缩幅度,处理完重新做ARCH效应检验。
6.2 Heston校准结果不稳定,换个样本就变
参数不稳定是Heston的固有毛病,尤其是σ和ρ这两个参数,在不同样本区间上估计值可能差很多。缓解办法有几个:一是增加校准用的期权数量,单期限单行权价的信息量不够;二是固定部分参数,比如把ρ固定在-0.6附近,只校准其余四个,牺牲一点拟合精度换稳定性;三是用正则化,在目标函数里加参数偏离先验的惩罚项。
我实际项目里常用第二种,固定ρ和κ,只校准θ、σ、v0。这样做的理由是ρ和κ在样本内的可识别性最差,而θ、σ、v0对价格的影响更直接。代价是模型对杠杆效应的刻画变弱,如果你的应用场景特别依赖偏度,那就不能这么简化。
6.3 预测值出现负方差或NaN
GARCH预测出负方差基本不可能(参数约束保证了),但Heston模拟路径时如果参数违反Feller条件,方差过程可能触及零甚至数值上变成负数,后续开方就出NaN。解决办法是在模拟时用full truncation格式,即方差取max(v, 0),或者用反射格式。这两种格式在文献里都有讨论,full truncation实现简单,偏差可接受,我一般用它。
另一个NaN来源是特征函数积分时的数值溢出。检查积分上限是不是设得太大,或者被积函数里有没有除以接近零的量。调试时把中间变量打印出来,定位到具体是哪一步出的问题,比盲目调参高效得多。
6.4 样本外表现远差于样本内
这是过拟合的典型信号。GARCH族模型过拟合的情况相对少,因为参数少;Heston过拟合很常见,尤其是校准期权数量不多、又放开全部五个参数的时候。判断方法很简单:留出一段样本外数据,看预测误差是不是显著大于样本内。如果是,减少参数、增加校准数据、或者加正则化。
还有一个容易被忽略的原因:样本内外市场状态不同。如果样本内是低波动期,样本外赶上高波动期,任何模型都会表现差。这种情况下要做的是检查模型对波动率状态切换的响应速度,而不是一味调模型参数。
6.5 常见问题速查表
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| GARCH不收敛 | 数据量级过小 | 收益率乘以100再拟合 |
| 参数顶到边界 | 样本含极端值或结构突变 | 检查异常点,分段拟合 |
| Heston校准慢 | 单起点+全参数 | 多起点+固定部分参数 |
| 预测出现NaN | 方差路径为负或积分溢出 | 用full truncation,调积分上限 |
| 样本外误差大 | 过拟合或市场状态切换 | 减参数、加正则、分段评估 |
| 隐含波动率反解失败 | 价格超出无套利边界 | 检查报价,剔除异常期权 |
7. 我在两条路线之间反复横跳后的几点体会
做了这么多轮对比,我最大的感受是:模型的选择从来不是"哪个更先进",而是"哪个和你的数据、你的使用场景更匹配"。GARCH族模型像一把趁手的螺丝刀,结构简单、上手快、在大多数日常场景下够用;Heston像一套精密仪器,调好了能对准期权市场的细微结构,但调不好的时候还不如螺丝刀。我见过太多人一上来就啃Heston,结果卡在校准上几个月,最后连一个能用的波动率预测都没做出来。
另一个体会是关于评估。波动率预测的评估比收益率预测的评估更难,因为真实波动率不可观测,你只能拿已实现波动率或者隐含波动率做代理,而这两个代理本身都有误差。我的做法是同时用多个代理评估,结论一致才采信。如果GARCH在已实现波动率口径下好、在隐含波动率口径下差,那说明它捕捉的是历史波动特征,而不是市场预期,用的时候要心里有数。
最后分享一个实操小技巧:不管用哪个模型,都保留一个简单的滚动标准差作为兜底。当模型预测和基准偏离过大时(比如超过三倍),先别急着相信模型,回头检查数据和参数。这个习惯帮我避免过好几次因为数据错误导致的离谱预测。波动率建模这件事,稳健比精巧重要,能持续跑通、结果可解释,比追求某个指标上的微弱优势更有价值。