时间序列计量
预计学习时间:3-4 小时
难度:⭐⭐⭐⭐
核心问题:金融数据天然是时间序列,普通回归方法用在时间序列上会出大问题。如何正确分析时间序列数据?
概述
上一文件讲的是经典回归,适用于截面数据(同一时间点上不同资产)。但金融数据几乎都是时间序列——同一资产在不同时间点上的观测。
时间序列和截面数据有本质区别:
| 维度 | 截面数据 | 时间序列数据 |
|---|---|---|
| 观测排列 | 不同个体 | 同一个体在不同时间 |
| 独立性 | 通常满足 | 几乎不满足(今天和昨天有关) |
| 平稳性 | 不是问题 | 核心问题 |
| 因果方向 | 相对清晰 | 可能互为因果 |
| 样本量 | 通常够大 | 金融数据常常不够长 |
如果在非平稳的时间序列上直接跑 OLS,你可能得到完全虚假的结果(回顾上一文件的虚假回归部分)。这是时间序列计量的核心关切。
一、平稳性:分析时间序列的第一步
1.1 什么是平稳性
白话解释:一个时间序列是平稳的,意思是它的”统计特性不随时间改变”。
具体来说,弱平稳(协方差平稳)要求:
- 均值恒定:,不随时间变化
- 方差恒定:,不随时间变化
- 自协方差只依赖时间间隔: 只取决于 ,不取决于
反例:股票价格通常不是平稳的——价格有上升趋势,波动率也会变化。
正例:股票收益率通常是(近似)平稳的——收益率围绕零波动,方差相对稳定。
1.2 为什么不平稳的序列分析结果不可信
- 虚假回归:两个不相关的非平稳序列也可能表现出高度”相关”
- 均值回归失效:趋势序列会”跑掉”,不会回到均值
- 标准推断失效:t 检验、F 检验的统计量分布不再是标准分布
量化中的教训:在做任何时间序列分析之前,先检验平稳性。这就像建房子先检查地基。
1.3 ADF 检验(Augmented Dickey-Fuller Test)
原理
ADF 检验是最常用的单位根检验。它检验的是:
零假设 H0:,即序列有单位根(非平稳) 备择假设 H1:,即序列无单位根(平稳)
如果 p 值足够小(通常 < 0.05),我们拒绝 H0,认为序列是平稳的。
白话理解:ADF 检验在问”这个序列的当前值是否主要依赖于上一期的值?如果是,它就像随机游走,不是平稳的。“
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller
np.random.seed(42)
n = 500
# ============================================================
# 生成三种序列:平稳、有趋势、随机游走
# ============================================================
# 1. 平稳序列(白噪声 + 微弱自相关)
stationary = np.zeros(n)
for t in range(1, n):
stationary[t] = 0.3 * stationary[t-1] + np.random.normal(0, 1)
# 2. 趋势序列(确定性趋势)
trend = np.arange(n) * 0.02 + np.random.normal(0, 1, n)
# 3. 随机游走(非平稳)
random_walk = np.cumsum(np.random.normal(0, 1, n))
# ============================================================
# ADF 检验
# ============================================================
def adf_test(series, name):
"""执行 ADF 检验并打印结果"""
result = adfuller(series, autolag='AIC')
print(f"\n{name}:")
print(f" ADF 统计量 = {result[0]:.4f}")
print(f" p 值 = {result[1]:.4f}")
print(f" 使用滞后阶数 = {result[2]}")
is_stationary = result[1] < 0.05
print(f" 结论: {'平稳 ✅' if is_stationary else '非平稳 ❌'}")
return is_stationary
print("=" * 50)
print("ADF 检验:判断序列是否平稳")
print("=" * 50)
adf_test(stationary, "平稳序列")
adf_test(trend, "趋势序列")
adf_test(random_walk, "随机游走")1.4 PP 检验和 KPSS 检验
不同检验从不同角度判断平稳性。实践中建议同时用多种检验,互相验证。
| 检验 | 零假设 | 检验方向 | 特点 |
|---|---|---|---|
| ADF | 有单位根(非平稳) | 拒绝 → 平稳 | 考虑高阶自相关 |
| PP | 有单位根(非平稳) | 拒绝 → 平稳 | 非参数方法,对异方差稳健 |
| KPSS | 平稳 | 拒绝 → 非平稳 | 和 ADF 方向相反,适合互补 |
from statsmodels.tsa.stattools import adfuller, kpss
def full_stationarity_test(series, name):
"""综合平稳性检验:ADF + KPSS"""
# ADF 检验(H0: 非平稳)
adf_result = adfuller(series, autolag='AIC')
# KPSS 检验(H0: 平稳)
kpss_result = kpss(series, regression='c', nlags='auto')
print(f"\n{name}:")
print(f" ADF: 统计量={adf_result[0]:.3f}, p={adf_result[1]:.4f} "
f"{'→ 平稳' if adf_result[1] < 0.05 else '→ 非平稳'}")
print(f" KPSS: 统计量={kpss_result[0]:.3f}, p={kpss_result[1]:.4f} "
f"{'→ 非平稳' if kpss_result[1] < 0.05 else '→ 平稳'}")
# 综合判断
adf_ok = adf_result[1] < 0.05 # ADF 拒绝 → 平稳
kpss_ok = kpss_result[1] >= 0.05 # KPSS 不拒绝 → 平稳
if adf_ok and kpss_ok:
print(f" 综合: 平稳")
elif not adf_ok and not kpss_ok:
print(f" 综合: 非平稳")
else:
print(f" 综合: 结果不一致,需要进一步分析")
# 对三种序列做综合检验
full_stationarity_test(stationary, "平稳序列")
full_stationarity_test(trend, "趋势序列")
full_stationarity_test(random_walk, "随机游走")1.5 差分平稳
大多数非平稳的金融时间序列,做一次差分后就变得平稳了。
股票价格是非平稳的,但收益率(对数差分)是近似平稳的。这就是为什么量化研究几乎都用收益率而不是价格。
# 对随机游走做一阶差分
diff_rw = np.diff(random_walk)
print(f"\n随机游走差分后的 ADF 检验:")
adf_test(diff_rw, "差分后的随机游走")
print(f" 差分前: 非平稳 → 差分后: 平稳 ✅")二、协整:两个不平稳序列之间的稳定关系
2.1 协整的本质
白话解释:协整说的是——两个序列各自都不是平稳的(各自有趋势),但它们之间存在一个固定的线性组合,这个组合是平稳的。
例子:两个人的身高各自都在增长(非平稳),但他们的身高差保持相对稳定(协整)。
量化意义:如果你发现两个资产的价格序列有协整关系,意味着它们的价差是平稳的——价差偏离后会回归。这是配对交易的理论基础。
2.2 协整 vs 相关性(关键区别!)
这是一个非常重要的区分,很多人搞混:
| 维度 | 相关性 | 协整 |
|---|---|---|
| 要求序列平稳吗? | 理论上要求(但很多人忽略) | 不要求——序列可以非平稳 |
| 衡量什么? | 同步变化的程度 | 长期均衡关系 |
| 假回归风险 | 高(对非平稳序列) | 低(本身就是为非平稳设计的) |
| 量化含义 | 短期同步性强 | 价差会回归——可做配对交易 |
核心教训:两个高度相关的序列不一定有协整关系,而有协整关系的序列相关性可能不高。
2.3 Engle-Granger 两步法
最简单的协整检验方法,分两步:
第一步:对两个序列做 OLS 回归,得到残差
第二步:对残差做 ADF 检验。如果残差平稳,则两个序列有协整关系。
import numpy as np
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller
np.random.seed(42)
n = 500
# ============================================================
# 模拟两个有协整关系的序列
# ============================================================
# 公共随机趋势
common_trend = np.cumsum(np.random.normal(0, 1, n))
# 序列 1 和序列 2 共享这个趋势,但有各自的偏移
y1 = common_trend + np.random.normal(0, 0.5, n) # 序列 1
y2 = 1.5 * common_trend + 2 + np.random.normal(0, 0.8, n) # 序列 2
# ============================================================
# Engle-Granger 两步法
# ============================================================
# 第一步:OLS 回归,得到残差
X = sm.add_constant(y1)
model = sm.OLS(y2, X).fit()
residuals = model.resid
# 第二步:对残差做 ADF 检验
# 注意:对残差做 ADF 时,不包含截距项和趋势项
adf_result = adfuller(residuals, maxlag=1, regression='n')
print("Engle-Granger 协整检验")
print("=" * 50)
print(f" 回归系数 beta = {model.params[1]:.4f}")
print(f" 残差 ADF 统计量 = {adf_result[0]:.4f}")
print(f" 残差 ADF p 值 = {adf_result[1]:.4f}")
print(f" R² = {model.rsquared:.4f}")
# Engle-Granger 的临界值比标准 ADF 更严格
# 因为残差是估计出来的,不是直接观测的
eg_critical = { # 近似临界值
'1%': -3.90, '5%': -3.34, '10%': -3.04
}
print(f"\n Engle-Granger 临界值(近似):")
for level, cv in eg_critical.items():
print(f" {level}: {cv:.2f}")
is_cointegrated = adf_result[0] < eg_critical['5%']
print(f"\n 结论: {'存在协整关系 ✅' if is_cointegrated else '不存在协整关系 ❌'}")
# ============================================================
# 对比:两个没有协整关系的独立随机游走
# ============================================================
rw1 = np.cumsum(np.random.normal(0, 1, n))
rw2 = np.cumsum(np.random.normal(0, 1, n))
X_ind = sm.add_constant(rw1)
model_ind = sm.OLS(rw2, X_ind).fit()
adf_ind = adfuller(model_ind.resid, maxlag=1, regression='n')
print(f"\n对比:两个独立随机游走")
print(f" 残差 ADF 统计量 = {adf_ind[0]:.4f}")
print(f" 残差 ADF p 值 = {adf_ind[1]:.4f}")
print(f" R² = {model_ind.rsquared:.4f}")
print(f" 结论: {'存在协整关系' if adf_ind[0] < eg_critical['5%'] else '不存在协整关系 ❌'}")
print(f" 注意:即使 R² 可能不低,但残差不平稳 → 没有协整")2.4 Johansen 检验:多变量协整
Engle-Granger 只能处理两个序列。当你有三个或更多序列时,用 Johansen 检验。
from statsmodels.tsa.vector_ar.vecm import coint_johansen
np.random.seed(42)
n = 500
# 模拟三个有协整关系的序列
common_trend = np.cumsum(np.random.normal(0, 1, n))
y1 = common_trend + np.random.normal(0, 0.5, n)
y2 = 1.5 * common_trend + 2 + np.random.normal(0, 0.8, n)
y3 = -common_trend + np.random.normal(0, 0.6, n)
data = np.column_stack([y1, y2, y3])
# Johansen 检验
result = coint_johansen(data, det_order=0, k_ar_diff=2)
print("=" * 50)
print("Johansen 协整检验")
print("=" * 50)
# 迹检验(Trace Test)
print("\n迹检验(Trace Test):")
for i in range(len(result.lr1)):
print(f" r <= {i}: 统计量={result.lr1[i]:.2f}, "
f"临界值(5%)={result.cvt[i, 1]:.2f} → "
f"{'拒绝' if result.lr1[i] > result.cvt[i, 1] else '不拒绝'}")
# 最大特征值检验
print("\n最大特征值检验:")
for i in range(len(result.lr2)):
print(f" r <= {i}: 统计量={result.lr2[i]:.2f}, "
f"临界值(5%)={result.cvm[i, 1]:.2f} → "
f"{'拒绝' if result.lr2[i] > result.cvm[i, 1] else '不拒绝'}")
print(f"\n协整秩(协整向量个数)的判断:")
print(f" 从 r=0 开始,逐个检验。第一个不能拒绝的 r 值就是协整秩。")三、VAR 模型:多个时间序列互相影响
3.1 向量自回归
白话解释:VAR 模型考虑多个时间序列互相影响——不仅 A 影响 B,B 也会反过来影响 A。
简单的一阶 VAR(1):
量化应用:
- 利率和通胀的交互影响
- 不同市场之间的溢出效应
- 宏观因子对股票收益的传导
3.2 模型估计与预测
import numpy as np
import pandas as pd
from statsmodels.tsa.api import VAR
np.random.seed(42)
n = 500
# 模拟两个互相影响的时间序列
# y1 受自身滞后和 y2 滞后影响
# y2 受自身滞后和 y1 滞后影响
y1 = np.zeros(n)
y2 = np.zeros(n)
y1[0], y2[0] = 0, 0
for t in range(1, n):
y1[t] = 0.5 * y1[t-1] + 0.3 * y2[t-1] + np.random.normal(0, 1)
y2[t] = 0.2 * y1[t-1] + 0.4 * y2[t-1] + np.random.normal(0, 1)
data = pd.DataFrame({'y1': y1, 'y2': y2})
# ============================================================
# VAR 模型估计
# ============================================================
model = VAR(data)
# 选择最优滞后阶数
lag_order = model.select_order(maxlags=10)
print("滞后阶数选择:")
print(lag_order.summary())
print(f"\n推荐滞后阶数: {lag_order.aic}")
# 拟合模型
results = model.fit(maxlags=lag_order.aic, ic='aic')
print(f"\nVAR 模型结果:")
print(results.summary())
# ============================================================
# 预测
# ============================================================
forecast = results.forecast(data.values[-results.k_ar:], steps=10)
forecast_df = pd.DataFrame(forecast, columns=['y1', 'y2'],
index=pd.RangeIndex(start=n, stop=n+10, name='时间'))
print(f"\n未来 10 期预测:")
print(forecast_df.round(3))
# 置信区间
forecast_ci = results.forecast_interval(
data.values[-results.k_ar:], steps=10, alpha=0.05
)
print(f"\n预测置信区间(95%):")
for i, var in enumerate(['y1', 'y2']):
print(f"\n {var}:")
for step in range(min(5, 10)):
print(f" 第{step+1}步: [{forecast_ci[0][step, i]:.3f}, "
f"{forecast_ci[1][step, i]:.3f}]")3.3 脉冲响应函数(IRF)
白话解释:IRF 回答的是——“如果我对变量 A 施加一个标准差的冲击,其他变量在未来各期会如何反应?”
这在量化中非常有用:比如你想知道”如果利率突然上升 1 个标准差,股市在未来 20 天会怎么走?“
# 脉冲响应分析
irf = results.irf(periods=20)
print("=" * 50)
print("脉冲响应函数(IRF)")
print("=" * 50)
print("问题:y1 受到一个标准差冲击后,y1 和 y2 如何变化?\n")
# y1 对 y1 的冲击的响应
irf_y1_y1 = irf.irfs[:, 0, 0] # [期数, 响应变量, 冲击变量]
irf_y2_y1 = irf.irfs[:, 1, 0] # y2 对 y1 冲击的响应
print("y1 自身冲击的传导路径:")
for t in range(min(10, len(irf_y1_y1))):
print(f" 第 {t} 期: y1={irf_y1_y1[t]:.4f}, y2={irf_y2_y1[t]:.4f}")
# 绘制 IRF 图
fig = irf.plot(orth=False)
fig.suptitle('脉冲响应函数', fontsize=14)
plt.tight_layout()
plt.show()3.4 方差分解
白话解释:方差分解回答的是——“在预测 y 的方差中,有多少是由 y 自身过去的冲击解释的?有多少是由其他变量的冲击解释的?“
# 方差分解
fevd = results.fevd(periods=20)
print("=" * 50)
print("预测误差方差分解")
print("=" * 50)
print("问题:预测 y1 和 y2 的误差中,各变量贡献了多少?\n")
fevd_table = fevd.summary()
print(fevd_table)
# 关键信息
print("\n关键结论:")
for i, var in enumerate(['y1', 'y2']):
print(f" {var} 的预测方差中:")
for j, contrib in enumerate(['y1', 'y2']):
print(f" {contrib} 的贡献: {fevd.decomp[9][i, j]*100:.1f}%")四、GARCH:波动率会”聚集”
4.1 ARCH 效应
白话解释:金融市场的波动率不是均匀的——“大波动跟大波动,小波动跟小波动”。这种现象叫做波动率聚集(Volatility Clustering)。
2008 年金融危机期间,每天都是巨幅波动。而在平静的 2017 年,市场几乎不动。如果用简单的”固定方差”模型,完全捕捉不到这种现象。
4.2 GARCH(1,1) 模型
GARCH = Generalized ARCH(广义自回归条件异方差)
模型分两部分:
均值方程(建模收益率):
方差方程(建模波动率):
- :基础波动水平
- :昨日冲击(残差)对今日波动率的影响
- :昨日波动率对今日波动率的影响
- :保证方差有限(平稳条件)
白话理解:
- 大:市场对昨天的大幅波动反应强烈
- 大:波动率有很强的惯性(持续性)
- 典型金融数据:,
4.3 Python 完整估计和预测
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from arch import arch_model
np.random.seed(42)
n = 1000
# ============================================================
# 模拟 GARCH(1,1) 过程
# ============================================================
omega_true = 0.02
alpha_true = 0.08
beta_true = 0.90
mu = 0.001 # 均值收益
returns = np.zeros(n)
volatility = np.zeros(n)
volatility[0] = np.sqrt(omega_true / (1 - alpha_true - beta_true))
returns[0] = mu + np.random.normal(0, volatility[0])
for t in range(1, n):
volatility[t] = np.sqrt(omega_true + alpha_true * returns[t-1]**2
+ beta_true * volatility[t-1]**2)
returns[t] = mu + np.random.normal(0, volatility[t])
# ============================================================
# 拟合 GARCH(1,1) 模型
# ============================================================
# 使用 arch 库: pip install arch
model = arch_model(returns * 100, vol='Garch', p=1, q=1,
mean='Constant', dist='normal')
result = model.fit(disp='off')
print("=" * 60)
print("GARCH(1,1) 估计结果")
print("=" * 60)
print(result.summary())
print("\n参数对比:")
print(f" {'参数':>8} {'真实值':>8} {'估计值':>8}")
print(f" {'omega':>8} {omega_true:>8.4f} {result.params['omega']:>8.4f}")
print(f" {'alpha':>8} {alpha_true:>8.4f} {result.params['alpha[1]']:>8.4f}")
print(f" {'beta':>8} {beta_true:>8.4f} {result.params['beta[1]']:>8.4f}")
print(f" {'alpha+beta':>8} {alpha_true+beta_true:>8.4f} "
f"{result.params['alpha[1]']+result.params['beta[1]']:>8.4f}")
# ============================================================
# 波动率预测
# ============================================================
# 一步向前预测
forecast = result.forecast(horizon=5, reindex=False)
cond_vol = result.conditional_volatility
print(f"\n未来 5 期波动率预测:")
for i in range(5):
print(f" 第 {i+1} 期: {np.sqrt(forecast.variance[-1, i]):.4f}")
# ============================================================
# 可视化
# ============================================================
fig, axes = plt.subplots(3, 1, figsize=(14, 10))
# 1. 收益率
axes[0].plot(returns, alpha=0.7, linewidth=0.5)
axes[0].set_title('模拟收益率序列')
axes[0].set_ylabel('收益率')
# 2. 真实波动率 vs 估计波动率
axes[1].plot(volatility, alpha=0.7, label='真实波动率', linewidth=1)
axes[1].plot(cond_vol / 100, alpha=0.7, label='GARCH 估计波动率', linewidth=1)
axes[1].set_title('真实 vs 估计波动率')
axes[1].legend()
axes[1].set_ylabel('波动率')
# 3. 条件波动率
axes[2].plot(cond_vol, alpha=0.7, color='red', linewidth=0.8)
axes[2].set_title('GARCH 条件波动率')
axes[2].set_ylabel('波动率')
plt.tight_layout()
plt.show()4.4 ARCH 效应检验
在做 GARCH 建模之前,应该先检验数据是否存在 ARCH 效应。
from statsmodels.stats.diagnostic import het_arch
# 对残差做 ARCH-LM 检验
# 先拟合一个简单的均值模型
from statsmodels.tsa.ar_model import AutoReg
ar_model = AutoReg(returns, lags=1).fit()
residuals = ar_model.resid
# ARCH-LM 检验
arch_test = het_arch(residuals, nlags=10)
print("ARCH-LM 检验(检验是否存在波动率聚集):")
print(f" LM 统计量 = {arch_test[0]:.4f}")
print(f" p 值 = {arch_test[1]:.4f}")
print(f" 结论: {'存在 ARCH 效应,应使用 GARCH 模型' if arch_test[1] < 0.05 else '不存在 ARCH 效应'}")五、误差修正模型(VECM)
5.1 长期关系 vs 短期调整
白话解释:VECM 把协整关系(长期均衡)和短期动态调整结合起来。
假设 A 股和 B 股有长期协整关系(价差围绕某个均值波动),那么:
- 长期:价差保持稳定(协整关系)
- 短期:如果价差偏离均值,会有一个”拉回”的力量
VECM 把这两种力量都建模了:
其中 就是上一期的偏离, 是调整速度( 表示会拉回)。
5.2 Python 实现
from statsmodels.tsa.vector_ar.vecm import VECM
import numpy as np
import pandas as pd
np.random.seed(42)
n = 500
# 模拟两个有协整关系的序列
common_trend = np.cumsum(np.random.normal(0, 1, n))
y1 = common_trend + np.random.normal(0, 0.5, n)
y2 = 1.5 * common_trend + 2 + np.random.normal(0, 0.8, n)
data = pd.DataFrame({'y1': y1, 'y2': y2})
# 确定 coint_rank(协整秩)先用 Johansen 检验
# 这里已知有 1 个协整关系
vecm = VECM(data, k_ar_diff=2, coint_rank=1, deterministic='ci')
result = vecm.fit()
print("=" * 50)
print("VECM 估计结果")
print("=" * 50)
print(result.summary())
# 关键参数
print("\n关键参数解读:")
print(f" 调整速度系数 alpha: {result.alpha}")
print(f" 协整向量 beta: {result.beta}")
print(f" alpha < 0 表示:偏离均衡后会被拉回(均值回归)")六、结构突变检验:关系中是否发生了断裂
6.1 Chow 检验
白话解释:Chow 检验回答的是——“在某个时间点前后,两个序列之间的关系是否发生了变化?”
这在量化中非常重要。市场结构、监管政策、交易制度都可能改变变量之间的关系。如果你忽略结构突变,整个回归结果可能都是错的。
import numpy as np
import statsmodels.api as sm
from scipy import stats
np.random.seed(42)
n = 200
# ============================================================
# 模拟有结构突变的数据
# ============================================================
# 前半段:y = 1 + 2x
# 后半段:y = 1 + 4x(斜率变化)
breakpoint = 100
x = np.random.normal(0, 1, n)
y = np.zeros(n)
# 前半段
y[:breakpoint] = 1 + 2 * x[:breakpoint] + np.random.normal(0, 0.5, breakpoint)
# 后半段——关系变了!
y[breakpoint:] = 1 + 4 * x[breakpoint:] + np.random.normal(0, 0.5, n - breakpoint)
# ============================================================
# Chow 检验
# ============================================================
X = sm.add_constant(x)
# 全样本回归
model_full = sm.OLS(y, X).fit()
SSR_full = np.sum(model_full.resid ** 2)
# 前半段回归
model_1 = sm.OLS(y[:breakpoint], X[:breakpoint]).fit()
SSR_1 = np.sum(model_1.resid ** 2)
# 后半段回归
model_2 = sm.OLS(y[breakpoint:], X[breakpoint:]).fit()
SSR_2 = np.sum(model_2.resid ** 2)
# 计算 F 统计量
k = 2 # 参数个数
n1, n2 = breakpoint, n - breakpoint
SSR_restricted = SSR_1 + SSR_2
SSR_unrestricted = SSR_full
# F = [(SSR_r - SSR_u)/k] / [SSR_u / (n1+n2-2k)]
# 注意:这里 SSR_full 是有约束的(全样本用同一组参数)
F_chow = ((SSR_restricted - SSR_unrestricted) / k) / \
(SSR_unrestricted / (n1 + n2 - 2 * k))
p_value = 1 - stats.f.cdf(F_chow, k, n1 + n2 - 2 * k)
print("=" * 50)
print("Chow 结构突变检验")
print("=" * 50)
print(f" 断裂点: 第 {breakpoint} 期")
print(f" 前半段斜率: {model_1.params[1]:.3f}")
print(f" 后半段斜率: {model_2.params[1]:.3f}")
print(f" F 统计量: {F_chow:.4f}")
print(f" p 值: {p_value:.4e}")
print(f" 结论: {'存在结构突变!' if p_value < 0.05 else '不存在结构突变'}")
# ============================================================
# 对比:没有结构突变的情况
# ============================================================
y_no_break = 1 + 2 * x + np.random.normal(0, 0.5, n)
model_full_nb = sm.OLS(y_no_break, X).fit()
SSR_full_nb = np.sum(model_full_nb.resid ** 2)
model_1_nb = sm.OLS(y_no_break[:breakpoint], X[:breakpoint]).fit()
SSR_1_nb = np.sum(model_1_nb.resid ** 2)
model_2_nb = sm.OLS(y_no_break[breakpoint:], X[breakpoint:]).fit()
SSR_2_nb = np.sum(model_2_nb.resid ** 2)
F_chow_nb = ((SSR_1_nb + SSR_2_nb - SSR_full_nb) / k) / \
(SSR_full_nb / (n1 + n2 - 2 * k))
p_value_nb = 1 - stats.f.cdf(F_chow_nb, k, n1 + n2 - 2 * k)
print(f"\n对比:没有结构突变的数据")
print(f" F 统计量: {F_chow_nb:.4f}")
print(f" p 值: {p_value_nb:.4f}")
print(f" 结论: {'存在结构突变' if p_value_nb < 0.05 else '不存在结构突变 ✅'}")本文件要点回顾
| 概念 | 一句话总结 |
|---|---|
| 平稳性 | 统计特性不随时间改变,时间序列分析的基石 |
| ADF 检验 | 检验序列是否有单位根(是否非平稳) |
| 协整 | 两个非平稳序列存在一个平稳的线性组合(配对交易基础) |
| Engle-Granger | 两步法协整检验:回归 → 残差 ADF |
| Johansen | 多变量协整检验 |
| VAR | 多个时间序列互相影响的建模框架 |
| IRF | 一个冲击如何传播到各个变量 |
| 方差分解 | 预测方差中各变量的贡献比例 |
| GARCH | 波动率聚集的建模方法 |
| VECM | 长期协整关系 + 短期调整 |
| Chow 检验 | 检验关系中是否发生了断裂 |
下一站
掌握了时间序列工具后,我们进入面板数据方法——当你同时有多个资产和多个时间点时,怎么利用这个二维结构提取更多信息。