4、平稳性处理进阶:高阶差分、季节性分解与方差稳定化变换

各位同学,咱们接着聊。上一章我们把平稳性的基础概念和单位根检验讲透了。但说实话,实战中遇到的金融时间序列,往往比教科书上的例子要「野」得多。你可能会遇到这样的情况:一阶差分后序列看起来还是不太对劲,或者方差忽大忽小,又或者季节性波动像幽灵一样缠着你的模型。

嗯,这一章我们就来解决这些「硬骨头」。我会把我在处理国债收益率、股指期货高频数据时踩过的坑,都摊开来给你们看。

4.1 高阶差分:什么时候用?怎么用?

先问个问题:为什么有时候一阶差分不够用?

我个人习惯,拿到一个序列后,先做ADF检验。如果p值大于0.05,就做一阶差分。但有一次,我处理某个新兴市场的汇率数据,一阶差分后ADF检验居然还是非平稳。当时我有点懵,后来仔细一看——这个序列的走势像抛物线一样,一阶差分只能消除线性趋势,但二次趋势还在。

这时候就需要高阶差分。

说白了,d阶差分就是连续做d次一阶差分。数学上很简单:

一阶差分:Δy_t = y_t - y_{t-1}
二阶差分:Δ²y_t = Δy_t - Δy_{t-1} = y_t - 2y_{t-1} + y_{t-2}
三阶差分:Δ³y_t = Δ²y_t - Δ²y_{t-1} = ...

但我要提醒你:高阶差分不是万能的。我曾经在项目中为了追求平稳性,一路差分到四阶,结果序列是平稳了,但经济含义全丢了——差分后的数据完全无法解释,模型预测出来的值回译后也面目全非。

避坑指南:我曾经犯过一个错误——对日度股票收益率数据做了二阶差分。后来发现,收益率本身已经是近似平稳的,二阶差分反而引入了负的自相关。记住:金融收益率序列通常d=0或d=1就够了,别为了「看起来平稳」而过度差分。

实战中,我一般这样判断差分阶数:

  • 看时序图:如果趋势是直线型,d=1;如果是抛物线型,试试d=2
  • 看ACF图:如果一阶差分后ACF衰减很慢,考虑二阶差分
  • 用KPSS检验辅助:KPSS原假设是平稳,如果一阶差分后KPSS仍拒绝,说明需要更高阶

代码实现很简单,Python里直接用numpy.diff或者pandas.DataFrame.diff

import pandas as pd
import numpy as np

# 假设data是原始序列
data_diff1 = data.diff().dropna()      # 一阶差分
data_diff2 = data.diff().diff().dropna()  # 二阶差分

# 或者用numpy
data_diff2_np = np.diff(data, n=2)

4.2 季节性分解:STL与X13-ARIMA-SEATS

说到季节性,做量化的人最容易忽略的就是它。你想想看,很多金融数据都有明显的季节性——比如国债回购利率在季末会飙升,股指期货的成交量在交割周会放大。如果不把这些季节性成分剥离出来,模型会学出很多「假规律」。

我个人最常用的两种方法是STLX13-ARIMA-SEATS。它们各有千秋,我分别说说。

4.2.1 STL(Seasonal-Trend decomposition using LOESS)

STL是我个人最喜欢的分解方法。为什么?因为它够灵活,够鲁棒。

STL的核心思想是用LOESS(局部加权回归)来分别拟合趋势、季节性和残差。它的优点很明显:

  • 可以处理任何频率的季节性(日、周、月、年)
  • 对异常值不敏感(鲁棒性版本)
  • 趋势和季节性可以平滑变化

我在处理某只商品的日度库存数据时,发现数据里有很多「尖刺」——比如某天库存突然暴增。如果用经典分解法,这些尖刺会污染整个季节性成分。但STL的鲁棒版本能很好地处理这种情况。

Python里用statsmodels实现:

from statsmodels.tsa.seasonal import STL
import matplotlib.pyplot as plt

# 假设data是月度数据,周期为12
stl = STL(data, period=12, robust=True)
result = stl.fit()

# 提取成分
trend = result.trend
seasonal = result.seasonal
residual = result.resid

# 可视化
result.plot()
plt.show()
小技巧:STL的参数robust=True建议默认开启。我在项目中对比过,开启后残差中的异常值明显减少,后续建模的稳定性提升不少。

4.2.2 X13-ARIMA-SEATS

如果说STL是「轻骑兵」,那X13-ARIMA-SEATS就是「重装甲」。这是美国人口普查局开发的官方季节性调整方法,被全球央行和统计局广泛使用。

它的核心是两阶段:

  • X13-ARIMA:先用ARIMA模型对序列进行前向和后向预测,然后做移动平均分解
  • SEATS:基于ARIMA模型的信号提取方法,把序列分解为趋势-周期、季节性、不规则成分

说实话,X13-ARIMA-SEATS的参数非常多,我刚开始用的时候也头大。但它的优势在于:能自动检测并修正日历效应(比如春节、复活节这种移动假日),这对处理中国市场的节假日效应特别有用。

Python里可以用statsmodelsX13ARIMAResults,但需要安装X13二进制文件。我一般用pandas-datareader配合x13库:

from statsmodels.tsa.x13 import x13_arima_analysis

# 需要先安装x13as二进制文件
results = x13_arima_analysis(data, maxorder=(2,1), maxdiff=(1,1))
seasonally_adjusted = results.seasadj
注意:X13-ARIMA-SEATS对数据长度有要求,一般建议至少4-5年的月度数据。我试过用2年的数据跑,结果季节性调整效果很差,还不如直接用STL。

为了让你更直观地理解这两种方法的区别,我画了一张对比图:

季节性分解方法对比 原始时间序列 STL分解 X13-ARIMA-SEATS 趋势 + 季节性 + 残差 季节调整后序列 + 日历效应修正 STL 特点 • 灵活:任意频率季节性 • 鲁棒:对异常值不敏感 • 简单:参数少,易上手 • 缺点:无日历效应修正 • 缺点:对短序列效果一般 X13-ARIMA-SEATS 特点 • 官方标准:央行/统计局使用 • 日历效应:自动处理移动假日 • 预测扩展:ARIMA前后向预测 • 缺点:参数多,配置复杂 • 缺点:需要较长历史数据

4.3 方差稳定化变换:Box-Cox与Yeo-Johnson

平稳性不只是均值要稳定,方差也得稳定。你想想看,如果某只股票的波动率在牛市中放大、熊市中缩小,那它的收益率序列虽然均值平稳,但方差明显不平稳——这就是所谓的「异方差性」。

处理方差非平稳,最经典的方法就是Box-Cox变换和它的改进版Yeo-Johnson变换

4.3.1 Box-Cox变换

Box-Cox变换的公式很简单:

y_t(λ) = (y_t^λ - 1) / λ, 当 λ ≠ 0
y_t(λ) = ln(y_t),         当 λ = 0

其中λ是一个待估计的参数。说白了,Box-Cox就是在找一个「最佳」的幂变换,让变换后的数据方差更稳定、更接近正态分布。

我在处理某只商品的期货价差时,发现价差序列的方差随着价格水平上升而增大。用Box-Cox变换后,方差明显稳定了,后续的GARCH模型拟合效果也好了很多。

但要注意:Box-Cox要求数据必须为正。如果数据有负值或零,就不能直接用。

from scipy.stats import boxcox
import numpy as np

# 假设data是正数序列
data_boxcox, lambda_opt = boxcox(data)
print(f"最优λ值: {lambda_opt}")

# 逆变换(恢复原始尺度)
data_original = (data_boxcox * lambda_opt + 1) ** (1 / lambda_opt)

4.3.2 Yeo-Johnson变换

Yeo-Johnson变换是Box-Cox的「升级版」。它解决了Box-Cox不能处理负值的问题。公式稍微复杂一点,但核心思想是一样的——通过一个参数λ来调整数据的分布形状。

我个人在实战中更常用Yeo-Johnson,因为金融数据经常有负值(比如收益率)。有一次我处理股指期货的5分钟收益率序列,里面有不少负值,用Box-Cox直接报错,换成Yeo-Johnson就完美解决了。

from sklearn.preprocessing import PowerTransformer

# Yeo-Johnson变换
pt = PowerTransformer(method='yeo-johnson')
data_yj = pt.fit_transform(data.values.reshape(-1, 1))

# 查看最优λ
print(f"最优λ值: {pt.lambdas_[0]}")

# 逆变换
data_original = pt.inverse_transform(data_yj)
核心建议:
  • 如果数据全为正数:优先用Box-Cox,解释性更强
  • 如果数据包含负值或零:用Yeo-Johnson
  • 变换后记得做逆变换,否则预测结果无法解释
  • 不要盲目变换——如果原始数据方差已经稳定,强行变换反而会破坏结构

4.4 实战中的组合策略

说了这么多,你可能想问:在实际项目中,这些方法到底怎么组合使用?

我一般遵循这样的流程:

  1. 先看时序图:判断是否有趋势、季节性、方差变化
  2. 方差稳定化:如果方差随时间变化,先做Box-Cox或Yeo-Johnson
  3. 季节性分解:如果存在明显季节性,用STL或X13-ARIMA-SEATS提取季节性成分
  4. 差分处理:对去除季节性后的序列做ADF检验,确定差分阶数
  5. 再次检验:对最终序列做白噪声检验,确保信息已被充分提取

举个例子,我曾经处理过一组中国10年期国债收益率的日度数据。先做Yeo-Johnson变换(因为收益率有负值),然后用STL分解出季节性(发现季末效应明显),最后做一阶差分。经过这三步处理后,序列的ADF检验p值从0.32降到了0.001以下,后续的ARIMA模型拟合效果非常好。

一个小经验:不要一次性把所有变换都做一遍。每做一步,都停下来看看效果。我见过有人把数据又差分又变换又分解,最后序列变成了一堆白噪声——信息全丢了。记住:平稳性处理的目标是「去除干扰,保留信号」,不是「把数据搞成白噪声」。

好了,这一章的内容就到这里。平稳性处理是时间序列预测的「地基工程」,地基打不好,上面盖的模型再漂亮也没用。希望你们在实际项目中,能灵活运用这些方法,少踩我踩过的坑。


公众号:蓝海资料掘金营,微信deep3321