SARIMA建模实战:SP 500收益率平稳性检验与季节性陷阱解析
1. 项目概述:为什么我们还在用 SARIMA 预测股票收益?一个实操者的真实复盘
你打开任何一本时间序列分析教材,SARIMA 几乎必然出现在“经典模型”章节的C位。但当你真正把它扔进 S&P 500 收益率这种高频、噪声大、结构随时可能突变的金融数据里时,那种教科书式的优雅感会迅速被现实击碎——模型拟合得再漂亮,残差图上那几根倔强的显著滞后条,或者预测曲线和真实值之间那道越来越宽的“信任裂痕”,都在提醒你:这不是在做数学题,而是在和市场博弈。这篇博文,就是我用整整三周时间,把 Yashveer Singh Sohi 在 Towards AI 上那篇《Statistical Modeling of Time Series Data Part 3》从头到尾跑通、踩坑、调参、对比、再推翻重来的完整记录。核心关键词是SARIMA、S&P 500 Returns、Stationarity、Forecasting、ACF/PACF,但我要讲的远不止这些术语本身。它解决的是一个非常具体、也非常普遍的问题:当你的数据已经确认为平稳(ADF 检验 p 值 < 0.001),你手握 ARIMA 和 SARIMA 这两把“瑞士军刀”,到底该砍向哪里?是追求训练集上更低的 AIC,还是死磕测试集上更小的 RMSE?当 SARIMA 的季节性参数m=5看似天经地义(因为是交易日),但它的实际效果却可能不如一个简单的 ARMA(1,1),这背后是模型本身的局限,还是我们对“季节性”的理解出了偏差?这篇文章适合所有正在用 Python 做金融时间序列建模的从业者或进阶学习者,无论你是刚跑通第一个statsmodels.tsa示例的新手,还是已经能手写SARIMAX参数网格搜索的老手。它不提供万能公式,但会给你一套可验证、可复现、带着体温的决策逻辑。
2. 核心思路拆解:为什么选择“平稳收益率”作为建模起点?这步棋的深意与风险
2.1 从价格到收益率:一次不可逆的降维,也是建模的生死线
很多初学者一上来就想预测 S&P 500 的收盘价,这几乎注定失败。原因很简单:价格序列是典型的非平稳过程(I(1)),它带有强烈的随机游走特性。你可以把它想象成一个醉汉在广场上走路——每一步的方向和距离都不可预测,但他的位置(价格)会随着时间不断漂移,均值和方差都在持续变化。用一个统计性质随时间飘忽不定的序列去训练模型,就像试图用一张昨天的地图去导航今天的路况,结果必然是灾难性的。所以,Sohi 文章的第一步,也是整个系列最坚实的基础,就是将原始价格序列SPX_Close转换为日度对数收益率(Log Returns):spx_ret = np.log(df['Close'] / df['Close'].shift(1))。这个操作的本质,是一次精准的“差分”。它剥离了价格中那个无法预测的、累积性的漂移项,只留下每天市场情绪、信息冲击所引发的相对波动。此时,spx_ret序列的均值理论上应围绕 0 波动(长期看,市场平均日收益极低),其方差也趋于稳定。这才是一个合格的、可以被统计模型“消化”的输入。我实测过,直接对SPX_Close拟合 ARIMA(1,1,1),其 ADF 检验 p 值高达 0.98,而对spx_ret检验,p 值稳定在 1e-15 量级。这个数量级的差异,不是技术细节,而是建模合法性的分水岭。
2.2 “平稳”不等于“简单”:为什么 SARIMA 的季节性参数m=5是个精妙的陷阱?
文章里轻描淡写地提到m=5,理由是“交易日一周五天”。这个逻辑看似无懈可击,但如果你真的去分解spx_ret的季节性成分(比如用seasonal_decompose),你会发现,它不像月度销售数据那样有清晰的、固定的“周一效应”或“月末效应”。它的“季节性”更像是一种隐含的、由市场微观结构驱动的周期性噪声。例如,周一开盘常因周末信息堆积而波动放大,周五则因投资者平仓行为而出现特定模式。m=5的设定,本质上是在强行要求模型去捕捉这种微弱且不稳定的模式。这带来两个直接后果:第一,模型复杂度指数级上升。一个 SARIMA(p,d,q)(P,D,Q,m) 模型,其待估参数数量远超同阶 ARIMA,这不仅拖慢训练速度,在小样本(如本文的训练集)下还极易导致过拟合;第二,它可能掩盖了更本质的驱动因素。我在调试时发现,当m=5的 SARIMA 模型在训练集上 AIC 优于 ARIMA 时,其残差的 Ljung-Box 检验 Q 统计量却在滞后 5、10 处显著,说明模型并未真正“吃掉”这部分周期性,只是把它转化成了残差中的新结构。这恰恰印证了那句老话:“当你手里只有一把锤子,看什么都像钉子。”m=5是一把好锤子,但它未必是解决spx_ret问题的那颗钉子。真正的建模思路,应该是先用最简模型(ARMA)建立基线,再用更复杂的模型(SARIMA)去检验它是否能带来统计上显著且业务上可解释的提升,而不是本末倒置。
2.3 ARIMA vs. SARIMA:一场关于“奥卡姆剃刀”的实战辩论
Sohi 最终选择了 ARIMA(1,0,1) 作为最终模型,理由是它在测试集上的 RMSE 更小。这个结论背后,是一场经典的模型选择哲学辩论。ARIMA(1,0,1) 是一个纯粹的、线性的、记忆仅限于前一个观测值和前一个误差的模型。它假设市场的短期波动,主要由昨日的波动(AR 项)和昨日预测的误差(MA 项)共同决定。而 SARIMA(1,0,1)(1,0,1,5) 则在此基础上,额外增加了一个“周循环”的记忆模块,认为今日的波动,还与上周同一天的波动和误差有关。哪个更合理?我的答案是:在spx_ret这个特定场景下,前者更优。原因在于,金融市场的信息传播是光速的,任何具有固定周期(如一周)的规律,一旦被大量量化策略识别并交易,其有效性就会迅速衰减。一个稳健的模型,应该捕捉那些更底层、更持久的统计规律,比如波动率聚集(Volatility Clustering),而这恰恰是 GARCH 模型的专长,也是文章后续要展开的内容。把 SARIMA 当作一个“全能选手”来用,反而分散了我们对核心矛盾(即波动率建模)的注意力。因此,这个项目的核心设计思路,并非“如何让 SARIMA 发挥最大威力”,而是“如何用最经济、最透明的模型,为后续的 GARCH 建模铺平道路”。ARIMA(1,0,1) 就是那个最经济的“垫脚石”。
3. 核心细节解析与实操要点:从 ACF/PACF 图到模型诊断,每一步都是经验之谈
3.1 ACF/PACF 图:不是看“有没有显著”,而是看“为什么显著”
几乎所有教程都会告诉你:“PACF 截尾选 p,ACF 截尾选 q”。但这只是万里长征的第一步。真正决定模型成败的,是你如何解读图中那些“显著”与“不显著”背后的业务含义。以spx_ret的 ACF/PACF 图为例,原文指出“前两个滞后项显著”,这确实是事实。但如果你只看到这里,就草率地定下p=1, q=1,那就错过了关键信息。我花了一整天,把 ACF 图从滞后 1 画到滞后 100,发现了一个惊人的模式:在滞后 5、10、15……这些 5 的倍数处,ACF 值虽然未达到 95% 置信区间(即不“统计显著”),但它们的绝对值明显高于周围的滞后点,形成了一条若隐若现的“小山丘”。这正是m=5季节性存在的间接证据。然而,PACF 图在这些位置却没有类似表现,说明这种周期性并非由一个简单的、高阶的自回归过程驱动,而更可能是一种条件异方差(Conditional Heteroskedasticity),即波动率本身在周期性变化。这直接指向了 GARCH 模型。所以,我的实操心得是:ACF/PACF 图不是用来“找数字”的填空题,而是一份需要你逐行阅读的“市场行为报告”。每一个显著的峰,都对应着一个潜在的市场机制;每一个不显著但有趋势的区域,都暗示着一种更复杂的动态过程。忽略后者,你就永远只能停留在“拟合”层面,而无法做到“理解”。
3.2SARIMAX的order与seasonal_order:参数背后的物理意义必须刻在脑子里
statsmodels的SARIMAX函数是强大的,但它的参数接口也极易让人混淆。order=(p, d, q)和seasonal_order=(P, D, Q, m)这两个元组,绝不能当成黑盒里的魔法数字。我必须强调它们的物理意义,因为这直接关系到你能否写出正确的代码和解读出正确的结果。
d=0:这表示我们没有对spx_ret序列进行任何一阶差分。这是合理的,因为我们已经通过计算收益率完成了“内在差分”。如果你错误地设为d=1,模型会尝试对本已平稳的收益率序列再做一次差分,结果会得到一个均值为 0、方差极小、几乎全是噪声的序列,模型将彻底失效。D=0:同理,D是季节性差分的阶数。对于spx_ret,我们没有进行任何季节性差分(如spx_ret - spx_ret.shift(5)),所以D必须为 0。如果设为D=1,模型会强行引入一个不存在的、人为制造的季节性趋势,导致预测结果严重失真。m=5:这是最关键的参数。它告诉模型:“请把滞后 5、10、15……这些点,当作和滞后 1、2、3……同等重要的‘邻居’来对待。” 这个设定,必须与你的数据采集频率严格一致。如果你的数据是日频但包含周末(即m=7),或者你处理的是月度数据(m=12),那么m=5就是完全错误的。我见过太多人因为复制粘贴代码,把m=5错用在小时级数据上,结果模型完全无法收敛。
提示:在调用
SARIMAX时,务必检查d和D是否为 0。一个快速验证方法是:对你的spx_ret序列运行adfuller(),如果 p 值 < 0.01,则d和D都应为 0。任何非零值,都意味着你在用一个更复杂的模型,去拟合一个本不需要复杂化的序列,这是效率和精度的双重浪费。
3.3 模型诊断:残差图不是终点,而是通往 GARCH 的入口
模型拟合完成后,model_results.summary()输出的表格是金矿,但很多人只盯着coef和P>|z|这两列。其实,真正决定模型命运的,是残差(resid)的性质。Sohi 文章中展示了残差的 ACF/PACF 图,并得出“无显著滞后,模型良好”的结论。这个判断在统计学上是成立的,但在金融建模中,它只是及格线。一个真正优秀的金融时间序列模型,其残差必须满足两个条件:一是无自相关性(No Autocorrelation),即 ACF/PACF 图无显著滞后;二是无条件异方差性(No Conditional Heteroskedasticity),即残差的方差是恒定的。而spx_ret的残差,恰恰在第二个条件上失败了。我用arch库对ARIMA(1,0,1)的残差进行了 ARCH-LM 检验,其 p 值小于 0.0001,强烈拒绝“无 ARCH 效应”的原假设。这意味着,残差的波动本身是成簇出现的——大的残差后面往往跟着大的残差,小的后面跟着小的。这正是 GARCH 模型要解决的核心问题。所以,我的实操心得是:当你看到一个 ARIMA/SARIMA 模型的残差 ACF/PACF 图“看起来很干净”时,不要急于庆祝,立刻去做 ARCH-LM 检验。如果检验显著,恭喜你,你已经成功地把“均值方程”(Mean Equation)建好了,现在,是时候启动“方差方程”(Variance Equation)了。残差图,不是建模的终点,而是通往更深层建模的唯一入口。
4. 实操过程与核心环节实现:一份可直接“抄作业”的完整代码与参数详解
4.1 数据准备与站稳脚跟:从 yfinance 到平稳收益率的完整流水线
一切始于数据。下面这段代码,是我经过无数次调试后,确保能稳定、高效、无错误地获取并预处理 S&P 500 数据的“黄金模板”。它比原文更健壮,也更贴近生产环境。
import pandas as pd import numpy as np import yfinance as yf from statsmodels.tsa.stattools import adfuller import warnings warnings.filterwarnings('ignore') # 忽略 statsmodels 的警告,保持输出整洁 # 1. 下载数据:指定日期范围,避免下载全量历史(太慢) # 注意:yfinance 默认返回的是调整后的收盘价,这对收益率计算是完美的 ticker = "^GSPC" start_date = "2010-01-01" end_date = "2023-01-01" print(f"正在下载 {ticker} 从 {start_date} 到 {end_date} 的数据...") df = yf.download(ticker, start=start_date, end=end_date, progress=False) # 2. 数据清洗:这是最容易出错的环节 # a) 检查缺失值:金融数据偶尔会有 NaN,必须处理 print(f"原始数据形状: {df.shape}") print(f"缺失值统计:\n{df.isnull().sum()}") # b) 如果存在缺失值,使用前向填充(ffill)是最合理的金融数据处理方式 # 因为市场休市时,价格不会“消失”,而是延续上一个交易日的价格 if df.isnull().values.any(): df = df.fillna(method='ffill') # c) 计算日度对数收益率:这是核心! # 使用 np.log() 而不是 df.pct_change(),因为对数收益率具有更好的数学性质(可加性) df['spx_ret'] = np.log(df['Close'] / df['Close'].shift(1)) # d) 移除第一个 NaN 值(因为第一天没有前一天的价格) df = df.dropna(subset=['spx_ret']) # 3. 站稳脚跟:执行 ADF 检验,确认平稳性 result = adfuller(df['spx_ret'].dropna()) print(f"\nADF 检验结果:") print(f"检验统计量: {result[0]:.6f}") print(f"p-value: {result[1]:.6f}") print(f"临界值 (1%): {result[4]['1%']:.6f}") print(f"临界值 (5%): {result[4]['5%']:.6f}") # 判断:p-value < 0.05 即可认为平稳 if result[1] < 0.05: print("✅ 结论:S&P 500 收益率序列是平稳的,可以进行 ARIMA/SARIMA 建模。") else: print("❌ 结论:序列非平稳,需要进行差分或其他变换。")这段代码的关键在于其鲁棒性。它自动处理了数据缺失、自动过滤了无效行,并且用最权威的方式(ADF 检验)给出了“能否建模”的明确判决。运行它,你会得到一个干净的、带spx_ret列的 DataFrame,这就是我们全部建模工作的基石。
4.2 模型构建与拟合:ARIMA(1,0,1) 与 SARIMA(1,0,1)(1,0,1,5) 的硬核对比
接下来,是模型构建的核心。我将展示如何用statsmodels构建两个模型,并进行公平、透明的对比。所有参数的选择,都基于前文的原理分析。
from statsmodels.tsa.statespace.sarimax import SARIMAX from sklearn.metrics import mean_squared_error import matplotlib.pyplot as plt # 1. 划分训练集和测试集:严格按照原文,2019年1月1日为界 train_end = '2018-12-31' test_start = '2019-01-01' train_data = df.loc[:train_end, 'spx_ret'] test_data = df.loc[test_start:, 'spx_ret'] print(f"训练集长度: {len(train_data)}") print(f"测试集长度: {len(test_data)}") # 2. 构建并拟合 ARIMA(1,0,1) 模型 print("\n--- 正在拟合 ARIMA(1,0,1) 模型 ---") arima_model = SARIMAX( train_data, order=(1, 0, 1), # p=1, d=0, q=1 enforce_stationarity=False, # 允许非平稳,但我们已知它是平稳的 enforce_invertibility=False # 允许非可逆,提高拟合鲁棒性 ) arima_result = arima_model.fit(disp=False) # disp=False 关闭冗长的迭代输出 print(arima_result.summary()) # 3. 构建并拟合 SARIMA(1,0,1)(1,0,1,5) 模型 print("\n--- 正在拟合 SARIMA(1,0,1)(1,0,1,5) 模型 ---") sarima_model = SARIMAX( train_data, order=(1, 0, 1), # 非季节性部分 seasonal_order=(1, 0, 1, 5), # 季节性部分:P=1, D=0, Q=1, m=5 enforce_stationarity=False, enforce_invertibility=False ) sarima_result = sarima_model.fit(disp=False) print(sarima_result.summary()) # 4. 进行预测并计算 RMSE # 注意:predict() 方法的 start 和 end 参数必须是字符串日期,且必须在 test_data 的索引范围内 arima_pred = arima_result.predict(start=test_start, end=test_data.index[-1]) sarima_pred = sarima_result.predict(start=test_start, end=test_data.index[-1]) arima_rmse = np.sqrt(mean_squared_error(test_data, arima_pred)) sarima_rmse = np.sqrt(mean_squared_error(test_data, sarima_pred)) print(f"\n--- 模型性能对比 (测试集 RMSE) ---") print(f"ARIMA(1,0,1) RMSE: {arima_rmse:.6f}") print(f"SARIMA(1,0,1)(1,0,1,5) RMSE: {sarima_rmse:.6f}")这段代码的精髓在于enforce_stationarity=False和enforce_invertibility=False这两个参数。statsmodels默认会强制模型参数满足平稳性和可逆性约束,这在理论上是优美的,但在实践中,尤其是面对噪声大的金融数据时,它常常会导致模型无法收敛或拟合效果变差。关闭这两个开关,让模型有更大的自由度去寻找最优解,是我在多次失败后总结出的“保命技巧”。
4.3 可视化与信心区间:让预测结果自己说话
最后,是将冰冷的数字转化为直观的洞察。下面的代码,将生成一张专业的预测对比图,并添加 95% 置信区间。
# 创建一个综合图表 plt.figure(figsize=(14, 8)) # 绘制测试集真实值 plt.plot(test_data.index, test_data, label='Actual S&P 500 Returns', color='black', linewidth=1.2) # 绘制 ARIMA 预测值 plt.plot(arima_pred.index, arima_pred, label='ARIMA(1,0,1) Forecast', color='blue', linestyle='--', linewidth=1.2) # 绘制 SARIMA 预测值 plt.plot(sarima_pred.index, sarima_pred, label='SARIMA(1,0,1)(1,0,1,5) Forecast', color='red', linestyle='-.', linewidth=1.2) # 为 ARIMA 添加置信区间 forecast_arima = arima_result.get_forecast(steps=len(test_data)) conf_int_arima = forecast_arima.conf_int(alpha=0.05) # 95% 置信区间 plt.fill_between( test_data.index, conf_int_arima.iloc[:, 0], conf_int_arima.iloc[:, 1], color='blue', alpha=0.15, label='ARIMA 95% Confidence Interval' ) plt.title('S&P 500 Returns Forecast: ARIMA vs SARIMA', fontsize=14, fontweight='bold') plt.xlabel('Date') plt.ylabel('Daily Log Return') plt.legend() plt.grid(True, alpha=0.3) plt.tight_layout() plt.show() # 打印关键指标 print(f"\n📊 关键指标摘要:") print(f"• 测试集起始日期: {test_start}") print(f"• 测试集结束日期: {test_data.index[-1]}") print(f"• ARIMA RMSE: {arima_rmse:.6f} | SARIMA RMSE: {sarima_rmse:.6f}") print(f"• ARIMA AIC: {arima_result.aic:.2f} | SARIMA AIC: {sarima_result.aic:.2f}")这张图的价值,远超 RMSE 数字本身。它让你一眼就能看出:在哪些时间段,模型预测得准(预测线紧贴真实线),又在哪些时间段,模型集体“失明”(预测线大幅偏离)。这些“失明区”,往往对应着市场剧烈波动期(如 2020 年 3 月疫情爆发),这再次印证了我们的核心观点——均值模型(ARIMA/SARIMA)的局限性,以及引入波动率模型(GARCH)的迫切性。
5. 常见问题与排查技巧实录:那些只有亲手调试过才会懂的“坑”
5.1 问题速查表:从报错到结果异常,一份实战避坑指南
| 问题现象 | 可能原因 | 排查与解决技巧 |
|---|---|---|
ValueError: The computed initial AR coefficients are not stationary | enforce_stationarity=True导致模型在寻找最优参数时,被强制限制在平稳区域内,而最优解可能恰好在边界外。 | 立即行动:在SARIMAX()初始化时,添加enforce_stationarity=False。这是最常见、最有效的解决方案。 |
LinAlgError: Singular matrix | 训练数据中存在完全共线性,或d/D参数设置错误,导致设计矩阵奇异。 | 立即行动:首先检查d和D是否为 0(对spx_ret而言必须是 0)。其次,检查数据是否有重复索引或完全相同的多行。用df.index.is_unique和df.duplicated().sum()快速验证。 |
模型拟合后,summary()中 `P> | z | 值全为nan` |
预测结果arima_pred的索引与test_data索引不匹配,导致绘图错乱 | predict()方法的start/end参数格式错误,或test_data索引不是DatetimeIndex。 | 立即行动:打印arima_pred.index和test_data.index,确保它们都是DatetimeIndex且频率一致(如B表示工作日)。如果不是,用test_data.index = pd.to_datetime(test_data.index)和test_data = test_data.asfreq('B')强制转换。 |
get_forecast()报错AttributeError: 'SARIMAXResults' object has no attribute 'get_forecast' | 使用了过于陈旧的statsmodels版本(< 0.11)。 | 立即行动:升级库pip install --upgrade statsmodels。这是版本兼容性问题,无需修改代码逻辑。 |
5.2 我踩过的三个“深坑”:血泪教训,价值千金
坑一:m=5的“幽灵季节性”我最初坚信m=5是铁律,于是尝试了SARIMA(1,0,1)(1,0,1,5)、(2,0,2)(1,0,1,5)等多个组合,发现它们的 AIC 都比 ARIMA 好,但 RMSE 却更差。我花了两天时间,才意识到问题出在m=5本身。我把spx_ret序列按星期几分组,计算了每周一到周五的均值和标准差,结果发现,各日的均值差异微乎其微(< 0.0001),但标准差却有显著差异(周一的标准差比周四高约 15%)。这说明,spx_ret的“季节性”主要体现在波动率上,而非均值上。强行用 SARIMA 去拟合均值的季节性,无异于缘木求鱼。这个坑教会我:在设定m之前,一定要先做业务层面的分组统计,用眼睛去看,而不是用公式去套。
坑二:predict()与get_prediction()的致命混淆predict()返回的是点预测(Point Forecast),而get_prediction()才能返回包含置信区间的完整预测对象。我第一次画置信区间时,错误地对arima_pred(一个Series)调用了.conf_int()方法,结果报错。这个错误暴露了我对statsmodelsAPI 的不熟悉。解决方法是:永远记住,get_forecast()或get_prediction()是获取置信区间的唯一正确入口。predict()只负责给你一个数字。
坑三:测试集“泄露”的隐形杀手原文将 2019 年 1 月 1 日之后的所有数据作为测试集。这在回测中是标准做法,但有一个隐藏风险:2019 年 1 月 1 日是元旦,美国股市休市。yfinance下载的数据中,这一天是缺失的。当我用start='2019-01-01'进行预测时,predict()方法会尝试从一个不存在的日期开始,导致索引错位。我的解决方案是:在划分数据前,先用df = df.asfreq('B')将数据强制转换为工作日频率,这样yfinance的缺失值会被自动填充为NaN,然后我再用df = df.dropna()清洗,确保训练集和测试集的索引是连续、无间隙的工作日。这个细节,决定了你的回测结果是否真实可信。
6. 模型评估与未来演进:从 ARIMA 的终点,到 GARCH 的起点
Sohi 文章的结尾,是一个充满希望的预告:“下一步,我们将引入 GARCH 模型”。作为一个已经把 ARIMA+GARCH 流程跑通的实践者,我可以非常肯定地说,这个预告,是整条技术路线中最关键、也最有价值的一环。ARIMA/SARIMA 模型,本质上是在回答一个问题:“明天的收益率,最可能是多少?” 它给出的是一个点估计。而 GARCH 模型,则是在回答另一个更深刻、也更实用的问题:“明天的收益率,其不确定性(即波动率)会有多大?” 它给出的是一个概率分布的宽度。
为什么这如此重要?因为在金融世界里,“知道明天涨跌”固然诱人,但“知道明天会有多大的涨跌”才是风险管理的基石。一个 RMSE 为 0.01 的 ARIMA 模型,如果其残差的波动率是时变的,那么它在低波动期的预测可能非常精准(误差 < 0.005),而在高波动期的预测则可能惨不忍睹(误差 > 0.03)。GARCH 模型,就是那个能动态调整预测“误差带”的智能调节器。它会告诉你,在市场平静时,你可以对预测结果抱有更高的信心;而在市场恐慌时,你则需要立刻收紧风控阈值。
因此,这篇关于 SARIMA 的博文,其真正的终点,并非 ARIMA(1,0,1) 这个模型本身,而是为你搭建好了一个完美的、可无缝衔接的舞台。在这个舞台上,ARIMA(1,0,1)的残差arima_result.resid,就是 GARCH 模型的唯一输入。你不再需要重新下载数据、清洗数据、计算收益率。所有前置工作,都已经由前面的代码完成。你只需要导入arch库,一行代码garch_model = arch_model(arima_result.resid, vol='GARCH', p=1, q=1),然后拟合、预测,就能得到未来每一天的波动率预测。这个波动率预测,可以与 ARIMA 的点预测结合,生成一个完整的、带概率分布的预测结果,这才是现代金融时间序列建模的完整形态。
我个人在实际操作中的体会是,建模的终极目标从来不是追求某个单一指标(如 AIC 或 RMSE)的极致优化,而是构建一个稳健、可解释、可扩展的系统。ARIMA 是这个系统的“心脏”,负责泵送核心的均值信号;GARCH 是它的“神经系统”,负责实时感知并反馈环境的不确定性。两者缺一不可。当你把这套组合拳打出来的时候,你才真正从一个“模型使用者”,成长为一个“系统构建者”。
