GARCH-分位数回归非线性交易模型:从波动率预测到尾部风险管理
把“初稿”两个字放在项目标题里反而是我觉得最值得讲的部分。GARCH-QR非线性回归交易模型我平时习惯缩写成GQNR做的是这样一件事先用GARCH族模型对资产收益率的波动率做条件估计再把不同置信水平的分位数预测交给分位数回归去拟合最后加一层非线性结构用来捕捉波动率在不同区间时对收益分布边缘的影响差异。模型的定位不是“预测明天涨还是跌”而是“预测明天如果跌大概会跌到什么位置”以及“这种尾部风险正在变大还是变小”。这套框架适合手里有持仓、需要做仓位管理或者对尾部风险量化感兴趣的读者参考。它还没到能直接实盘的程度但作为一套研究框架它已经把一个很重要的思路讲清楚了交易决策真正依赖的不是均值而是条件分布的两条尾巴。1. 为什么非要把波动率模型和分位数回归放在一起1.1 GARCH很能打但交易关心的是条件分布的边缘GARCH类模型在金融时间序列里的地位不用多说它的核心贡献是用一个简洁的递推式把波动率聚集效应描述出来。GARCH(1,1)的形式很多人闭着眼都能写σ_t² ω α · ε_{t-1}² β · σ_{t-1}²这个式子的好处是它把t时刻的条件方差看成上一期残差平方和上一期条件方差的加权平均能很好地解释“大波动后面往往跟着大波动”的现象。做风险管理和衍生品定价时σ_t是最基础的一层输入。但问题也出在这。GARCH给出的只是一个条件方差σ_t²它是一个数字。可交易者做仓位决策时真正想知道的是收益率的条件分布长什么样尤其是左尾和右尾的位置。这时候你必须在GARCH外面再套一个分布假设通常是正态分布或者t分布。而真实的高频日频收益率并不是正态的它既有偏度又有厚尾而且左尾和右尾的厚尾程度往往不一样。更要命的是这种不对称程度会随着波动率状态变化——低波动期和高波动期呈现出来的尾部形状根本不是同一个。如果强行用“GARCH估计σ_t 固定分布假设”去推导VaR或者期望损失结果就是在温和行情里看似合理碰到极端行情就集体失效。我见过很多回测曲线漂亮的模型把分布假设换成t分布之后反而更差就是因为t分布的固定自由度根本没法适应时变的尾部形态。1.2 分位数回归恰好补上这块短板分位数回归和普通回归的本质区别非常直接普通回归拟合的是E(Y|X)即给定解释变量X时Y的条件均值分位数回归拟合的是Q_τ(Y|X)即给定X时Y在τ这个置信水平下的条件分位点。你不需要预设一个整体的分布函数只需要针对你关心的那几个分位点做回归。分位数回归的损失函数是检查函数ρ_τ(u) u · (τ - I(u 0))最小化Σρ_τ(y_i - x_iβ)得到的β就是τ分位点的回归系数。这个目标函数是加权绝对偏差的和不是平方偏差所以它对极端值的敏感度远低于均值回归。尾部数据再诡异它也能稳健地描述出“当波动率处于高位时5%分位点到底在哪里”。把GARCH和QR放在一起逻辑就顺了GARCH负责把波动率状态提炼成一个条件估计值σ_tQR负责在给定这个状态的前提下直接估计条件收益分布的各个分位点。这样就不需要再纠结“收益到底服从什么分布”所有分布特征全部由数据自己说了算。这就是GQNR模型最核心的动机GARCH提供时变状态变量分位数回归负责刻画条件分布的边缘两者是天然的互补关系而不是强行拼凑。2. GQNR模型的结构设计与模块拆解2.1 三层架构波动率层、分位数层、非线性层GQNR整体可以拆成三层来看这个分层方式对我自己梳理代码和调试帮助很大。第一层是波动率状态层。我用GJR-GARCH(1,1)作为默认配置而不是经典GARCH(1,1)。原因是金融收益率对负向冲击的反应通常比正向冲击更剧烈这个现象叫杠杆效应。GJR模型在方差方程里加了一个非对称项σ_t² ω α · ε_{t-1}² γ · I(ε_{t-1}0) · ε_{t-1}² β · σ_{t-1}²当ε_{t-1}0时I(ε_{t-1}0)1负面冲击对波动率的贡献额外多出γ这一项。用GQR替代完之后首先要给每一项命名不用变量名。这一层输出的核心产物是σ_{t1|t}也就是基于t时刻信息对t1时刻波动率的一步预测。第二层是分位数回归层。针对一组预先设定的分位点τ分别建立条件分位数方程。因为要覆盖左右两条尾我一般会选τ∈{0.01, 0.05, 0.25, 0.50, 0.75, 0.95, 0.99}。每个τ对应一个方程彼此独立估计。这一层解决的是“在不同波动率水平下收益的各个分位点分别落在哪里”。第三层是非线性映射层。这是GQNR区别于“GARCH线性QR”的关键。普通的线性QR会假设波动率与收益分位数之间是直线关系但真实数据往往不是这样。低波动区和高波动区的斜率可能明显不同左尾和右尾的弯曲方向也可能不同。所以我在分位数回归里引入了非线性基函数让f_τ(σ_t)可以随波动率水平改变斜率。三层各司其职第一层解决状态提取第二层解决边缘拟合第三层解决复杂形态整体布局很清晰。2.2 核心公式与建模约定我用的基准方程可以写成Q_τ(r_{t1} | F_t) α_τ f_τ(σ_{t1|t}) β_τᵀ X_t其中F_t是t时刻的信息集σ_{t1|t}是第一步GARCH的一步预测。X_t是可选的外生因子比如趋势指标、成交量的异常变化或者流动性代理变量。初稿阶段我为了控制复杂度先让X_t为空只保留波动率状态这一个核心解释变量。f_τ用B样条基展开f_τ(x) Σ_{j1}^{K} c_{j,τ} · B_j(x)B_j(x)是定义在σ_t取值范围内的B样条基函数K是基函数个数。分位数回归的目标是min Σ_t ρ_τ(r_t - α_τ - f_τ(σ_t))每个τ独立求解一组参数{c_{j,τ}, α_τ}。估计完成之后给定一个新的σ_{t1|t}就能预测出对应分位点Q̂_τ。建模时有一个约定需要注意GARCH残差的标准化和分位数回归是在同一段样本上完成的所以必须先做波动率拟合把σ_t序列提取出来再把它当成解释变量塞进分位数回归。这个顺序不能颠倒否则模型的信息结构就不对了。2.3 为什么偏要加非线性环节很多做量化的人看到“线性模型效果还不错”就不愿意加非线性了因为非线性意味着过拟合风险大增。但在这个场景里非线性不是锦上添花而是非加不可。我拿某指数的日频数据做过一个简单扫描把σ_t按从小到大分成十组然后分别统计每一组内实际收益的5%分位点。结果非常直观在低波动组里5%分位点随波动率上升只是轻微下移进入高波动组之后5%分位点出现断崖式跳水。这种“斜率拐点”用一条直线去拟合无论如何都拟合不出来要么低估低波动区的下移速度要么在高波动区给出过于乐观的尾部估计。这种非线性形状是有金融逻辑的。波动率升到极端水平往往意味着市场正在处理某种集中释放的负面信息这时流动性变差、止损盘集中涌出收益左尾的恶化速度会远超线性外推的预期。反过来在正常波动区间波动率上升对尾部的影响相对温和。所以f_τ(σ_t)的形状本身就是市场尾部行为的一张素描图。初稿阶段我用B样条自由度控制在4到6之间就是为了能捕捉到主要弯曲又不会陷入局部噪声的抖动。3. 参数估计与实操配置3.1 两步估计法稳定优先GQNR的参数估计我强烈建议用两步法而不是一步联合估计。第一步先估计GJR-GARCH得到σ_t序列第二步把σ_t作为解释变量分别对每个τ做带非线性基函数的分位数回归。一步联合估计在理论上更优雅因为它能把GARCH参数的不确定性传导到分位数回归里。但实际操作中联合估计的非线性目标函数非常复杂数值优化经常不收敛或者收敛到奇怪的局部最优解。我初版试过联合估计调试成本翻了好几倍换来的收益却很不明显。两步法的优点在于每一步都有成熟的算法出了结果也容易诊断波动率拟合得对不对看标准化残差的Ljung-Box检验分位数回归拟合得对不对看覆盖率检验问题出在哪一层一目了然。两步法的代价是σ_t的估计误差没有被传导到第二步这会导致分位数回归的标准误略微偏小。初稿阶段我选择接受这个偏差汇报时明确标注这一点对后续研究的解释是够用的。3.2 参数选择与窗口设置下面这组参数是我在初稿阶段反复试过之后留下的基准配置参数基准配置备注GARCH模型GJR-GARCH(1,1)杠杆效应不显著时退回GARCH(1,1)收益率频率日频同时准备5日重采样做稳健性检验分位点集合0.01, 0.05, 0.25, 0.50, 0.75, 0.95, 0.99覆盖左右尾与中位数训练窗口252个交易日约一年滚动前进B样条自由度5太少欠拟合太多过拟合惩罚项暂不加用样本外检验监控过拟合异常值处理1%和99%缩尾防止个别极端值扭曲样条边界训练窗口设为252是兼顾了估计稳定性和时变性。窗口太短比如60天GARCH参数方差很大B样条在尾部边界处几乎没法用窗口太长比如1000天参数对结构变化的响应太慢遇到市场风格切换时会滞后很久。252天是一个经验上还不错的起点另外建议跑一个窗口敏感性分析分别看126天和504天的结果确认模型结论不是某一个窗口长度特有的偶然现象。GARCH的初始值设置也有一点讲究。方差方程里的ω初始值可以取样本无条件方差的(1-α-β)倍σ_0的初始值直接设成样本标准差。这样设置能让优化过程少走很多弯路避免初始参数落在不可行域里导致迭代不收敛。3.3 用覆盖率检验判断分位数预测是否合格很多人做分位数模型只盯着回归的拟合优度看这是不对的。分位数模型的检验逻辑应该是覆盖率检验假如你预测的是5%分位点那么在实际样本中真实收益率低于这个预测值的比例应该接近5%。具体做法是用滚动窗口计算每一天的Q̂_τ(r_{t1})然后统计全样本中r_{t1}Q̂_τ的天数占比π。如果π显著高于τ说明模型系统性高估风险预测偏保守如果π显著低于τ说明模型低估尾部风险这是更危险的错误。可以用Kupiec的LR统计量做正式检验LR -2 · ln[(1-τ)^(T-N) · τ^N / ((1-π)^(T-N) · π^N)]其中N是实际突破预测分位数的天数T是总天数。LR近似服从χ²(1)超过3.84就说明在5%显著性水平下覆盖率不合格。这个检验可以很快找到问题是在GARCH层还是QR层比如某资产所有τ的覆盖率都系统性偏低那大概率是波动率状态变量提取不到位需要回第一层修正。4. 把模型输出变成可执行的交易信号4.1 从分位数序列到风险触发信号分位数预测本身不是交易信号它只是一组分布信息。要让模型真正指导仓位需要把Q̂_τ转换成可执行的规则。初稿阶段我设计了三个比较简单、不太容易过拟合的信号规则。第一个是左尾恶化信号。当Q̂_{0.05}低于它自身过去20日的20%分位数时认为左尾风险正在异常扩大目标仓位下调到基准仓位的50%。这个信号捕捉的是分位数预测的突变比单纯看数值绝对值要稳健因为不同资产的分位数基准水平差异很大。第二个是波动状态确认信号。当σ_{t1|t}高于其自身过去一年序列的75%分位数时禁止任何加仓动作只允许减仓或维持现状。这个信号起的是过滤器作用防止在波动率极端膨胀的环境里盲目抄底或追涨。第三个是右尾强化信号。当Q̂_{0.95}高于过去20日的80%分位数同时σ_t没有处于极端高位时可以适当增加20%的风险暴露。右尾走强意味着上涨行情有延续性但要先确认波动状态正常两个条件都得满足。这三个规则都不是从历史数据里挖出来的复杂条件组合而是基于很朴素的直觉左尾恶化就收缩波动过大就降低进攻性右尾走强且环境正常就加大进攻。逻辑简单的好处是回测结果可信度高不容易陷入数据挖掘偏差。4.2 一个可以直接跑起来的回测骨架我把回测框架写成walk-forward形式这是时间序列回测的基本要求。每一步只用截至当前时刻的数据做估计绝不用未来信息填充训练集。# 伪代码GQNR walk-forward 回测骨架 T len(returns) window 252 equity [1.0] for t in range(window, T - 1): # 第一步GJR-GARCH 估计与一步预测 train_ret returns[t - window:t] garch_model fit_gjr_garch(train_ret) sigma_next garch_model.forecast_step(1) # 第二步分位数回归拟合与预测 sigma_hist garch_model.fitted_sigma qr_05 fit_quantile_regression(train_ret, sigma_hist, tau0.05, spline_df5) qr_95 fit_quantile_regression(train_ret, sigma_hist, tau0.95, spline_df5) q05 qr_05.predict(sigma_next) q95 qr_95.predict(sigma_next) # 第三步信号生成 signal generate_position_signal(q05, q95, sigma_next) # 第四步组合收益扣除简单交易成本 trade_cost 0.0005 * (signal ! last_signal) equity.append(equity[-1] * (1 signal * returns[t 1] - trade_cost)) last_signal signal这个骨架非常朴素但信息结构是干净的t时刻的sigma_next只依赖于截至t的收益序列QR模型也只用了截至t的数据。每一步都在做真实的前向预测没有未来函数。需要单独提醒一点trade_cost的计算方式决定了回测结果离真实有多远。初稿阶段我按单边万五来算看起来不高但如果信号频繁翻转成本会迅速吃掉收益。跑完回测后建议做一个成本敏感性测试把成本从万五分别改成万二和千二观察策略从正收益变成负收益的临界点在哪里。4.3 回测评估指标的选取逻辑评估GQNR这种模型不能只看年化收益和夏普比率。我建议至少盯住一张包括四项指标的表指标作用初稿接受标准年化收益率策略赚钱能力比基准高即可不追求暴利最大回撤风险底线不超过基准的1.5倍分位数覆盖率模型预测是否合格每个τ的LR都不显著偏离换手率成本消耗速度月换手率低于200%分位数覆盖率是GQNR特有的检验项它不直接反映盈亏但直接反映模型是否在正确的分布上做决策。如果覆盖率都不合格即使回测曲线很好看大概率是某种偶然的数据噪声不是模型在起作用。我习惯把基准设为最简单的等权买入持有或者等权风险平价组合。GQNR策略如果连简单的基准都跑不赢说明尾部风险信号对仓位调整的贡献是负面的这种情况下应该回去调模型而不是调回测规则。5. 踩过的坑与问题排查实录5.1 GARCH参数对初始值极度敏感我第一次跑GARCH时用的是默认初始值结果ω经常跑到几乎为0α和β的估计也和预期差得很远。后来发现是初始值设置太随意对数似然函数存在多个局部最优解单纯靠优化器从默认起点出发很容易掉进坏解。之后我把初始值改成这样σ_0设为训练集标准差ω设为无条件方差的(1-α-β)倍α设为0.05β设为0.90。收敛速度立刻变快估计结果也稳定很多。如果换了资产还是不稳定建议用多个随机起点跑十次看参数分布是否集中。参数分布散得厉害的话大概率是样本窗口太短而不是优化器的问题。5.2 分位数交叉问题分位数回归直接对每个τ独立求解可能会出现Q̂_{0.05}高于Q̂_{0.50}这种理论上不允许的情况。这在B样条自由度偏高时尤其容易发生因为样条在尾部区间的弯曲幅度太大容易把低分位数的曲线抬到中位数之上。初稿阶段我用一个简单手段缓解先估计τ0.50的中位数曲线然后对τ0.50的曲线加上负偏移约束对τ0.50的曲线加上正偏移约束。更严格的方案是用单调分位数回归但实现复杂度上升不少初稿可以先不做。5.3 生成回归元带来的隐性偏差两步法的第二步里σ_t是估计出来的变量而不是真实值这在计量上属于生成回归元问题。标准误和参数估计都会受到影响。初稿阶段我接受这个偏差但心里有数最后报告覆盖率时如果有条件应该用bootstrap重新计算标准误避免对模型显著性做出过度乐观的判断。5.4 换一个标的就失效的典型案例GQNR在某指数上好用不代表换到商品期货上也好用。我试过一个商品品种发现B样条在高波动区的斜率估计极不稳定覆盖率检验也不通过。排查后发现是标的的流动性不够日收益率里频繁出现跳空GARCH对这种跳空结构拟合得很差。处理方式是先对收益率做缩尾处理再用5日重采样把日频噪声压下去同时把换手率限制加进信号层。经过这三步调整后覆盖率才勉强回到可接受区间。这件事的教训是GQNR不是开箱即用的通用模型每个标的都需要重新做一轮标的适配诊断。5.5 回测结果好到不真实时先查未来函数有一次我在某版回测里看到年化收益高得离谱第一反应不是庆祝而是检查代码。最后发现问题是标准化残差的z_t被错误地用于样本外预测导致模型间接使用了当天收盘后的信息。这类未来函数错误非常隐蔽往往藏在数据预处理环节。排查方法是把每一天的数据生成流程列成时间线确认任何t时刻变量都只依赖t日及之前的信息。下面是一个按症状分类的排查表遇到问题可以直接对照症状可能原因排查方向分位数预测出现交叉各τ独立拟合且样条弯曲过度加偏移约束或降低自由度GARCH参数估计不稳定初始值不合理或窗口太短改用经验初始值增加窗口覆盖率系统性偏低尾部风险被低估换GJR-GARCH增加非线性自由度回测换手率过高信号阈值设置太近加死区设置最小调整比例换标的后模型失效数据特性与默认参数不匹配重跑标的适配流程回测收益异常高存在未来函数逐日检查信息时间线6. 初稿的边界和下一步可以走的路6.1 当前版本有哪些不完善初稿的定义域必须划清楚。当前实现只处理单一标的没有纳入资产间的协方差结构所以它回答不了组合层面“两个资产同时在尾部恶化的概率”这类问题。分位数τ也是静态的不会随市场状态变化模型没有自适应切换风险偏好的能力。再者GARCH参数估计误差没有传导到分位数层严格来说统计推断的完整性还差一步。这些限制不是缺陷而是初稿该有的样子关键是别在汇报时把它们藏起来。6.2 值得优先尝试的三个扩展方向第一个方向是把GARCH换成DCC-GARCH先估计多资产的动态条件协方差再对组合收益率做条件分位数预测。这样一来GQNR就能从单标的尾部风险工具升级成组合风险预算工具信息价值会大很多。第二个方向是引入时变的τ选择机制。比如根据波动率状态自动调整分位点高波动时关注0.01分位点正常波动时关注0.05分位点。这比固定分位点更贴近真实风险管理需求。第三个方向是贝叶斯分位数回归。用MCMC或变分推断获得参数后验分布这样每个预测的分位数都带一个不确定性区间信号触发时可以判断“是确定性的恶化还是噪声扰动”能明显减少假信号。从初稿到可用的过程本质上是不断在模型复杂度和稳健性之间做权衡。GQNR目前还站在“框架有价值”这个阶段离“策略产品化”还有不少距离。如果让我回到初稿阶段重新过一次我会先把样本外覆盖率检验做成每次回测的第一项产出而不是先折腾信号生成和绩效指标。分位数预测如果覆盖率不对后面的一切决策判断都是空中楼阁。覆盖率稳定过关之后再加交易成本、再谈仓位调整这条路走起来会顺很多。