ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SSTVARToolbox实战:平滑转换向量自回归建模与非线性脉冲响应

SSTVARToolbox实战:平滑转换向量自回归建模与非线性脉冲响应

简介:SSTVARToolbox 是面向经济、金融时间序列分析人员的 MATLAB 工具包,专注平滑转换向量自回归(STVAR)模型的估计、检验与应用,适合已掌握基础 VAR 模型、希望进一步处理非线性动态关系的研究者与分析师。压缩包共 23 个文件,以 21 个 .m 脚本为核心,覆盖模型设定、参数估计、稳定性检验与脉冲响应分析等环节,另附 2 份 PDF 指南辅助理解,整体约 112KB,结构紧凑便于快速上手。目前已有 325 人学习下载。工具包提供从平滑转换函数与阈值选择、最大似然或 GMM 估计,到脉冲响应、方差分解及诊断检验的流程化支持,并配有应用示例,帮助读者将 STVAR 应用于宏观经济指标或金融市场数据,提升非线性结构下预测与政策评估的准确度。

1. 从一张脉冲响应图说起:SSTVARToolbox 到底解决什么问题

很多人第一次接触SSTVAR(平滑转换向量自回归)是在论文里看到一张非线性脉冲响应图——同一笔冲击,在区制一里三个季度就衰减干净,在区制二里却能拖两年还反弹。回头想用自己手头的宏观或金融月度数据复现,才发现普通 VAR 根本画不出这种曲线,因为它的系数矩阵是固定的,而 SSTVAR 的系数会随一个可观测的转换变量在两组参数之间平滑滑动。SSTVARToolbox就是冲着这个痛点来的:它把模型设定、非线性最小二乘估计、区制识别、广义脉冲响应这几步打包成一套可调用的流程,让你不用从零推导转移函数和雅可比矩阵。这篇笔记面向已经跑过 VAR、想往非线性方向走一步的从业者,也适合被导师要求复现 STVAR 结果的研究生。我会按「模型怎么立住 → 工具箱怎么跑通 → 参数怎么调 → 坑在哪」的顺序讲,所有步骤都能落到你自己的数据上。

2. 平滑转换向量自回归的数学骨架与选型理由

2.1 从线性 VAR 到 SSTVAR:多出来的那个转移函数

普通 VAR 的形式大家都熟:y_t = c + A_1 y_{t-1} + ... + A_p y_{t-p} + ε_t,系数矩阵 A 不随时间变。SSTVAR 在它基础上做了一件事——让系数在两组极端状态之间连续插值:

y_t = c + [A_1^L y_{t-1} + ...] * (1 - G(s_t)) + [A_1^H y_{t-1} + ...] * G(s_t) + ε_t

这里 L 和 H 分别代表低区制和高区制,G(s_t)是取值在 0 到 1 之间的转移函数,s_t是转换变量(可以是滞后的某个内生变量,也可以是外生的经济指标)。常见的转移函数有两种:逻辑型G = 1/(1+exp(-γ(s_t - c)))和指数型G = 1 - exp(-γ(s_t - c)²)。逻辑型描述的是「低于阈值一套规则、高于阈值另一套规则」的非对称切换,指数型描述的是「偏离阈值越远、非线性越强」的对称效应。

选型上我一般这样判断:如果你的经济故事是「衰退期和扩张期传导机制不同」,用逻辑型;如果是「极端冲击才有非线性、温和波动时接近线性」,用指数型。转换变量s_t的选择更关键——它必须理论上能驱动机制切换,实操里常用产出缺口、利率水平、波动率指数或者滞后的因变量本身。选错了s_t,估计出来的 γ 会不显著,模型退化成线性 VAR,白忙一场。

2.2 为什么不用门限 VAR 而用平滑转换

门限 VAR(TVAR)是硬切换,G只有 0 和 1 两个值,状态在阈值处瞬间跳变。SSTVAR 的平滑转换更贴近现实——经济体制的切换通常有过渡期,不是某一天突然全变。代价是估计更难:TVAR 可以按阈值分组做分段 OLS,SSTVAR 必须用非线性优化同时估 γ、c 和所有系数矩阵,目标函数非凸,对初值敏感。这也是为什么需要工具箱——手写这段优化,光雅可比矩阵就能让你调一整天。

提示:如果你的样本量小于 150 个观测点,先别急着上 SSTVAR。参数个数是线性 VAR 的两倍多,加上 γ 和 c,小样本下估计量偏差会大到没法解释。

2.3 工具箱里通常包含的模块

一套完整的 SSTVAR 实现,核心模块跑不出这几块:数据预处理与滞后阶选择、线性 VAR 初估(给非线性优化提供初值)、转移函数与似然函数构造、非线性最小二乘或极大似然估计、区制概率与转换变量时序图、广义脉冲响应函数(GIRF)。GIRF 这块要特别注意——非线性模型的脉冲响应依赖冲击发生时的历史状态和冲击大小,不能像线性 VAR 那样只画一条线,得对历史状态做蒙特卡洛积分。

3. 用 SSTVARToolbox 跑通第一个模型:从数据到脉冲响应

3.1 数据准备与滞后阶的确定

假设你手头是一份月度宏观数据,包含产出增速gdp、通胀cpi、利率rate三个变量,样本从 2005 年 1 月到 2023 年 12 月。第一步是整理成矩阵并确定滞后阶。工具箱一般要求输入T×K的数值矩阵,时间在行、变量在列。

import numpy as np import pandas as pd # 读取数据,确保时间列已解析为索引 df = pd.read_csv('macro_monthly.csv', parse_dates=['date']).set_index('date') data = df[['gdp', 'cpi', 'rate']].dropna() # 用 AIC/BIC 在滞后 1 到 8 阶之间选线性 VAR 的最优滞后 from statsmodels.tsa.api import VAR model = VAR(data) lag_order = model.select_order(maxlags=8) print(lag_order.summary()) p = lag_order.aic # 假设 AIC 选出 2 阶

这段代码先用statsmodels的select_order给出 AIC、BIC、HQIC 三个准则下的建议滞后阶。逻辑是:SSTVAR 的滞后阶通常直接沿用线性 VAR 的选择,因为非线性部分不改变滞后结构。参数上maxlags=8是月度数据的经验上限,季度数据可以设到 4。如果三个准则打架,优先看 BIC,它在大样本下更一致;样本小于 200 时看 AIC,避免欠拟合。

3.2 线性 VAR 初估:给非线性优化一个靠谱起点

非线性优化最怕初值乱给。工具箱的标准做法是先估一个线性 VAR,把系数矩阵作为低区制和高区制的共同初值,γ 给一个中等值(比如 1),c 给转换变量的中位数。

# 拟合线性 VAR 并提取系数作为初值 results = model.fit(p) A_coefs = results.coefs # 形状 (p, K, K) intercept = results.intercept # 构造 SSTVAR 的初值向量:两组系数先都设成线性估计值 init_params = { 'A_low': A_coefs.copy(), 'A_high': A_coefs.copy(), 'c_low': intercept.copy(), 'c_high': intercept.copy(), 'gamma': 1.0, 'threshold': np.median(data['gdp']) # 转换变量取 gdp }

这里A_low和A_high都设成同一组值,意味着模型从线性出发,优化过程中逐渐分化出两个区制。gamma=1.0是个中性起点,太大容易让转移函数变成硬切换,太小则退化成线性。threshold用转换变量的中位数,保证初始状态下两个区制都有足够的样本点参与估计。

3.3 调用工具箱估计 SSTVAR

不同版本的 SSTVARToolbox 接口名可能不同,但核心调用逻辑一致:传入数据、滞后阶、转换变量、转移函数类型和初值。

from sstvar import SSTVAR # 转换变量取 gdp 的滞后一阶,避免同期内生性 s_t = data['gdp'].shift(1).dropna() data_aligned = data.loc[s_t.index] model_sstvar = SSTVAR( data=data_aligned, lags=p, transition_var=s_t, transition_type='logistic', # 逻辑型转移函数 init_params=init_params ) res = model_sstvar.fit(method='nm', maxiter=5000) print(res.summary()) print(f"估计的 gamma: {res.gamma:.3f}, 阈值 c: {res.threshold:.3f}")

method='nm'指 Nelder-Mead 单纯形法,对非凸目标函数比梯度法稳健,代价是收敛慢。maxiter=5000是保险值,实际收敛通常在 1000 次以内。跑完后重点看两个数:γ 的 t 统计量是否显著(不显著说明非线性不成立),以及阈值 c 是否落在转换变量的合理取值范围内。如果 γ 估计出来大于 50,转移函数近似硬切换,要考虑换指数型或者检查转换变量是否选错。

3.4 区制识别与转换变量时序图

估计完不能只看系数,得确认两个区制在样本里都有足够观测。工具箱一般会输出平滑概率或转移函数时序。

import matplotlib.pyplot as plt # 绘制转移函数随时间的变化 G = res.transition_function(s_t) fig, ax1 = plt.subplots(figsize=(10, 4)) ax1.plot(G.index, G, color='steelblue', label='G(s_t)') ax1.axhline(0.5, color='gray', linestyle='--', linewidth=0.8) ax1.set_ylabel('转移函数值') ax1.set_title('区制转换时序') plt.tight_layout() plt.savefig('transition_plot.png', dpi=150)

G接近 1 的时段属于高区制,接近 0 的属于低区制,0.5 附近是过渡带。如果G长期贴在 0 或 1 上,说明模型退化成硬切换,平滑转换的设定没带来额外信息。理想情况下G应该在样本期内有几次完整的穿越,且每个区制的观测数不少于总样本的 15%。

4. 参数怎么调:γ、c 与转换变量的实操边界

4.1 γ 的尺度问题与重参数化

γ 是转移函数的斜率,但它的量纲依赖转换变量的量纲。如果s_t是利率(取值 0 到 10),γ 估计出来可能是 2;如果s_t是产出缺口(取值 -0.05 到 0.05),γ 可能上千。这导致优化时 γ 的搜索空间极不对称,梯度法基本没法用。

常见做法是对转换变量做标准化,让s_t的均值为 0、标准差为 1,这样 γ 的合理范围收敛到 0.5 到 20 之间。工具箱如果没内置标准化,自己在传入前处理:

s_raw = data['gdp'].shift(1).dropna() s_std = (s_raw - s_raw.mean()) / s_raw.std()

标准化后阈值 c 的含义变成「转换变量偏离均值多少个标准差时机制切换」,解释起来反而更直观。注意标准化参数要保存下来,后面做脉冲响应或者预测时要按同一套均值方差还原。

4.2 阈值 c 的网格搜索策略

非线性优化对 c 的初值也敏感。我一般先用网格搜索粗定位:在转换变量的 20% 到 80% 分位数之间取 20 个点,每个点固定 c 估一次 γ 和系数,选残差平方和最小的那个作为正式优化的初值。

import numpy as np quantiles = np.linspace(0.2, 0.8, 20) ssr_list = [] for q in quantiles: c_init = np.quantile(s_std, q) # 固定 c,只估 gamma 和系数 res_tmp = model_sstvar.fit(c_fixed=c_init, method='nm', maxiter=2000) ssr_list.append(res_tmp.ssr) best_c = np.quantile(s_std, quantiles[np.argmin(ssr_list)]) print(f"网格搜索最优阈值初值: {best_c:.3f}")

这一步多花几分钟,但能把陷入局部最优的概率降一大半。血泪经验:跳过网格搜索直接优化,十次里有三次收敛到边界解(γ 趋近 0 或无穷),结果没法用。

4.3 转换变量选择的三个判断标准

转换变量选错是 SSTVAR 翻车最常见的原因。三个标准帮你筛:第一,理论上它得能驱动机制切换,比如研究货币政策传导,利率或信用利差比 GDP 更合适;第二,它得是平稳的或者至少和因变量协整,否则转移函数会漂移;第三,它不能是模型里某个内生变量的同期值,否则联立内生性会让估计不一致,用滞后一阶或外生指标。

如果拿不准,可以分别用两三个候选变量各估一次,比较 γ 的显著性和信息准则。工具箱通常支持传入多个候选做对比,没有的话手动循环即可。

5. 避坑与排查:SSTVAR 估计中最容易翻车的五件事

5.1 现象:优化不收敛,报错「maximum iterations exceeded」

原因通常是初值太离谱或者目标函数在该区域太平坦。γ 初值给 1 而实际最优在 15 附近时,单纯形法要爬很久。解决:先做 4.2 的网格搜索,把 c 和 γ 的初值都粗定位一遍;同时把maxiter提到 10000,并换用method='pso'(粒子群)做第一轮全局搜索,再用 NM 精修。

5.2 现象:γ 估计出来不显著,但线性 VAR 的残差明显有结构变化

这说明非线性可能存在,但转换变量选错了。解决:换转换变量。常见替代包括因变量的滞后平方项、滚动标准差(波动率)、或者外部的政策指标。另一个可能是转移函数类型不对,逻辑型换成指数型再试。

5.3 现象:脉冲响应图在区制二里发散到无穷

非线性模型的 GIRF 需要对冲击大小做积分,如果冲击给得太大(比如 3 个标准差),在高区制里系数矩阵的特征根可能大于 1,响应就爆了。解决:把冲击限制在 1 个标准差以内,并在 GIRF 计算时检查每个历史状态下系数矩阵的稳定性。工具箱如果有check_stability选项,务必打开。

5.4 现象:两个区制的系数矩阵几乎一样,模型退化成线性

原因可能是样本里转换变量没有足够的变异性,或者 γ 被估得太小。解决:先画转换变量的时序图,确认它在样本期内有几次明显的升降;如果它本身波动就小,SSTVAR 不适合你的数据,老老实实用线性 VAR 加虚拟变量。

5.5 现象:区制划分和已知的经济事件对不上

比如模型把 2008 年划成低区制,但理论上那应该是高波动区制。原因可能是转换变量滞后阶选得不对,或者阈值 c 被估计到了样本边缘。解决:检查s_t的滞后阶,通常用 1 阶,但有时 2 阶更符合经济决策延迟;同时确认 c 不在转换变量的 5% 分位数以外,否则某个区制的样本太少,估计不可靠。

6. 进阶技巧:用 GIRF 验证非线性是否真的存在

跑完 SSTVAR 最关键的验证不是看 t 统计量,而是比较线性 VAR 和 SSTVAR 的广义脉冲响应。如果两条曲线在统计上没差别,非线性就是摆设。具体做法:对同一笔冲击,分别在线性 VAR 和 SSTVAR 下算 GIRF,然后对 SSTVAR 的 GIRF 做 bootstrap 置信区间,看线性 VAR 的响应是否落在区间外。

# 假设 res_linear 是线性 VAR 结果,res_sstvar 是 SSTVAR 结果 girf_linear = res_linear.girf(shock=1.0, horizon=20) girf_sstvar = res_sstvar.girf(shock=1.0, horizon=20, n_boot=500) # 比较同一变量在区制一和区制二的响应差异 diff = girf_sstvar['high'] - girf_sstvar['low'] ci_low, ci_high = np.percentile(girf_sstvar['boot_diff'], [2.5, 97.5]) if ci_low > 0 or ci_high < 0: print("两个区制的脉冲响应存在显著差异,非线性成立") else: print("差异不显著,考虑退回线性模型")

n_boot=500是 bootstrap 次数,正式发表建议 1000 次以上。horizon=20对月度数据是 20 个月,季度数据取 8 到 12。这一步跑完,你对模型有没有必要上非线性就有底了。

我自己的习惯是:任何 SSTVAR 结果,先不看系数,先画三张图——转换函数时序、两个区制的 GIRF 对比、残差的自相关。三张图都干净,再回头解释系数。这个顺序帮我省了很多返工。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进