TVP-VAR-SV:时变参数 VAR 模型的完整 Python 实现

一句话:把宏观与金融实证里最常用的 TVP-VAR-SV 模型——时变参数 + 随机波动率——做成一套开箱即用的 Python 实现:完整的贝叶斯 MCMC、时变溢出分解、脉冲响应,再配一个打开浏览器就能用的界面。


你大概遇到过这些

变量之间的传导关系,危机前后明显不一样,可模型里只有一个固定系数; 政策冲击的效果,十年前和现在不是一回事; 残差波动有明显的聚集,模型却假设它是常数。

这些正是 TVP-VAR-SV 要回答的问题——它在三个层次上都允许参数随时间变化。

但真动手时门槛不低:Gibbs 抽样要自己搭;Carter-Kohn 前向滤波后向抽样要自己写; 随机波动率不是线性高斯,得用混合正态去近似;链一旦不收敛,还很难定位原因。 好不容易跑通,时变溢出、脉冲响应、对冲比率又要自己从头算……

结果 80% 的时间花在实现上,只有 20% 在做研究。

导入数据后立刻给出样本量、序列数与区间,11 个商品价格序列一览

模型:三处时变性

TVP-VAR-SV 的观测方程是

1
y_t = X_t β_t + A_t⁻¹ Σ_t^{1/2} ε_t

和普通 VAR 相比,它多了三个状态方程,把三样东西都设成随时间的随机游走:

状态 刻画什么 状态方程
β_t 时变系数——变量之间的传导关系 β_t = β_{t-1} + u_t,u_t ~ N(0, Q)
a_t 时变联立关系——同期冲击的相互影响 a_t = a_{t-1} + v_t,v_t ~ N(0, S)
h_t 随机波动率(h = log σ) h_t = h_{t-1} + w_t,w_t ~ N(0, W)

这是 Primiceri (2005) / Nakajima (2011) 的标准设定,也是宏观与金融实证里引用最多的版本。

为什么值得这么麻烦? 因为它把"参数不稳定"从一个需要做稳健性检验的麻烦, 变成了模型本身就能刻画的对象——你可以直接把系数路径和波动率路径画出来, 看清它在哪一年、因为什么事件发生了变化。

滚动窗口分析:观察模型关系在不同样本区间内的稳定性

实现难在哪

真正写过的人才知道,难点集中在三处:

① 状态维度高。 β 的维度是 n(np+1)——三个变量、两阶滞后就是 21 维, 而且每一期都要抽一次。这是典型的"高维状态空间 + 时间维"问题, 朴素做法在数值上非常脆弱。

② 随机波动率不是线性高斯。 把 log(ε²) 展开会得到 log(ε²) = 2h + log(χ²₁),最后那一项不是正态的, 所以状态空间方法不能直接用——必须用混合正态去近似它。

③ 弱识别。 系数游走方差 Q 和波动率 σ 会相互替代: Q 一大,系数就能自己漂移着把数据的异方差"吸收"掉, 于是抽样器把 σ 压向零。似然几乎不惩罚这件事,只有先验能约束它。

这三点如果没处理好,表现是看起来跑完了、结果却不对——比直接报错更难发现。

溢出分析:把预测误差方差分解成各来源的贡献

Python 实现

整套模型实现为一个独立的 Python 包,不依赖任何商业软件:

抽样框架——Primiceri / Nakajima 的传统做法,逐块共轭更新:

模块 方法
时变系数 β_t、联立关系 a_t Carter-Kohn 前向滤波后向抽样(FFBS)
随机波动率 h_t Kim-Shephard-Chib 混合抽样 + FFBS
Q / S 逆 Wishart(Bartlett 分解)
W 逆 Gamma

实现过程中解决了几个容易被忽略的坑,这里挑两个说:

KSC 混合表的均值是错的。 文献里广为流传的那张 7 分量混合参数表, 隐含均值恰好为 0;但 log(χ²₁) 的真均值是 ψ(1/2) + log 2 = −1.2704。 用错均值不只是导致偏移,更糟的是制造伪不动点——各分量均值相距很远, 起点稍偏就会锁死在错误分量上,波动率路径再也走不动。 这里用 EM 重新拟合了这张表(均值 −1.2726、方差 4.9466,与理论值一致)。

Q 的先验必须真的起作用。 逆 Wishart 先验的影响力通过尺度矩阵而非自由度体现, 后验自由度约为 q_df + T − 1,尺度设小了先验形同虚设。 这里按 Primiceri 的做法把 Q 锚定在 OLS 系数的抽样方差上, 并在模型自模拟数据上做了标定扫描。

估计完成后直接给出保留抽样数、总溢出指数与变量清单
脉冲响应:标准模式与严格模式可以直接对照

验证:不看图"像不像",看数字

模型类代码最怕"跑出来了但不对"。这里的做法是把每个模块单独拎出来验证:

  • FFBS 单独验证——用已知的随机游走状态生成观测,看能否复原: 后验路径与真值的相关性 0.995;
  • 随机波动率分块单独验证——固定真实残差只跑 SV 那一块, 收敛到真值(比值 0.92–1.10),说明 SV 模块本身没有偏差;
  • 模型自模拟数据验证——从模型本身生成数据再估计: σ 恢复比 0.92、路径相关性 0.78;
  • 115 项单元测试覆盖全部函数。
1
2
3
4
5
FFBS 后验 vs 真值            相关性 0.995
随机波动率分块 比值 0.92 – 1.10(收敛到真值)
模型自模拟:σ 水平恢复比 0.92
模型自模拟:σ 路径相关性 0.78
单元测试 115 项全部通过
描述统计:统计量表 + 相关系数热力图 + 收益率序列 + 分布直方图

一个验证陷阱,值得单独说

做逐位一致性验证时,报告一直显示"约一半的正态随机数差 1 ULP"。 这个比例很可疑,我一度以为抽样表写错了,还专门写了求解器去反解……

结果是:表本来就是对的。 真正的问题在这里:

1
2
3
float('-0.94724664395737113')          # -> -0.9472466439573711  正确
np.float64('-0.94724664395737113') # -> -0.9472466439573711 正确
pd.read_csv(...).to_numpy(float)[0] # -> -0.9472466439573712 错了!

pandas.read_csv 的默认解析器对浮点文本会有 1 ULP 误差。 用它读参考值去做逐位检验,会让正确的实现看起来是错的。

改一个参数就解决了:pd.read_csv(..., float_precision="round_trip")。 写在这里是因为——做数值工作的人迟早会踩到,而它会伪装成你自己的 bug。


可复现性:随机数流逐位可控

随机数是科学计算里最容易被忽视的可复现性来源:抽样器实现差一点,MCMC 结果就完全不同。

这套实现把随机数流做到了逐位可控——同一个种子,在任何机器上跑出来的结果完全一致。 正态抽样用的是 256 层 Ziggurat 拒绝采样(Marsaglia & Tsang, 2000), 实测每次尝试恰好消耗 2 个 32 位字、约 2.13% 被拒绝; 正因为拒绝路径也是逐位一致的,rand / randn / randi 三者交错调用后流位置依然对齐。

配套的验证框架在 320 万个取值上做了零失配检查。


界面:不写代码也能跑完全流程

除了当库用,还配了一个本地 Web 工作台——打开浏览器就能用, 不需要联网(图表库已本地化打包)。11 个功能页:

数据 → 描述统计 → 单位根检验 → 溢出分析 → 净溢出 → 滚动窗口 → TVP-VAR-SV 估计 → 脉冲响应 → 对冲分析 → 论文版式图 → 数值验证

严格模式的 MCMC 是真在跑的贝叶斯抽样,带实时进度条

每一步都能看到原始数值,也都能导出——不是黑箱。

论文版式图库:网格划分与配色按期刊论文惯例,可直接用于写作

除了估计,还能直接得到什么

TVP-VAR-SV 的价值不止在系数本身,配套的几样分析这里都做好了:

  • 波动率溢出分解——广义预测误差方差分解(Diebold-Yilmaz), 含总溢出指数与方向性溢出;
  • 时变溢出——每个时点的溢出结构,看传导关系如何演化;
  • 脉冲响应——含常数项 VAR 的 Cholesky 正交化响应;
  • 对冲与组合——动态对冲比率、最优权重、对冲有效性;
  • 单位根检验——ADF-GLS(Elliott-Rothenberg-Stock)。
净溢出:各序列在滚动窗口内的净溢出强度
对冲分析:动态对冲比率、最优权重与对冲有效性

适合谁

  • 做 宏观 / 金融时变特征实证的研究生与青年教师;
  • 需要波动率溢出、有连接性分析的课题组成员;
  • 想快速试一个模型、不想先折腾两周环境的分析师;
  • 教书时需要一个能当场演示、不用配环境的工具。

数据格式很宽松:首列是日期、其余是数值序列即可,CSV / Excel 都能直接导入。


最后

整个项目已经整理好:完整的模型实现 + 11 页操作界面 + 验证与测试框架, 115 项单元测试、320 万取值零失配检查全部通过。

如果你正在做时变参数、随机波动率或溢出分析,应该能直接省下不少时间。

📮 需要的加我微信:canglang12002(任公子),备注「TVP-VAR」优先通过。


本文由项目自带的发布脚本生成。所有数据、接口与结论均可在本地复现;生成时间:2026-09-26 22:56:53。