TVP-VAR 与风险溯源
Time-Varying Parameter VAR
当大缓和、全球金融危机、疫情冲击接连重塑宏观经济结构,常系数 VAR 只能给出"平均效应"。TVP-VAR 让系数、协方差、波动率都随时间漂移,再结合 Diebold–Yilmaz 连通性指数,从方差分解里读出风险从哪里来、向哪里去。
numpy、pandas、statsmodels;进阶用 pyMC)。MCMC 建议多核。01 为什么需要时变参数
经典 VAR(Sims 1980)假设系数 $B_1,\dots,B_p$ 和扰动协方差 $\Sigma$ 在整个样本期内不变。这是一个极强的假设。半个世纪的美国宏观数据至少经历了三次结构性断裂:
- 大缓和(Great Moderation, ~1984–2007):产出与通胀波动率系统性下降,货币政策对通胀的反应系数(泰勒原则)显著增强。
- 全球金融危机(2008–2012):金融摩擦突然活跃,利率传导渠道被零利率下限(ZLB)冻结,传统货币政策失效。
- 疫情冲击(2020–):供给侧停摆 + 财政货币双宽松,通胀动态与以往任何一次都不同。
如果用整条样本估一个常系数 VAR,你得到的是这三个体制的"加权平均"——它既不能回答"2008 年货币政策到底有效没有",也不能回答"沃尔克时期和格林斯潘时期的政策反应函数是否不同"。时变参数 VAR(TVP-VAR)让系数 $B_t$、结构性冲击协方差 $A_t$、波动率 $\Sigma_t$ 都按随机游走漂移,从而允许模型在每一个时点 $t$ 都有自己的"小型经济"。
常系数 VAR = 用一条直线拟合整条数据;TVP-VAR = 用一条弯曲的、随时间缓慢移动的线拟合,且弯曲的幅度由数据(先验 + 似然)决定。它比"分段 VAR / Chow 检验"灵活——不需要事先知道断点在哪。
TVP-VAR 与其他"结构变化"模型的取舍
时变参数并不是唯一的建模范式,常见的三条路线各有取舍:
| 模型 | 结构变化形式 | 优点 | 局限 |
|---|---|---|---|
| 常系数 VAR | 不变 | 估计简单、推断干净 | 无法捕捉体制变化 |
| 马尔可夫区制 MS-VAR | $K$ 个离散体制之间跳变 | 可识别"扩张/衰退"体制 | 必须事先指定体制数 $K$,且体制切换是离散跳跃 |
| 门槛 TVAR / 阈值 VAR | 按某变量阈值切换 | 有明确经济学解释(如信贷周期) | 门槛变量外生给定 |
| TVP-VAR | 系数/协方差/波动率连续漂移 | 不预设断点、连续平滑、适合慢变化 | 计算重、参数多、MCMC 易不收敛 |
宏观结构变化在大多数场景下是缓慢连续的(大缓和用了二十年),而非一夜之间跳变,因此 TVP-VAR 比 MS-VAR 更贴近现实。但如果你研究的是"零利率下限前后"这种明确的离散体制,MS-VAR 或 MS-DSGE 反而更合适。
02 TVP-VAR 模型设定
设 $y_t$ 是 $n\times 1$ 宏观向量(如通胀、产出缺口、利率)。Primiceri (2005) 形式的 TVP-VAR 写成:
其中 $X_t = I_n \otimes [1, y_{t-1}',\dots,y_{t-p}']$,$\beta_t$ 是堆叠起来的时变系数向量。三类时变参数:
- $\beta_t$:时变 VAR 系数。它告诉你"产出对利率的反应"或"央行对通胀的反应"如何随时间变化。
- $A_t$:下三角单位对角矩阵,刻画同期变量之间的结构性关系(Cholesky 分解的时变对应物)。
- $\Sigma_t = \mathrm{diag}(\sigma_{1,t},\dots,\sigma_{n,t})$:随机波动率。它允许冲击本身的方差随时间漂移——这对金融数据尤其关键,2008 年的冲击"大小"就该比平时大。
把它们都写成随机游走状态方程:
三个状态扰动 $u_t, v_t, w_t$ 互相独立,协方差分别为 $Q, S, W$。随机游走先验意味着:参数可以任意漂移,但不会爆炸——这是 TVP-VAR 相比"每年重估一个 VAR"的关键优势。
03 状态空间表示与估计
把 Eq. 2.1 当作观测方程、Eq. 2.2 当作状态方程,TVP-VAR 就是一个非线性、非高斯的状态空间模型。由于 $A_t$ 和 $\Sigma_t$ 进入观测方程的方式是非线性的,卡尔曼滤波的闭式递推不能直接用。实务中用 Bayesian MCMC(Gibbs 抽样):把高维后验分解成若干条件后验,逐块抽样。
TVP-VAR 参数维度高(一个 $n=3,p=2$ 的系统,$\beta_t$ 就有 $n(np+1)=21$ 个时变系数),链很容易不收敛。必须报告:(1) 多链 Gelman–Rubin R-hat;(2) 关键参数的 trace plot;(3) 自相关图。R-hat > 1.1 就重跑。
04 Primiceri (2005) 框架
Primiceri 在 Review of Economic Studies 2005 年的文章 "Time Varying Structural Vector Autoregressions and Monetary Policy" 把上面这套东西第一次完整地、可复现地写了出来,并用它回答了宏观经济学的一个经典问题:战后美国货币政策到底变了没有?他的三点贡献至今仍是标准做法:
- 时变系数 $\beta_t$:刻画政策反应函数和传导机制的缓慢漂移(如沃尔克时代到格林斯潘时代)。
- 时变协方差 $A_t$:允许同期结构关系本身变化(如利率对产出的同期反馈)。
- 随机波动率 $\Sigma_t$:允许冲击大小变化。这一点尤其重要——如果不允许波动率时变,MCMC 会把"波动率的突变"错误地归因到"系数的突变"上。
同时期 Cogley & Sargent (2005, Review of Economic Dynamics) 的 "Drifts and Volatilities" 也独立做了几乎一样的事,但不允许 $A_t$ 时变。两篇文章合在一起,奠定了 TVP-VAR 文献的基础。
识别上,Primiceri 沿用了 VAR 文献最经典的 Cholesky 短期约束:把 $A_t$ 设为下三角,按"产出—通胀—利率"排序,假设利率在当期不影响产出和通胀。这套排序对货币政策冲击识别是标准的,但审稿人会要求你做排序稳健性(换利率到最后、用符号约束替代 Cholesky)。做中文应用时,变量排序要结合中国的政策现实——DR007/MLF 这类政策利率往往被排在最后。
05 完整 MATLAB 代码框架
完整 Primiceri 估计器约 1500 行 MATLAB,本页给出核心骨架,并指向官方工具箱。生产环境强烈建议直接用下面两个开源工具箱,不要自己重写 MCMC。
% ==============================================================
% TVP-VAR 估计核心骨架(Primiceri 2005 简化版)
% 生产环境请直接使用:
% - Gary Koop & Dimitris Korobilis 的 TVP-VAR 工具箱
% (https://sites.google.com/site/garykoop/)
% - Primiceri 个人主页提供的 replication code
% 本文件只演示"如何组织数据 + 调用 FFBS + 保存后验"
% ==============================================================
clear; clc; rng(2024);
% ---------- 0. 读数据:n x T ----------
% y 三列: [inflation, unemployment, interest_rate]
load us_macro.mat;
y = us_macro;
[T, n] = size(y);
p = 2; % VAR 滞后阶数
m = n*(n*p + 1); % beta_t 的维度
% ---------- 1. 堆叠 VAR 右侧 ----------
Y = y(p+1:end, :);
[T2, ~] = size(Y);
X = zeros(T2, m);
for t = 1:T2
xt = 1;
for lag = 1:p
xt = [xt, y(p+t-lag, :)];
end
X(t,:) = kron(eye(n), xt);
end
% ---------- 2. 先验(Primiceri 2005 第 4 节) ----------
B0_hat = (X'*X)\X'*Y(:); % 用全样本 OLS 做初值
beta_0 = zeros(m,1);
Vbeta_0= 4*eye(m); % 扩散先验
s2 = 4; % 超参数尺度
Q = (s2+1)*eye(m)*0.01; % 状态协方差初值
% ---------- 3. MCMC 主循环(示意 5000 次) ----------
nrep = 5000; nburn = 2000;
beta_draws = zeros(nrep, T2, m);
for irep = 1:nrep
% (a) FFBS 抽 beta_t(给定 A, Sigma)
% beta_t 状态方程: beta_t = beta_{t-1} + u_t, Var=Q
% 观测方程: Y = X*beta + A^-1*Sigma*eps
beta_draws(irep,:,:) = FFBS_linear(Y, X, Q, beta_0, Vbeta_0);
% (b) 抽 A_t(下三角同期结构)
% A_draws(irep,:,:) = FFBS_A(...);
% (c) 抽 Sigma_t(随机波动率,KSC 1998 mixture)
% Sigma_draws(irep,:,:) = KSC_sv(Y, beta_draws(irep,:,:));
% (d) 抽超参数 Q(Inverse-Wishart 后验)
% Q = iwishrnd(...)
if mod(irep,500)==0
fprintf('MCMC iteration %d / %d\n', irep, nrep);
end
end
% ---------- 4. 后处理:剔除 burn-in ----------
post_beta = beta_draws(nburn+1:end,:,:);
% 平均时变系数、后验分位数
beta_med = median(post_beta, [1 3]);
% ---------- 5. 计算时变脉冲响应 ----------
% 在每个时点 t 用后验 beta_t, A_t, Sigma_t 构造 VAR companion form,
% 向前迭代 h=0..20, 得到 IRF(t, h, shock)
% plot(0:20, squeeze(IRF(2008,:,3))) % 2008 年货币政策冲击对产出
% plot(0:20, squeeze(IRF(2020,:,3))) % 2020 年同一冲击
FFBS(Forward-Filter Backward-Sampling)和 KSC 随机波动率抽样都是数值上很微妙的环节。Gary Koop 与 Dimitris Korobilis 的 MATLAB 工具箱在论文里公开发布,被引用数千次;直接下载 tvp_var/ 目录,把数据矩阵换成你的即可。
06 完整 Python 代码:简化版 TVP-VAR
下面用 numpy 实现一个简化版 TVP-VAR:只让系数 $\beta_t$ 随机游走,固定 $A_t$、$\Sigma_t$,用卡尔曼滤波 + 前向过滤后向抽样。它不能替代 Primiceri 完整估计器,但足以让你理解"时变系数如何被估计出来"。
# ==============================================================
# 简化 TVP-VAR:时变系数 + 固定协方差
# 教学用途。完整 Primiceri 估计见上节 MATLAB 工具箱。
# 依赖: pip install numpy pandas statsmodels matplotlib
# ==============================================================
import numpy as np, pandas as pd, matplotlib.pyplot as plt
from scipy import stats
np.random.seed(0)
# ---------- 0. 模拟一段数据:真实 beta_t 缓慢漂移 ----------
T, n, p = 400, 2, 2
# 真实系数随时间随机游走(演示"结构变化")
beta_true = np.zeros((T, n*(n*p+1)))
beta_true[0] = np.tile([0.5, -0.2, 0.1, 0.3, 0.0, 0.2], 1)
for t in range(1, T):
beta_true[t] = beta_true[t-1] + np.random.normal(0, 0.02, beta_true.shape[1])
y = np.zeros((T, n))
y[:p] = np.random.normal(0, 1, (p, n))
for t in range(p, T):
xt = np.concatenate([[1.0], y[t-1], y[t-2]]) # 1 + 2*2 = 5 维
# 注意: 这里演示用 n=2, m=5; 与上面 beta_true 维度示意略作简化
y[t] = xt @ beta_true[t][:5].reshape(n, 5).T + np.random.normal(0, 0.3, n)
# ---------- 1. 构造观测矩阵 X ----------
m = n*(n*p + 1)
X = np.zeros((T-p, m))
Y = np.zeros((T-p, n))
for i, t in enumerate(range(p, T)):
xt = np.concatenate([[1.0], y[t-1], y[t-2]])
X[i] = np.tile(xt, n)
Y[i] = y[t]
# ---------- 2. 卡尔曼滤波 (线性高斯状态空间) ----------
# 观测: Y_t = X_t * beta_t + eps, eps ~ N(0, R)
# 状态: beta_t = beta_{t-1} + eta, eta ~ N(0, Q)
Q = np.eye(m) * 0.02**2 # 状态扰动方差(关键超参)
R = np.eye(n) * 0.3**2 # 观测噪声
beta_filt = np.zeros((T-p, m))
P = np.eye(m) * 10 # 初始状态方差
beta_tt = np.zeros(m)
for i in range(T-p):
# predict
beta_pred = beta_tt
P_pred = P + Q
# update
S = X[i].reshape(1,-1) @ P_pred @ X[i].reshape(-1,1) + R
K = P_pred @ X[i].reshape(-1,1) / S
innovation = Y[i] - X[i]*0 # 简化为单变量情形(演示)
beta_tt = beta_pred + (K.flatten() * (Y[i,0] - X[i] @ beta_pred))
P = P_pred - K @ X[i].reshape(1,-1) @ P_pred
beta_filt[i] = beta_tt
# ---------- 3. 画出"估计出的时变系数" ----------
plt.figure(figsize=(10,5))
plt.plot(beta_filt[:,0], label='filtered beta(1,1)')
plt.plot(beta_true[p:,0], 'k--', label='true beta(1,1)')
plt.legend(); plt.title('TVP-VAR filtered coefficient')
plt.savefig('tvpvar_filtered.png', dpi=120, bbox_inches='tight')
print("已保存 tvpvar_filtered.png")
# ---------- 4. 后续:在每个时点做时变 IRF ----------
# 对每个 t, 用 beta_filt[t] 构造 companion matrix,
# 迭代 h 步, 得到 IRF(t, h, shock_j)
# 比较 2008 vs 2020 的 IRF 曲线(见下一节)
07 脉冲响应的时变特征
TVP-VAR 最有价值的产出不是 $\beta_t$ 本身,而是时变脉冲响应函数 $\mathrm{IRF}_t(h)$:在每一个时点 $t$,用该时点的 $\beta_t, A_t, \Sigma_t$ 构造 companion form,向前迭代 $h=0,1,\dots,H$ 步。于是你得到一张三维图 $\mathrm{IRF}(t,h,j)$,可以横切(固定 $h$,看 $t$ 的变化)也可以纵切(固定 $t$,看 $h$ 的路径)。
典型发现:
- 货币政策效应在大缓和时期更弱:1985–2007 年间,利率冲击对产出的 IRF 绝对值比 1970 年代小——这是"预期管理成功、央行可信度提高"的证据。
- 2008 vs 2020 的对比:2008 年危机中,利率冲击几乎传不到实体经济(零利率下限 + 银行惜贷);2020 年疫情中,财政与货币双宽,IRF 形状又恢复。
- 通胀对利率冲击的反应符号会翻转:某些时期出现"价格之谜"(price puzzle),某些时期没有——这本身就是宏观结构变化的证据。
标准做法:画一张热图(横轴 $h$、纵轴 $t$、颜色 = $\mathrm{IRF}_t(h)$),再在热图上叠加几条"切片曲线"(选 2008Q4、2015Q4、2020Q2 三个时点的 $\mathrm{IRF}_t(h)$ 叠加在一张小图上)。
需要强调的是:TVP-VAR 的 IRF 不是"点估计 + 单一置信带",而是整个 MCMC 后验。每一次 MCMC 抽样都能画出一条 IRF 曲线,你最终画的是几千条曲线的分位数(16%/50%/84%)。如果某次抽样的 IRF 在 2008 年符号为正、2010 年符号为负,那你的"时变效应"很可能只是后验分布的扩散,而非真正的结构性变化。判断标准是:不同时点 IRF 的后验中位数要显著分离,且各自的 68% 区间不互相覆盖,才能说"政策效应确实变了"。
08 高维网络分析入门:Diebold–Yilmaz 连通性
当 $n$ 很大(10+ 变量)时,TVP-VAR 的参数爆炸(每个变量就有 $n(np+1)$ 个时变系数)。Diebold & Yilmaz (2014, Journal of Econometrics) 提出了一条轻量路线:用常系数或时变 VAR 的广义预测误差方差分解(FEVD),把它写成一个连通性矩阵,再解读为网络。
直观解释:$C_{i\leftarrow j}$ 是"变量 $i$ 的未来 $H$ 步预测误差方差里,有百分之几来自变量 $j$ 的冲击"。把 $n\times n$ 的 $C$ 矩阵画成热力图,就是一张风险传导网络——对角是"自相关",非对角是"溢出"。Total Connectedness 衡量整个网络的"总连通度"。
把它和 TVP-VAR 结合:在每个时点 $t$ 用估计出的 $\beta_t, A_t, \Sigma_t$ 算 $C_t(H)$,就得到一条时变连通性指数。2008 年 9 月、2020 年 3 月这种时点,Total Connectedness 会瞬间飙升。
09 风险溯源:从方差分解看风险来源
"风险溯源"(risk attribution / spillover source identification)就是回答:在某一次危机里,到底是谁把风险传染给了我?
这套方法在金融风险监测里被广泛使用:央行、证监会、IMF 的金融稳定报告常引用"基于 TVP-VAR-Diebold–Yilmaz 的时变溢出指数",用来回答"当前中国股市波动主要是受美股溢出还是自身因素驱动"。
从"总连通度"到"风险地图"
仅看一个 Total Connectedness 标量是不够的,真正有用的是横截面。把 $C_t(H)$ 矩阵沿时间切片,可以回答一组具体问题:
- 谁是常态源头?在非危机期,$S_{i,j,t}$ 长期为正的部门(通常是金融部门、能源部门)就是系统性重要节点,应该被宏观审慎监管重点盯防。
- 谁是危机触发器?在 2008Q4、2020Q1 这种时点,谁的"对外溢出"突然翻倍?Diebold–Yilmaz 文献发现 2008 年危机的源头是美国金融部门向实体部门溢出,2020 年疫情则是实体部门向金融部门反向溢出。
- 风险是怎么传导的?把 $C_{i\leftarrow j}$ 画成有向图,节点大小 = 总溢出,箭头粗细 = 成对溢出,就能画出一张"风险地图"。这张图通常是金融稳定报告的封面图。
Diebold–Yilmaz 原始版本用的是 广义方差分解(Koop–Pesaran–Shin 1998),不依赖 Cholesky 排序,因此对变量顺序不敏感——这正是它比传统方差分解受欢迎的原因。但广义分解不是结构识别,它告诉你的是"预测误差方差里有多少来自谁",而不是"外生结构冲击的因果效应"。要做因果政策分析,仍需回到 TVP-VAR 内部的 Cholesky / 符号约束识别。
10 论文案例与常见错误
论文案例
常见错误
TVP-VAR 参数维度高、后验平坦,前 5 万次抽样常常还在 burn-in。直接画 IRF 热图会得到"锯齿状噪声"。必须报告 R-hat(< 1.1)、trace plot、有效样本量(ESS > 数百),否则审稿人会直接要求重跑。
$Q$ 控制 $\beta_t$ 的漂移速度:太大 → 系数追逐噪声,IRF 热图全是抖动;太小 → 系数几乎不动,TVP-VAR 退化成常系数 VAR。Primiceri 推荐用 OLS 残差 + 小倍数(如 $Q=(\tau+1)^{-1}\cdot\mathrm{Cov}(\hat\beta_{OLS})$,$\tau=40$)作为初值,并做敏感性分析。
$n$ 从 3 涨到 10,$\beta_t$ 维度从 21 涨到 310,MCMC 链几乎不可能收敛。标准做法:(1) 限制 $n\le 6$;(2) 用因子增广 FAVAR 把高维信息压成几个因子再进 TVP-VAR;(3) 用 Koop–Korobilis 的 dynamic model averaging 允许变量集时变。
"$\beta_t$ 在 2015 年上升了 0.05"——这只是后验均值,置信区间可能跨零。TVP-VAR 给的是整个后验分布,必须带阴影区间画热图。只点估计、不画不确定性,等于把噪声当信号。
如果只让 $\beta_t$ 时变、不让 $\Sigma_t$ 时变,2008 年的"波动率暴涨"会被 MCMC 强行分配给"系数漂移",得到虚假的"2008 年政策反应函数突变"。Primiceri 2005 的核心贡献之一就是同时允许两者时变,不要为了省事砍掉 SV。
进阶代码资源
- Koop & Korobilis MATLAB TVP-VAR 工具箱:sites.google.com/site/garykoop — 直接可跑的 replication code。
- Primiceri 个人主页 replication files:NYU 经济系主页公开。
- R 包
bvarsv:Krueger 提供的贝叶斯 TVP-VAR 实现,适合小样本入门。 - Python
pyMC/statsmodels:本页简化版的生产化路径;完整 SV 建议直接调用 MATLAB 工具箱。