
做交易和风控的人应该都见过这种画面某一天市场突然暴跌紧接着不是安静下来而是一连串剧烈的上下震荡反过来在行情平淡的日子里波动也像睡着了一样。金融里把这种现象叫波动率聚集而描述它最经典的工具就是GARCH模型。GARCH的全称是广义自回归条件异方差听起来很拗口但它做的事情其实很朴素——把“今天波动大不大”这件事用过去几天的价格变化和波动水平预测出来。它不预测涨跌方向只预测风险的大小所以在期权定价、止损设置、风险管理、资产配置里都是常客。这篇文章会从波动率聚集这个现象讲起把GARCH的原理、参数意义讲透然后带你把模型在Python里完整跑一遍并处理几个常见的坑。适合想进量化金融但还没系统接触过波动率建模的读者也适合做风控、基本面研究时想给风险加一个动态口径的从业者。1. 波动率聚集先看懂市场的一个“指纹”1.1 你能看见的规律大波动后面跟着大波动把任意一段金融资产的日收益率序列画出来第一眼的感觉往往是“乱”但如果多看几眼你会发现一个非常稳定的现象波动不是均匀分布的而是呈块状出现的。某段时期收益率在±3%以上来回冲击另一段时期可能连续几周都在±0.3%以内横着走。这个现象在统计上叫波动率聚集。我常用一个比喻来解释它——地震后的余震。主震之后往往不是立刻彻底平静而是一连串余震强度逐步衰减金融市场也是一样暴跌或暴涨之后市场情绪、流动性、杠杆调整都需要时间消化所以后面会跟着一段高波动期直到各方重新找到均衡。反过来平稳期里市场信息清淡、交投情绪稳定波动率也会自我维持在一个低位。传统金融模型有一个很大的问题它假设收益率的方差是常数。这在数学上很方便但和现实明显不符。如果用整个样本段一个标准差去描述风险高波动期的风险会被严重低估低波动期的风险又被高估。换句话说传统模型给出的是一个“平均风险”而不是“当前风险”。GARCH模型的价值就是让方差本身成为一个动态变量昨天的波动会传导到今天今天的新消息又会进一步修改明天的波动预期这样我们得到的才是真正贴近市场的实时风险度量。1.2 从ARCH到GARCH方差不再是常数GARCH并不是凭空出现的。1982年Engle提出了ARCH模型思路很直接资产收益率的当期方差可以用过去若干期的“残差平方”来解释。比如ARCH(1)就是σ_t² ω α·ε_{t-1}²这里σ_t²是t时刻的条件方差ε_{t-1}是前一期的收益率残差。这个模型的优点是简单但缺点也很明显如果冲击的影响持续很久需要很多阶滞后项才能拟合出来参数一多估计就不稳定样本稍微短一点就很容易出问题。1986年Bollerslev在ARCH基础上加了“自身滞后”项提出GARCH。以最常用的GARCH(1,1)为例σ_t² ω α·ε_{t-1}² β·σ_{t-1}²和ARCH相比GARCH只增加了一个β项但效果完全不同昨天的条件方差σ_{t-1}²被纳入了今天的计算而昨天的σ_{t-1}²又包含前天的信息这样即便只有一阶滞后期也可以表达一段很长很长的记忆衰减。用一个不太严谨但很好懂的说法GARCH模型本身自带“递归记忆”所以它能用极少的参数刻画出波动率聚集这种长期依赖现象。2. GARCH模型核心原理它凭什么能抓住波动率的尾巴2.1 一个简洁的方程两个核心系数继续围绕GARCH(1,1)来说它在实际应用里占绝对主导。完整方程一般写成两部分均值方程r_t μ ε_t其中 ε_t σ_t · z_t条件方差方程σ_t² ω α·ε_{t-1}² β·σ_{t-1}²这里z_t是独立同分布的随机扰动常见假设是标准正态分布或标准化学生t分布。条件方差方程里的三个参数各有明确分工ω长期基准方差分量代表没有任何近期冲击时波动率所在的基本水平。α新息系数衡量当期“新消息冲击”对波动率的影响。α越大新信息对当天波动的冲击越敏感。β持续系数衡量上一期波动率对当期的影响。β越大波动率的惯性越强。实操中最需要关注的指标是αβ。它衡量的是波动率冲击的持续性学术上叫持久性。如果αβ接近1说明一次冲击的影响会持续非常久今天的暴涨暴跌会在很长一段时间内影响未来波动。我可以给一个更直觉化的计算如果αβ0.95冲击半衰期大约是ln(0.5) / ln(0.95)大概13.5个交易日。也就是说一次冲击发生后大约要两周它的影响力才衰减一半。这在日度数据上是一个非常典型的经验值。2.2 条件方差如何制造“聚集”效应很多人第一次接触GARCH时会有个疑问这个简单的递归方程为什么就能产生波动率聚集关键在于传导路径。假设今天出现了一个大的负向冲击ε_{t-1}²会是一个很大的数通过α项推高今天的σ_t²。今天的σ_t²变大之后又会通过β项把高波动传递到明天。换句话说今天的“大波动”直接作为明天的“波动水平”参与计算。这就是聚集效应的机制来源波动率自身带有惯性而新冲击可以在这个惯性上进一步叠加。这和“重大事件接连发生导致波动不断”的解释不同。GARCH并没有假设外部消息本身会聚集它只依赖收益率自己的历史就能自然复现出高波动段落在时间上的延续。这也是它在金融计量里如此重要的原因不需要额外引入新闻流数据仅凭价格序列本身就能提取出波动率聚集这个特征。顺便多说一句厚尾问题。金融收益率最常见的特征除了波动率聚集还有尖峰厚尾也就是极端值出现的概率比正态分布更高。GARCH模型天然能产生厚尾ε_t σ_t · z_t当条件方差σ_t持续处于高位时即使z_t只有中等程度偏离收益率的绝对值也会被放大。所以GARCH不需要刻意做“厚尾处理”模型本身就能生成厚尾序列这一点也是它相比恒定方差模型的一大优势。2.3 分布选择为什么正态分布不够用虽然GARCH模型能产生厚尾但z_t的具体分布假设依然很关键。最常用的三种选择是正态分布、学生t分布、偏态学生t分布。如果不考虑分布假设直接用正态往往会低估尾部风险尤其是计算VaR这种极端分位数时偏差会很致命。实操经验是先跑一个学生t分布的GARCH看一下自由度nu。nu越小尾部越厚当nu在5到10之间时说明数据有非常明显的厚尾特征用正态分布肯定不合适。如果nu跑到30以上那基本接近正态用t分布和正态分布结果差异不大。我自己做日度收益率建模时绝大多数情况下nu落在6到12之间所以我现在习惯直接把studentt作为默认分布省一轮来回比较。偏态学生t分布适合那些上下尾不对等的资产比如某些商品在某些时段上涨和下跌的剧烈程度不同。不过偏态参数在样本不充分时估计误差很大建议数据至少要有两三年日频长度再考虑否则很容易过拟合。3. Python实操把GARCH模型跑起来3.1 环境与数据准备收益率序列是第一步Python生态里做GARCH最成熟的是arch包作者是Kevin SheppardAPI设计和文档都做得比较规范。安装很简单pip install arch pandas numpy matplotlib我假设你手头有一份指数或个股的日线CSV至少包含date和close两列。先读进来计算对数收益率。为什么用对数收益率而不是简单百分比变动因为对数收益率具有时间可加性而且数值上近似连续复利对GARCH这种“方差累积”结构更友好。import pandas as pd import numpy as np import matplotlib.pyplot as plt from arch import arch_model from statsmodels.tsa.stattools import adfuller from statsmodels.stats.diagnostic import acorr_ljungbox df pd.read_csv(close.csv, parse_dates[date], index_coldate) close df[close].astype(float) ret 100 * np.log(close / close.shift(1)).dropna()这里有个细节我提醒一下我习惯把收益率乘以100再进模型。核心原因是数值规模会影响优化器表现原始收益率通常都是0.001级别的小数方差更是小到0.000001级别这样ω、α、β的参数估计容易遇到收敛困难乘以100之后收益率在百分之几的量级方差在个位数量级数值更友好输出结果读起来也更直观——比如条件波动率是2含义就是日波动率大约2%。处理完数据后可以先画一下收益率序列肉眼确认有没有波动率聚集。这一步虽然简单但比任何检验都直接。3.2 建模前必做平稳性与ARCH效应检验GARCH建模有一个前置条件收益率序列要平稳。如果价格本身是非平稳的基本不能直接建模但收益率通常是平稳的。用ADF检验可以做个确认adf_result adfuller(ret) print(ADF p-value:, adf_result[1])p值小于0.01基本就是平稳这种情况在日收益率上很常见。接下来要检验ARCH效应也就是收益率平方是否存在显著自相关。因为条件异方差的本质就是方差序列存在相关性。最简单的方法是直接对ret的平方做Ljung-Box检验lb_test acorr_ljungbox(ret**2, lags10, return_dfTrue) print(lb_test)看最后一列lb_pvalue如果显著小于0.05说明收益率平方存在显著自相关适合用GARCH建模。如果p值很大那说明这个序列可能没有明显的波动率聚集硬建GARCH意义就不大了。我踩过一次坑拿一段在窄幅震荡区间整理的日线数据直接套GARCH结果α和β全部不显著后来才发现序列本身方差变化很小ARCH效应本身就是弱的。3.3 参数拟合与解读GARCH(1,1)输出怎么看定阶的话我的建议很朴素绝大多数金融日收益率序列从GARCH(1,1)起步就够了。如果想要更严谨可以遍历p和q在0到2之间的组合用AIC或BIC选最优但不要盲目追求复杂阶数。best_aic np.inf best_order None for p in range(1, 4): for q in range(1, 4): try: am arch_model(ret, meanConstant, volGARCH, pp, qq, diststudentt) res am.fit(dispoff, update_freq0) if res.aic best_aic: best_aic res.aic best_order (p, q) except Exception: continue print(Best order by AIC:, best_order)上面这段代码里arch_model指定了均值方程用常数均值、波动率方程用GARCH、残差分布用studentt。因为我们在谈收益率建模多数情况下收益率均值并不显著偏离0常数均值足够了。遍历时不设置warnings过滤也行但建议加上避免拟合失败时一堆提示刷屏。拟合主模型am arch_model(ret, meanConstant, volGARCH, p1, q1, diststudentt) res am.fit(update_freq0, dispoff) print(res.summary())拟合完成后重点关注几个量mu均值方程的常数项一般很小甚至不显著这很正常。omega长期基准方差。alpha新息冲击系数。beta波动持续系数。nu学生t分布自由度上面说过nu越小尾部越厚。假设alpha0.09beta0.89omega0.03那么αβ0.98这是一个非常典型的日度序列数值。它意味着波动率冲击的衰减很慢今天一个异常波动会在未来大半个月内持续影响风险水平。如果alpha明显大于beta说明波动率更受新消息影响这种资产在出事时往往跳得又快又猛如果beta占绝对主导说明波动率的惯性更强冲击之后会“黏”很长时间。拟合后必须做一步诊断把标准化残差和它的平方再做Ljung-Box检验。标准化残差是模型剔除条件波动率后的残差如果模型提取信息充分标准化残差不应该再表现出明显的自相关或ARCH效应std_resid res.std_resid lb_resid acorr_ljungbox(std_resid, lags10, return_dfTrue) lb_resid_sq acorr_ljungbox(std_resid**2, lags10, return_dfTrue) print(lb_resid) print(lb_resid_sq)两步检验的p值最好都大于0.05说明模型已经把收益率序列里的自相关和异方差结构提取得差不多了。如果p值很小就需要考虑调整均值方程或提高GARCH阶数。3.4 波动率预测与VaR应用模型要能“干活”拟合不等于结束对实际业务来说更重要的是提取条件波动率序列。arch模型直接提供了属性cond_vol res.conditional_volatility plt.figure(figsize(12, 5)) plt.plot(ret, colorgray, alpha0.6, labelreturn) plt.plot(cond_vol, colorred, labelcond vol) plt.legend()这条红色曲线就是模型的“核心产出”能直观看到波动率高企的时段和低波动的时段。做风险监控时我一般会选择条件波动率超过某一个阈值比如样本95%分位的时间段发出预警相比固定标准差阈值这种方式对市场状态切换的反应要快很多。最后一行预测代码也很常用fore res.forecast(horizon5) var_fore fore.variance.iloc[-1] vol_fore np.sqrt(var_fore) print(vol_fore)这里predicted variance对应未来5个交易日的条件方差。需要注意一个问题GARCH的多步预测会随着h的增加向长期无条件方差ω/(1-α-β)收敛。也就是说预测期限拉长模型其实是在告诉你是“长期平均波动水平”而不是未来每天都会出现的新冲击。这在风险评估里反而很有用——短期看当前状态长期看均值回归水平两者互为补充。把GARCH和VaR结合是量化风控里最常见的落地方式之一。用学生t分布假设算一个95%的单日VaR示例from scipy.stats import t mu res.params[mu] nu res.params[nu] sigma_t cond_vol.iloc[-1] z t.ppf(0.05, dfnu) var_95 -(mu z * sigma_t) print(f95% daily VaR: {var_95:.2f}%)注意我取负号的逻辑是VaR通常表达为“最大可能损失”是个正数。如果返回值是正的说明正常情况。这只是最基础的单期VaR框架真实场景里还要考虑持有期转换、组合层面相关性、极值情形压力测试但GARCH提供的动态波动率足以让VaR从“静态平均数”升级成“跟随市场状态更新的活数据”。4. 常见问题与排查技巧实录4.1 拟合报错和参数不显著怎么办最常见的问题就是求解器不收敛报错类似“Solver failed to converge”或者“Optimization terminated successfully but...”。我总结下来有这么几条解决路径首先检查数据值域。如果你拿原始小数收益率直接建模那么要先乘以100。数值太小会让梯度不稳定乘以100之后大部分收敛问题能直接消失。其次检查数据里有没有极端异常值比如某个日收益率突然出现-30%或30%可能是数据源错误也可能是拆股、除权除息没有复权。这种异常值会极大影响α的估计先去重或处理数据比调模型更有用。数据没问题但还不收敛可以考虑换优化器。arch包fit里可以传optimizer参数比如optimizerpowell或optimizernelder-mead这类无梯度算法在参数边界情况下往往更稳res am.fit(dispoff, optimizerpowell)如果你的模型和参数都正常但α或β显著性很弱先回到ARCH效应检验这一步。很多新手会忽略这个前置检查直接上GARCH结果自然不理想。没有ARCH效应GARCH就是在拟合噪声参数不显著是必然的。4.2 当αβ接近1警惕模型快“爆仓”日频收益率里αβ通常在0.95以上但如果常年贴近0.999甚至等于1就要警惕了。αβ1意味着模型退化成了IGARCH方差冲击的衰减速度几乎为零任何一次冲击都会永久影响未来波动水平这从经济和统计两个角度都很难接受。出现这种情况最常见原因是样本包含多个不同的宏观环境。比如一段很长的平静期外加一段剧烈危机期拼在一起模型为了让“平均行为”符合现实会把持久性拉得极高。处理方式通常有三种一是截取相对同质的时段重新建模二是换用GJR-GARCH或EGARCH把正负冲击的非对称效应加进去很多时候拿走杠杆效应后持久性指标会明显下降第三就是加入外生变量比如成交量或市场恐慌指数帮助模型区分不同波动来源。顺便说一下很多人喜欢直接对比GARCH和GJR-GARCH。GJR-GARCH在方差方程里多了一个非对称项用来表达“坏消息带来的波动通常比好消息更大”的现象。实测下来大多数股票指数都存在明显的负向冲击非对称性GJR-GARCH的拟合优度和预测表现往往比普通GARCH更好。所以如果你用普通GARCH拟合股票数据我建议顺手也跑一个GJR-GARCH做对比这算是我每次项目的标配动作。4.3 预测结果异常的真实原因有的朋友会问为什么GARCH的长期预测看起来那么“平”波动率始终趋近同一个值这不是模型出bug而是条件方差本身的数学性质。GARCH模型的条件方差是一个均值回归过程无论当前处在什么状态只要预测期限足够长它都会回归到长期平均方差。这恰恰是模型合理的地方未来30天的平均波动率会受到今天、明天新信息的影响但仅仅基于今天已知信息长期预测的期望只能是历史平均水平。真正要小心的是下面两类操作陷阱第一用全样本拟合后再对样本内历史做“预测”这属于泄露未来信息。正确做法是用滚动窗口或扩展窗口只利用t时刻以前的数据拟合模型再预测t1期这样得到的预测序列才能用于回测和绩效评估。第二是样本量不足。GARCH依赖大样本才能稳定估计尤其在用studentt分布时尾部参数nu对样本量更敏感。我个人的经验线是日度数据至少要有500个观测值低于这个数量参数估计的置信区间会特别宽模型输出的条件波动率也可能经常出现跳跃。如果确实只有一年左右的高频数据用周频数据反而可能更稳定虽然样本点更少但每个点携带的信息量更大。我自己在实际操作中还有个经验不要把GARCH当成黑盒每次拟合完我都会把条件波动率序列和实际绝对值收益率画在同一张图上肉眼看看高波动时段是否基本吻合。如果某些明显的风险事件完全没有在波动率曲线上体现出来那基本可以判定数据有问题或者模型设定错了方向。这种“看图检查”虽然原始却是排查问题最有效的第一道防线。如果你刚开始接触这个领域我建议不要急着搞各种GARCH变体先用标准GARCH(1,1)配合studentt分布把数据读取、检验、拟合、诊断、预测这条链路完整跑到滚瓜烂熟。等到你对输出结果每一个数字都能说出“它为什么是这个数”的时候再去研究EGARCH的非对称项、DCC-GARCH的多资产相关性就会发现那些都是在这个核心框架上的自然延伸。波动率建模不是一个能让人一夜暴富的魔术但它确实能让你在看市场的时候多出一双衡量风险的“尺子”。