新闻详情

新闻详情

首页 / 资讯中心 / 详情

ARIMA+LSTM组合预测上证指数:Python实战与避坑指南

发布时间:2026/9/28 5:35:24来源:尧图网络
ARIMA+LSTM组合预测上证指数:Python实战与避坑指南
简介基于ARIMA与LSTM的上证指数预测MATLAB代码包面向金融时序分析与量化入门用户适合本科及以上学习者用于课程设计、算法验证或科研扩展。方案针对新版MATLAB中ARIMA报错做了适配保留LSTM对时间序列非线性特征的建模优势代码注释完整附带000001.csv、沪深300等指数数据替换数据即可迁移到其他指数预测场景。压缩包共19个文件含10个m脚本、5个csv行情数据、2个mat变量、1个xls表格及1个asv备份文件整体仅3.23MB便于快速调试。提供MSE、RMSE、MBE、MAE和R²等误差评估模块可一键输出预测精度指标同时给出ARIMA与LSTM训练、预测和对比流程方便扩展更多金融时间序列实验。目前已有198人学习下载。1. 把 ARIMALSTM 组合真正用在上证指数预测上难点不在模型沪深指数的日线收盘价看起来有趋势、有波动但单独拿 ARIMA 或者 LSTM 去预测结果通常都很难看ARIMA 对非线性特征基本无能为力LSTM 虽然能捕捉局部形态却容易把噪声当成规律。把两者拼起来让 ARIMA 负责剥离线性趋势和周期让 LSTM 吃 ARIMA 剩下的残差是这类预测项目里最多见也最稳妥的做法。这篇笔记按“数据齐全”的项目形态来讲一份沪深指数日线数据、一条能从头跑通的 Python 链路先跑 ARIMA 部分再挂 LSTM最后给出组合预测主流程和几个绕不开的坑。2. 先跑通 ARIMA把沪深指数里的线性结构剥出来ARIMA 三个字母分别对应自回归、差分、移动平均它最适合处理的是一类带有明显趋势和短期自相关的序列。上证指数收盘价就属于这种今天和昨天的价格高度相关长期看又有漂移。问题在于这种序列通常不是平稳的直接建模会让统计量失真。所以落地时的第一步不是急着拟合而是先做平稳性检验再决定差分阶数最后才谈 p 和 q 的取值。2.1 数据清单与 ADF 检验先确认这份数据能不能用拿到一份沪深指数数据集先把列名和顺序核对清楚。最常见的格式是 date、open、high、low、close、volume按日期升序排列。数据齐全不代表可以直接用常见麻烦有两个日期乱序以及除权除息后产生的缺失值。建议读进来之后先排序再确认缺失情况。import pandas as pd from statsmodels.tsa.stattools import adfuller df pd.read_csv( data/index.csv, parse_dates[date], index_coldate ).sort_index() close df[close].dropna() print(缺失值数量:, df.isnull().sum().sum()) print(close.head()) adf_result adfuller(close, autolagAIC) print(ADF p值:, adf_result[1])逻辑说明先按日期排序避免乱序数据在差分时引入虚假的跳跃dropna()只丢掉缺失的价格点不做填充因为沪深指数的复权缺口如果靠前向填充去补等于人为造出连续平台。adfuller的autolagAIC让函数自动选择滞后阶数返回结果的第二个元素就是 p 值。p 值大于 0.05 说明序列存在单位根需要差分小于 0.05 则可以直接进入定阶环节。参数说明金融日线序列一般 d1也就是做一阶差分。沪深指数的对数价格通常是一阶单整几乎不需要二阶差分。如果你在 ADF 检验时发现一阶差分后 p 值仍然不显著先检查数据是否混入了停牌日、是否把非交易日当作 0 填充而不是急着加大差分阶数。2.2 定阶与拟合用 AIC 把 p、q 选出来然后取残差statsmodels 的 ARIMA 接口要求传入(p, d, q)。d 已经确定为 1p 和 q 用遍历的方式选。范围控制在 0 到 5 就够了再大的阶数在日线数据上几乎没有业务意义只会让模型多记几根 K 线的噪声。from statsmodels.tsa.arima.model import ARIMA best_aic float(inf) best_order None for p in range(6): for q in range(6): try: model ARIMA(close_train, order(p, 1, q)).fit() if model.aic best_aic: best_aic model.aic best_order (p, 1, q) except Exception: continue print(最优order:, best_order, AIC:, best_aic) final_model ARIMA(close_train, orderbest_order).fit() fitted_values final_model.fittedvalues resid (close_train - fitted_values).dropna()逻辑说明AIC 在衡量拟合优度的同时惩罚参数数量所以它不会一味追求复杂模型。fittedvalues是模型用训练段历史数据逐步预测得到的样本内拟合值它和close_train在索引上是一一对应的。残差用close_train减fitted_values得到这一步产生的残差序列就是下一步 LSTM 的输入。参数说明这里的close_train只取时序的前 85% 左右最后一段留作测试。p、q 的范围从 0 到 5加上 d1组合数量只有 36 个遍历时间可以接受。如果你用的 statsmodels 是 0.12 之前的旧版本接口在statsmodels.tsa.arima_model新版本统一走statsmodels.tsa.arima.model两者不能混用。残差提取这里有一个经常被忽略的细节fittedvalues在序列起始几个点可能是 NaN因为差分后没有足够的前置数据。直接减会造成索引错位所以dropna()必须放在减完之后。等残差序列干净了再确认它的长度和close_train对齐这一步我在后面避坑章节会重点展开。3. LSTM 吃残差python 里的时间序列预处理与最小模型ARIMA 留下来的残差理论上应该是白噪声但 AIC 定阶只能保证整体线性结构被剥离不代表局部非线性模式不存在。LSTM 在这条链路里的角色就是再扫一遍残差LSTM 做时间序列预测时python 实现上最核心的两个动作是构造滑动窗口和监督信号的归一化处理。3.1 归一化与滑动窗口给 LSTM 造样本LSTM 的输入形状是(样本数, 时间步, 特征数)。残差是单变量序列所以特征数为 1。滑动窗口长度我一般取 20 个交易日对应一个月的交易节奏。import numpy as np from sklearn.preprocessing import MinMaxScaler resid_train resid.values.reshape(-1, 1) scaler MinMaxScaler(feature_range(-1, 1)) resid_scaled scaler.fit_transform(resid_train) def build_sequences(data, seq_len20): X, y [], [] for i in range(seq_len, len(data)): X.append(data[i - seq_len:i, 0]) y.append(data[i, 0]) return np.array(X), np.array(y) X_seq, y_seq build_sequences(resid_scaled, seq_len20) X_seq X_seq.reshape(X_seq.shape[0], X_seq.shape[1], 1)逻辑说明循环从seq_len开始取样本每个样本包含最近 20 个交易日的残差目标变量是第 21 天的残差值。reshape把每一条样本变成(20, 1)对应“20 个时间步、1 个特征”。参数说明feature_range(-1, 1)比(0, 1)更合适因为残差有正有负映射到对称区间能让 LSTM 的 tanh 激活函数更早进入有效梯度区。窗口长度 20 是把月内节奏和滞后效应折中之后的选择窗口太短模型只看得到一周的波动窗口太长沪深指数的噪声占比会拖慢收敛。训练时不建议打开 shuffle时间序列一旦被打乱LSTM 学到的就不再是顺序依赖而是乱序中的伪规律。3.2 PyTorch 最小实现一个 LSTM 层一个全连接模型结构不需要复杂。单层 LSTM 加一层全连接隐藏单元 32dropout 0.2足够在残差这种低频信噪比序列上做拟合。网络层数加多之后训练集的 loss 会降得更快但验证集几乎必然反弹。import torch import torch.nn as nn class ResidLSTM(nn.Module): def __init__(self, input_size1, hidden_size32, num_layers1, dropout0.2): super().__init__() self.lstm nn.LSTM( input_size, hidden_size, num_layers, batch_firstTrue, dropoutdropout ) self.fc nn.Linear(hidden_size, 1) def forward(self, x): out, _ self.lstm(x) return self.fc(out[:, -1, :]) model ResidLSTM() optimizer torch.optim.Adam(model.parameters(), lr0.001) criterion nn.MSELoss()逻辑说明out的形状是(batch, seq_len, hidden_size)取out[:, -1, :]表示拿最后一个时间步的隐藏状态作为整个窗口的压缩表达再接全连接输出一个标量预测值。对照torch.nn.LSTM的源码看这一步是最容易看明白的地方LSTM 返回的out是所有时间步的隐藏状态序列只有最后一个时间步包含了“看到第 20 天为止”的全部信息。参数说明hidden_size 32 对日线残差预测够用。调整顺序应该是先试 seq_len再试 hidden_size最后才动 num_layers。lr 0.001 是 Adam 的常见起点残差序列本身值域已经在 -1 到 1 之间不需要做学习率预热。训练代码保持最简形式split_point int(len(X_seq) * 0.9) X_train X_seq[:split_point] y_train y_seq[:split_point] X_val X_seq[split_point:] y_val y_seq[split_point:] X_train_t torch.tensor(X_train, dtypetorch.float32) y_train_t torch.tensor(y_train, dtypetorch.float32).unsqueeze(1) for epoch in range(50): model.train() optimizer.zero_grad() pred model(X_train_t) loss criterion(pred, y_train_t) loss.backward() optimizer.step() if epoch % 10 0: print(fepoch {epoch}, loss {loss.item():.6f})逻辑说明训练段从残差序列的最前面连续切出 90%剩下 10% 作为验证段不跨到原始数据集的测试段。unsqueeze(1)把目标变量从(batch,)变成(batch, 1)对齐模型输出的形状。50 个 epoch 是经验起点沪深指数残差信噪比低再多也不会有本质提升反而容易记住个别极端行情。验证段在训练循环里没有直接参与 loss 计算它是用来判断过拟合的参照。如果验证 loss 连续十个 epoch 不降甚至上升就把 dropout 调到 0.3或者把隐藏单元降到 16而不是去加训练轮数。4. 组合预测主流程把残差回填串出可用的量化策略代码ARIMA 和 LSTM 各自的训练代码跑通之后剩下的是主流程组装。常见的错误是把两个模型的预测结果直接相加但两者在时间点上往往没有对齐。ARIMA 预测的是未来一段连续价格LSTM 预测的是残差的下一步两者必须先确认对齐关系再相加。4.1 训练集、验证集、测试集怎么切整个组合模型里的切分要分成两层。第一层是 ARIMA 的原始数据切分我用前 85% 做训练后 15% 做测试。第二层是 LSTM 的残差切分在 ARIMA 的训练段内部再切 90/10。这样保证 LSTM 永远不会看到测试段的数据。import pandas as pd import numpy as np df pd.read_csv(data/index.csv, parse_dates[date], index_coldate) close df[close].dropna() split int(len(close) * 0.85) train_close close[:split] test_close close[split:]逻辑说明这里刻意不使用随机切分。沪深指数预测是时间序列任务随机打乱会让后续的滞后期特征泄漏到未来回测指标立刻虚高。前 85% 和后 15% 的切分保持了时间连续性也符合“用过去预测未来”的基本假设。4.2 组合预测的滚动递推环节ARIMA 部分先对测试段做一次性连续预测LSTM 部分则需要每预测一个交易日就更新一次残差历史。之所以不能提前把所有残差算完是因为测试段的真实残差只能随着时间推进逐步获得。from statsmodels.tsa.arima.model import ARIMA arima_model ARIMA(train_close, orderbest_order).fit() fitted_train arima_model.fittedvalues.dropna() resid (train_close - fitted_train).dropna() lstm_model, scaler train_lstm_on_resid(resid) arima_forecast_all np.asarray( arima_model.forecast(stepslen(test_close)) ).reshape(-1) resid_history list(resid) seq_len 20 preds [] for i in range(len(test_close)): last_resid np.array(resid_history[-seq_len:]).reshape(1, seq_len, 1) scaled_resid scaler.transform(last_resid) x_tensor torch.tensor(scaled_resid, dtypetorch.float32) with torch.no_grad(): resid_pred lstm_model(x_tensor).item() final_pred arima_forecast_all[i] resid_pred preds.append(final_pred) actual_resid test_close.iloc[i] - arima_forecast_all[i] resid_history.append(actual_resid)逻辑说明arima_forecast_all是 ARIMA 从训练段结束位置出发对整段测试期的连续预测它不会使用测试段真实价格这是严格意义上的样本外预测。循环里的resid_history初始值是 ARIMA 训练段的残差列表每预测完一天就把当天真实价格减去 ARIMA 预测值得到的真实残差追加到历史里这样下一轮 LSTM 才能看到最新的信息。这里的代码有一个关键点第 i 天的真实残差计算用的是test_close.iloc[i] - arima_forecast_all[i]而不是用前一天的预测值。因为残差的历史必须反映真实发生过的预测误差LSTM 才能在这个误差序列里找出规律。坑在最后一步preds目前是纯点预测没有置信区间。如果直接把这一列当作买入信号会把连续几天的预测误差累积当成趋势变化。下一节会专门讲验证手段先把代码跑通再说。5. 避坑与排查沪深指数预测最容易翻车的 5 个点这个组合看起来逻辑成立真正跑起来翻车点集中在数据对齐、归一化、切分这三件事上。以下每一条都是实操中反复遇到过的现象。5.1 ARIMA 残差和原始价格对不上现象LSTM 训练 loss 很低但测试段预测曲线和真实价格错开一大截。原因fittedvalues在序列开头有 NaN直接拿close_train - fitted减完再传进 LSTM等于让 LSTM 学习了一组错位的“残差”。解决减完必须dropna()并且打印残差的索引头部和尾部确认它和训练段的价格索引对齐。5.2 归一化把测试段统计量混进来现象测试集 MAE 很漂亮一换到滚动回测框架里就崩。原因在整段残差序列上先调MinMaxScaler.fit_transform再把训练段切片。测试段的极大极小值已经被 scaler 记住等于未来信息提前进入训练。解决scaler.fit只作用在resid上后续所有transform都只做转换不再重新 fit。5.3 ARIMA 残差不是白噪声LSTM 吃的是垃圾现象LSTM 预测的残差序列仍然带有明显趋势最终预测结果追着真实价格跑但始终慢半拍。原因AIC 选出来的阶数只能让模型在训练段拟合好不保证残差通过白噪声检验。解决拟合后跑一次acorr_ljungboxp 值小于 0.05 就直接提升 p 或 q。残差有线性结构残留时LSTM 去学这些残留等于重复造轮子。from statsmodels.stats.diagnostic import acorr_ljungbox lb_pvalue acorr_ljungbox(resid, lags[10], return_dfTrue) print(lb_pvalue)逻辑说明Ljung-Box 检验判断的是残差在滞后 10 期以内是否还存在显著自相关。p 值大于 0.05说明白噪声假设不能被拒绝这时候把残差交给 LSTM 才是合理的。p 值显著时回到定阶环节把 p 或 q 调大重新提取残差。5.4 时间序列被当成普通回归数据随机切分现象训练时 loss 抖动验证集指标反而好过训练集。原因数据被train_test_split或者 DataLoader 的 shuffle 打乱相邻样本的时间依赖被破坏模型的滞后记忆失效。解决所有切分都按顺序进行验证集从训练段尾部连续截取DataLoader 设置shuffleFalse。5.5 预测曲线比真实走势晚一天现象预测形态和真实曲线很像但整体往右平移方向命中率只有 0.5 左右。原因模型的输入是截至昨天收盘价的窗口预测对象是今天收盘价但今天开盘跳空会破坏这种静态关系。解决把预测目标从“次日收盘价”改成“未来 5 日收益率”或者把当日开盘价也拼进特征。滞后一天是金融序列预测里的系统性偏差不是代码 bug是目标定义的问题。6. 验证预测效果的三个进阶手段滚动回测、方向命中率与白噪声复盘组合预测跑出第一版结果后先别急着换策略做三个验证动作。第一个动作是方向命中率用预测值和真实值各自的涨跌方向比对命中率稳定在 0.55 以上才算有价值0.5 附近说明模型只是复制了昨天的方向。第二个动作是滚动回测以 60 天为一个窗口重新训练每 20 天往前滚动一次把每一窗的预测拼接成一条完整的样本外预测曲线这样能暴露数据泄漏问题。第三个动作是残差白噪声复盘。把“最终预测价 - 真实价”重新当作一个新的残差序列跑一次 Ljung-Box 检验如果这份最终残差仍然有显著相关性说明组合模型还没吃干净结构。下面是一个对比方向的参考表模型ARIMA 单独LSTM 单独ARIMALSTM方向命中率0.480.510.57日均绝对误差32.629.124.8数字只是一个量级参考真实值会随数据段变化但观察方法不变组合模型的提升主要来自方向命中率而不是把绝对误差压到极小。进阶玩法的方向是给 LSTM 输出加上分位数目标训练时用 pinball loss 替代 MSE输出的不再是单一预测价而是 10% 到 90% 的置信区间。落在区间内的日子照常交易区间之外就空仓这比单纯预测收盘价更容易落到实盘规则里。我有一次在测试段反复调参调出了漂亮曲线换到滚动回测才发现全是领先一期的假信号。从那以后任何超参组合必须在滚动回测框架里跑完再定结论这个习惯帮我避开了很多自我感动式的优化。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Python量化回测:事件驱动与向量化框架全对比 2026/9/28 7:36:03

Python量化回测:事件驱动与向量化框架全对比

做量化这一年多,我前前后后折腾了不少Python库和框架,从Backtrader到Zipline,再到自己手撸回测引擎,最后发现市场上最主流的其实就两条技术路线:一条是事件驱动型框架,用Backtrader这类现成轮子&#xff1b…

阅读更多 →
SpringBoot+Vue3旅游管理系统源码解析:架构、数据库与避坑指南 2026/9/28 7:36:02

SpringBoot+Vue3旅游管理系统源码解析:架构、数据库与避坑指南

很多同学拿到一套旅游管理系统源码,SpringBoot Vue3 MyBatis MySQL这套前后端分离的组合,第一反应是“东西挺全,但不知道从哪下手”。尤其做毕设或者公司内部要快速搭业务后台的时候,光是把数据库脚本跑通、前端依赖装完、接口…

阅读更多 →
AgentScope 2.0多智能体实战:RAG服务化与生产级部署指南 2026/9/28 7:36:02

AgentScope 2.0多智能体实战:RAG服务化与生产级部署指南

1. 从踩坑到入坑:我为什么最终选了AgentScope大概半年前,我所在的技术团队准备做企业级Agent平台,市面上叫得上名字的多智能体框架几乎试了个遍。LangChain生态确实热闹,但它在多智能体编排层面太"自由",自由…

阅读更多 →
【FreeRTOS学习笔记】(三) 2026/9/28 7:36:02

【FreeRTOS学习笔记】(三)

萨达萨达萨达

阅读更多 →
OpenSlide 实战指南:在循环与热路径中缓存属性访问,避免重复查找的 JavaScript 性能优化 2026/9/28 7:36:02

OpenSlide 实战指南:在循环与热路径中缓存属性访问,避免重复查找的 JavaScript 性能优化

【免费下载链接】open-slide A slide framework built for agents. 项目地址: https://gitcode.com/gh_mirrors/op/open-slide 点击查看 免费下载 导读 本指南围绕 Vercel React/Next.js 性能最佳实践规则集中的 js-cache-property-access 规则展开,讲…

阅读更多 →
Gopeed 下载管理器指南:10 分钟装好并跑通 2026/9/28 7:35:55

Gopeed 下载管理器指南:10 分钟装好并跑通

Gopeed 下载管理器指南:10 分钟装好并跑通 【免费下载链接】gopeed A fast, modern download manager for HTTP, BitTorrent, Magnet, and ed2k. Cross-platform, built with Golang and Flutter. 项目地址: https://gitcode.com/GitHub_Trending/go/gopeed Gopeed 下载…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉