Python LSTM股市预测实战:数据清洗、模型搭建与回测陷阱

📅 发布时间:2026/10/12 21:48:43
Python LSTM股市预测实战:数据清洗、模型搭建与回测陷阱
简介这是一份基于LSTM长短期记忆网络进行股市预测的Python完整项目适合深度学习初学者、金融数据分析爱好者以及需要完成课程设计或期末大作业的高校学生。项目包含数据预处理、模型构建、训练与预测的完整流程代码注释详细便于理解LSTM在时间序列预测中的应用思路也支持在此基础上进行二次开发或扩展其他金融数据。资源包共含89个文件以Python脚本、预训练模型文件、数据集文本、可视化图表以及Web展示相关文件为主配套了格式化的股票数据表与说明文档整体压缩包大小约8.87MB结构清晰下载后即可运行查看效果。目前已有793人学习使用项目被评为97分高分设计具有一定的参考价值和实操指导意义。对于希望快速上手LSTM预测任务或需要搭建完整演示项目的读者来说这份资料能提供从数据到模型再到结果展示的完整闭环帮助减少环境搭建与代码调试的时间成本。1. 股市预测源码包到手后先别急着跑代码看到“Python基于LSTM模型实现预测股市源代码模型数据集”这类项目压缩包第一反应不是调参而是先弄清楚里面那份数据是怎么来的、怎么切的。我接过三四份这样的包真正有价值的从来不是预测曲线有多漂亮而是把一个完整的时序预测流程摊在你面前原始行情、数据清洗、归一化、滑动窗口、模型训练、回测评估。这套东西适合两类人想搞懂LSTM在时间序列预测里到底怎么落地的人以及想验证自己量化交易策略思路的初学者。不适合指望“跑通代码就躺赚”的人金融时序预测的坑远比想象中多而且大部分藏在数据和评估环节不在模型结构里。2. 把行情数据切成 LSTM 能吃的样本清洗、归一化与滑动窗口2.1 项目包里那份数据集到底怎么读多数股市预测项目的数据集是 CSV 或 pickle 格式列名通常包含 date、open、high、low、close、volume。拿到后第一件事不是画图而是检查缺失值和索引顺序。import pandas as pd # 读入行情数据日期列转成索引 df pd.read_csv(stock_data.csv, parse_dates[date]) df df.set_index(date).sort_index() # 缺失值检查 print(df.isna().sum()) print(df.shape) # 停牌导致的空档用前向填充避免把 NaN 直接喂给 LSTM df[[open, high, low, close]] df[[open, high, low, close]].fillna(methodffill) df df.dropna()parse_dates 让日期列变成 datetime 类型这是后续按时间切分的前提。set_index 加上 sort_index 保证数据按时间升序排列LSTM 对时间顺序极其敏感乱序数据会直接毁掉训练结果。fillna 用前向填充处理停牌日是因为金融数据里 NaN 不能简单删除——删除会让时间轴断裂序列关系就丢了。前向填充的含义是“停牌期间沿用上一交易日价格”这是行情数据处理最常见的做法。2.2 归一化的正确时机别让未来数据偷看训练肉眼看不出来的问题往往最致命。很多人在整个数据集上直接调用 MinMaxScaler 的 fit_transform再切训练集和测试集这是股市预测项目里最常见的血泪坑。scaler 在全量数据上拟合意味着测试集的统计信息已经混进了训练用的缩放参数里等于模型偷看了未来回测结果自然漂亮一到新数据上就翻车。from sklearn.preprocessing import MinMaxScaler # 先按时间切分前 80% 训练后 20% 测试 train_size int(len(df) * 0.8) train_df df.iloc[:train_size] test_df df.iloc[train_size:] # 只在训练集上 fit再分别 transform scaler MinMaxScaler(feature_range(0, 1)) scaled_train scaler.fit_transform(train_df[[close]]) scaled_test scaler.transform(test_df[[close]])fit_transform 在训练集上完成两件事算最小值和最大值然后做缩放。transform 只做缩放用的是训练集存下来的参数。这样测试集的信息就不会渗入训练过程这是时间序列预测里的铁律。若发现测试集最小值低于训练集最小值缩放结果会超出 [0,1] 区间这是正常现象说明市场出现了训练区间没见过的情况恰恰是模型真正要面对的挑战。2.3 构造滑动窗口lookback 和 horizon 怎么定LSTM 不直接吃整段行情它吃的是固定长度的窗口。这个窗口长度叫 lookback也就是用过去多少天的数据预测未来。股市预测项目里最常见的配置是 lookback60 预测 1 天对应大约三个月的交易日。import numpy as np def make_sequences(data, lookback60): X, y [], [] for i in range(lookback, len(data)): X.append(data[i - lookback:i]) # 过去 lookback 天 y.append(data[i]) # 预测第 i 天 return np.array(X), np.array(y) X_train, y_train make_sequences(scaled_train, lookback60) X_test, y_test make_sequences(scaled_test, lookback60) print(X_train shape:, X_train.shape) # (样本数, 60, 特征数)X 的形状是 (样本数, 时间步, 特征数)这是 LSTM 输入的标准三维结构。时间步就是 lookback特征数是 1只用收盘价。循环里 i 从 lookback 开始取 i-lookback 到 i-1 这 60 天的数据作为输入第 i 天作为标签正好错开一天不会泄漏。测试集单独构造序列不和训练集拼接这样测试时模型的输入完全来自测试区间自身符合实际使用场景。2.4 脏数据的三个表现跳空、除权、复权真实行情数据远没有教程里那么干净。跳空高开低开会造成价格突变除权除息会让价格出现“假跌”这些都会干扰 LSTM 学习正常的价格形态。多数项目数据集会做前复权处理但有的偷懒只给了原始价。判断方法很简单看价格序列里有没有单日跌幅超过 20% 且次日没有回补的“坑”大概率是除权没处理。解决办法是换用前复权数据或者对价格序列做收益率变换收益率能天然消除绝对价格跳变带来的影响。这一步不做后面模型训练出来可能就是废的。3. 用 Keras 搭一个 LSTM 预测模型模型结构、训练参数与收敛控制3.1 从 PyTorch 到 Keras我选后者的理由股市预测项目的特点是快速迭代、反复试验特征和窗口参数我用 Keras 的频率远高于 PyTorch。Keras 的 Sequential 接口对新手友好不需要手写训练循环Debug 成本低PyTorch 更灵活适合研究新结构但这个场景用不上。以下代码可以在 CPU 上直接跑数据量不大时完全够用。from tensorflow import keras from tensorflow.keras.layers import LSTM, Dense, Dropout model keras.Sequential([ LSTM(64, return_sequencesTrue, input_shape(lookback, n_features)), Dropout(0.2), LSTM(32, return_sequencesFalse), Dropout(0.2), Dense(1) ]) model.compile( optimizerkeras.optimizers.Adam(learning_rate1e-3), lossmse )return_sequences 是堆叠 LSTM 时的关键参数。第一层设成 True输出的是完整的时间步序列给第二层继续处理第二层设成 False只输出最后一个时间步的隐状态再接全连接层输出预测值。input_shape 这里的 lookback 就是滑动窗口长度 60n_features 是特征数只用收盘价就是 1。Dropout 放在两层 LSTM 之间用来抑制对短期波动的过度拟合金融时序本身噪声大没有 Dropout 很容易在训练集上表现完美、测试集上崩溃。3.2 训练与早停val_loss 是最好的刹车训练时序模型不能用固定 epochs 硬跑。loss 曲线通常先降后升升起来的那一段就是模型开始记住训练集噪声的时刻。EarlyStopping 监控验证集 loss连续 patience 轮不下降就停并回滚到最优权重。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint callbacks [ EarlyStopping(monitorval_loss, patience15, restore_best_weightsTrue), ModelCheckpoint(best_lstm_model.h5, save_best_onlyTrue) ] history model.fit( X_train, y_train, validation_data(X_test, y_test), batch_size64, epochs100, callbackscallbacks, shuffleFalse, verbose1 )validation_data 直接用之前构造好的 X_test 和 y_test这样每轮训练都能看到验证集上的表现。shuffleFalse 保持样本的时间顺序金融时序样本之间高度相关随机打乱会引入噪声。patience 设 15 意味着允许模型在验证集上“空转” 15 轮才停这个数值给模型留了翻盘余地又不会让它彻底放飞。ModelCheckpoint 保存验证集最优的权重文件这个是后悔药训练过程意外中断也能恢复到最佳状态。3.3 训练参数的推荐区间与调整逻辑参数推荐值调整逻辑learning_rate1e-3 起步1e-4 收尾Adam 下 lr 太大 loss 震荡太小收敛极慢batch_size32~128金融样本高度重叠太大容易陷入局部最优lookback5 / 20 / 60日线级别 60 天约等于一个季度的交易信息LSTM units32~128数据量小用 64超过 200 条样本可以考虑加层dropout0.1~0.3验证集 loss 比训练集高很多时优先加大patience10~20数据噪声越大patience 越小防止过拟合参数调整有一个基本顺序先定 lookback再调 units最后才动 learning_rate。反过来调会陷入“参数迷宫”改一个坏一个。我自己习惯用一个笨办法固定 lookback 和 units 跑三组 learning_rate1e-2、1e-3、1e-4观察验证集 loss 的下降速度选最快收敛且不震荡的那组再微调其他参数。3.4 训练资源开销别被“深度学习 大算力”吓到这个项目的数据量通常只有几千行单变量序列CPU 训练一轮几十秒整个训练五分钟以内完成。如果你用的是 GPU反而可能出现“显存利用率极低”的尴尬瓶颈在数据预处理和 IO 上。真正需要 GPU 的是高频 tick 数据或多股票同时训练的场景比如几千只股票一起做时序预测那种情况才值得上批量矩阵运算。跑通这个项目最推荐的配置是 CPU 16G 内存环境方面只要 tensorflow 和 sklearn 装好就行python 版本别超过 3.12否则部分老版本库不兼容。4. 股市预测项目的 5 个高频翻车点与排查方法4.1 数据泄漏回测很漂亮实盘就崩现象训练集 loss 和验证集 loss 都低方向命中率在测试集上超过 60%但换成另一只股票或者另一段时间效果直接掉到接近随机水平。原因归一化时对全量数据做了 fit_transformscaler 偷看了测试区的价格区间。预测时模型在训练阶段就“认识”了未来的价格波动范围。解决严格照第 2 章的顺序操作先按时间切分再 fit 训练集transform 全部集合。用 TimeSeriesSplit 做交叉验证时也要格外小心每一折的 scaler 都必须在该折的训练部分上重新拟合。4.2 维度报错expected ndim3, found ndim2现象调用 model.fit 或 model.predict 时报维度错误提示 LSTM 层期望三维输入实际收到二维。原因输入数组的形状是 (样本数, 特征数)缺少时间步这一维。常见于直接从 DataFrame 切片喂给模型或构造序列时形状写错。解决打印 X_train.shape 确认是否为 (样本数, lookback, 特征数)。数据形状不对时显式 reshapen_samples len(X_train) X_train X_train.reshape(n_samples, lookback, 1) X_test X_test.reshape(len(X_test), lookback, 1)4.3 预测值是上一天的复制模型退化成恒等映射现象预测曲线和真实曲线形状几乎一致但整体向右平移了一天预测值约等于昨天的收盘价。计算预测涨跌方向的命中率时接近 50%。原因单变量价格序列本质上是一个随机游走当前价格就是明天价格的最优估计。LSTM 在平方误差损失下发现“复制昨天”能拿到最低 loss于是放弃了学习任何形态特征。解决改用收益率序列训练即对收盘价取一阶差分或对数收益率再做归一化。收益率序列是平稳的模型被迫学习涨跌形态而非“复制价格”。还可以加入成交量、最高最低价等特征给模型更多信息。4.4 训练过程出现 NaN 或者 loss 爆炸现象训练到某一步 loss 突然变成 nan或者从第一轮就开始疯涨。原因输入序列的值域过大归一化后仍然存在极端异常值或者 learning_rate 太高导致梯度爆炸。股票价格在除权、暴跌时能产生远超常规的值scaler 的 min 和 max 被异常值霸占。解决先检查缩放后的数据里有没有超过 10 或小于 -10 的值有就做截断处理。再用 np.clip 把极端值限制在合理范围最后把 learning_rate 降到 1e-4 重跑。4.5 随机切分训练集时间序列被拦腰斩断现象模型在测试集上表现尚可但逐笔回测时会发现“未来数据出现在训练里”一些投资者常用指标莫名完美。原因用了 sklearn 默认的 train_test_split参数里没设 shuffleFalse。它随机打乱样本把未来的片段混进训练集等于在考试时提前看到了后半张试卷的答案。解决改用按时间顺序的切分方式或使用 TimeSeriesSplitfrom sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for train_index, val_index in tscv.split(X): pass # 每一折的训练集永远在验证集之前5. 从 loss 到真金白银用回测检验模型可不可用5.1 逆归一化把预测值还原成价格模型的输出是缩放后的价格要看懂它必须还原成原始价格。预测值还要反归一化后才能和真实股价对比算误差。y_pred model.predict(X_test) y_pred_inv scaler.inverse_transform(y_pred.reshape(-1, 1)) y_test_inv scaler.inverse_transform(y_test.reshape(-1, 1)) # 平均绝对误差单位是“元” mae np.mean(np.abs(y_pred_inv - y_test_inv)) print(fMAE {mae:.2f} 元)reshape(-1, 1) 是把预测结果恢复成二维列向量scaler 的 inverse_transform 才能正确处理。这里的 scaler 和训练时是同一个实例保证了缩放参数一致。MAE 的单位是原始价格单位不同股票之间不能直接比较同股票不同时间段的 MAE 才有可比性。5.2 判断模型有没有用的两个核心指标回测的核心不是 loss 多低而是预测方向的正确率。涨跌方向判断对了才有交易价值数值预测得再准方向反了就是亏钱。# 方向命中率 direction_pred np.sign(y_pred_inv[1:] - y_pred_inv[:-1]) direction_true np.sign(y_test_inv[1:] - y_test_inv[:-1]) hit_rate np.mean(direction_pred direction_true) # 简化回测预测第二天上涨就持有否则空仓 position (direction_pred 0).astype(float) daily_ret np.diff(y_test_inv.squeeze()) / y_test_inv[:-1].squeeze() strategy_ret position * daily_ret cum_ret np.cumprod(1 strategy_ret) print(f方向命中率: {hit_rate:.2%}) print(f回测累计收益: {cum_ret[-1]:.2f})方向命中率超过 52% 才有做策略的讨论价值因为还要扣交易成本。回测里没有算手续费和滑点每笔交易扣掉万二点五后收益会明显缩水。position 在 t 日决策、t 到 t1 日收益结算用 diff 之后的数组对齐不会有未来函数问题。5.3 一眼识别过拟合训练集与验证集差异指标健康范围过拟合信号train_loss / val_loss 差距差距不超过 20%val_loss 比 train_loss 高 50% 以上方向命中率val大于 52%训练集 70% 但验证集跌到 48%预测与上一日相关性小于 0.3预测序列与真实序列相关系数接近 1LSTM 神经网络结构本身有大量参数几千条样本很容易被完全记住。如果在 val_loss 还没起来时收了 EarlyStopping模型会停留在泛化能力最好的点。建议每次训练后记录 train loss 和 val loss 的比值连续多次训练都比值偏高说明模型容量超出了数据表达能力可以减少 units 或增加 Dropout。5.4 为什么预测结果总是慢半拍股市预测模型给出的预测值天然会滞后真实走势。原因是模型学到的最优策略是“跟随”——昨天的价格包含了今天的大部分信息LSTM 只需要做很小的修正就能让 loss 很低大胆预测逆势拐点反而会付出巨大的 loss 代价。这是金融时序的熵特征换任何模型都躲不开。有人把预测曲线右移一天后画在一起看起来“神预测”这正是滞后效应的直接证明。验证模型是否真的学到了结构要看 phase shift 校正后的相关性而不是原始曲线的形状相似度。6. 模型跑通之后三个值得投入的进阶方向预测股价从来不是终点这个项目的真正价值在于把时序建模的思路迁移到更多场景。跑通基础版本后我会花精力做三件事。第一从单步预测改成多步预测。LSTM 训练时只预测下一天的收盘价但实际交易需要知道未来三天或五天的走势。循环预测是偷懒的办法把预测值重新喂回模型当输入预测误差会指数累积越滚越偏。更可靠的是序列到序列结构encoder 部分读入历史窗口decoder 部分逐步输出未来多个时间步。Keras 的 LSTM 自带 return_sequences 参数改造成本不算高。第二从单一收盘价扩展到多特征输入。把 open、high、low、volume 和收益率堆叠成五维特征矩阵input_shape 从 (lookback, 1) 变成 (lookback, 5)模型能学到的形态就丰富多了。选特征的原则是少而有效不要一口气塞十几个技术指标指标之间的共线性会给 LSTM 增加负担。第三从点预测变成区间预测用分位数损失训练模型输出价格的置信区间。涨跌方向的置信度比单个预测值更有交易价值区间宽说明模型没有把握这时候降低仓位或空仓比硬着头皮进场更理性。我现在拿到任何一个股市预测项目包第一件事永远是检查数据切分和归一化有没有泄漏第二件事是跑一个“复制昨日价格”的基线模型看它在数据集上的 loss然后再谈 LSTM 调参。这个顺序救过我无数次也省下了大量调参时间希望帮到你。本文还有配套的精品资源点击获取