资讯动态

LSTM股票预测实战:从数据清洗到时序评估的完整流程

发布时间:2026/10/20 7:51:12 来源:尧图企业网站定制
简介一份面向金融数据分析与人工智能初学者的股票预测项目资源使用深度学习中的长短期记忆网络构建预测模型并用Python语言完成代码实现。压缩包共包含12个文件总大小约959KB其中有3个Python源文件涵盖模型训练、预测演示与辅助工具还包含训练好的权重文件、两份CSV行情数据以及说明文档便于直接运行和修改。该项目已有22368人浏览学习是社区内热度较高的AI金融入门资料。通过动手实践读者可以完整经历数据获取、清洗、标准化、序列分段、模型搭建、训练评估及未来价格预测的流程还能理解循环神经网络中梯度消失问题的产生原理及长短期记忆门控机制的作用。资料还给出了股票预测中的过拟合、特征工程等注意事项有助于使用者规避常见错误适合课程设计、毕业设计或自学参考。1. 项目定位把LSTM搬到股票预测里到底值不值先给结论用 LSTM 预测股票价格最难的不是模型本身而是数据时序的切分和评估指标的理解。这个 Python 项目把整条链路做成了可以直接跑的实验从行情数据清洗、滚动窗口构造、标准化到 LSTM 模型定义、训练和结果可视化每一步都有对应代码。它适合两类读者一类是想上手时序预测的 Python 学习者能看清 LSTM 在真实连续数据上怎么落地另一类是已经在写策略脚本的开发者拿它当预测信号组件而不是直接当理财建议。项目本身不保证盈利它解决的是人工智能那一端怎么接在行情数据上的工程问题。2. 核心模块拆解从行情数据清洗到预测落盘的完整链路2.1 项目结构速览与数据清洗模块拆项目先看目录。我拿到源码包后会第一时间把目录结构画出来确认作者把哪部分当成核心、哪部分只是示例。这个项目展开后大概是这样的stock_lstm/ ├── data/ │ └── daily.csv # 历史日线行情字段包含 date/open/high/low/close/volume ├── src/ │ ├── data_loader.py # 数据读取与清洗 │ ├── dataset.py # 滑动窗口构造 │ ├── model.py # LSTM 模型定义 │ ├── train.py # 训练主入口 │ └── evaluate.py # 评估与可视化 ├── config.yaml # 超参与路径配置 └── README.mddata_loader.py 解决的是原始行情数据的“脏”问题。股票日线里最常见的脏数据是停牌空行和复权跳变停牌日成交量变 0部分数据源还会补一根收盘价不变但成交量极小的假 K 线除权除息日价格直接跳空。如果不做处理模型会在这些断点上浪费大量容量去拟合异常跳变正常的局部规律反而学不到。常见做法是先把日期转成时间索引并排序再剔除成交量与收盘价缺失的行然后把 open/high/low 暂时丢弃只保留 close 和 volume 两列。为什么丢弃 high/low因为当日 K 线走完之前这两个值是不可知的拿它们做输入相当于把当天未来信息提前泄漏给了模型。这里我顺手整理一段可跑的数据清洗函数import pandas as pd def load_daily_data(csv_path: str) - pd.DataFrame: # 读取行情CSVdate列转成时间索引 df pd.read_csv(csv_path, parse_dates[date]) df df.set_index(date).sort_index() # 剔除成交量与收盘价缺失的交易日 df df.dropna(subset[close, volume]) # 只保留建模需要的字段避免无关特征干扰时序学习 df df[[close, volume]] # 如果行情源提供复权因子这里应先做前复权处理 return df逻辑说明入参 csv_path 指向 data/daily.csv返回的 df 以时间为索引、按日期升序排列。set_index sort_index 这两行决定了后面切窗时窗口内部时间严格递增dropna 则保证了喂给网络的是连续的“真实交易日”不会混入 0 成交量的假数据。close 和 volume 是后续输入的两维特征第 0 列是 close第 1 列是 volume窗口构造时会用到这个列顺序。清洗之后紧接着做标准化和切分。这里有一个大多数新手会踩的坑先对整个数据集做了标准化再切分训练集和测试集。这样测试集的均值或标准差已经参与过训练网络在训练时偷看了测试集的分布信息验证时指标虚高实盘滚动预测立刻现原形。正确顺序是先用时间切分再只用训练集样本拟合 StandardScaler然后用同一个 scaler transform 测试集。from sklearn.preprocessing import StandardScaler import numpy as np def split_and_scale(df: pd.DataFrame, train_ratio0.8): # 按时间顺序切分不随机打乱 split_idx int(len(df) * train_ratio) train_df df.iloc[:split_idx] test_df df.iloc[split_idx:] # 只用训练集拟合标准化参数 scaler StandardScaler() train_scaled scaler.fit_transform(train_df) test_scaled scaler.transform(test_df) # 注意这里不重新 fit return train_scaled, test_scaled, scaler逻辑说明train_ratio0.8 意味着前 80% 的历史数据做训练后 20% 做测试。参数 scaler 是 sklearn 的 StandardScaler 对象训练结束后务必把它序列化保存下来实盘预测新数据时用同一个对象做 transform不能用新数据的分布重新计算否则预测出来的值在量级上是错的。提示如果你的行情数据跨越一个完整的牛熊周期我更建议按关键时间点切分而不是按固定比例切。模型只学分布训练集和测试集分布越接近验证结论越可信。2.2 滑动窗口构造与模型定义切完窗口后LSTM 的输入是三维张量形状是 (样本数, 时间步长, 特征数)。前面反复提到 time_steps 和窗口它们指的是用过去 N 个交易日的 close 和 volume 共同预测第 N1 日的 close这是标准的单步预测范式。def make_window_dataset(scaled_data: np.ndarray, time_steps30, step1): # 构造监督学习格式过去30天 - 第31天收盘价 xs, ys [], [] for i in range(time_steps, len(scaled_data) - 1, step): x scaled_data[i - time_steps:i] # 左闭右开正好30个交易日 y scaled_data[i 1, 0] # 目标下一天的第0列即收盘价 xs.append(x) ys.append(y) return np.array(xs), np.array(ys)逻辑说明i 从 time_steps 开始是因为窗口内需要完整 30 天循环上限是 len(scaled_data) - 1因为 y 要拿到 i1 的位置。x 的取值是左闭右开scaled_data[i-time_steps:i] 取到的下标正好是 i-30 到 i-1共 30 行。y 取下一天第 0 列是因为目标永远是“未来”如果取第 i 天的值模型学的就是预测当天没有实用价值。参数 step1 表示窗口每 1 个交易日平移一次样本之间高度重叠这也是训练时必须关掉全局 shuffle 的原因——重叠窗口意味着相邻样本高度相似shuffle 后训练集和验证集会互相泄漏。模型定义部分我直接用了 Keras 的 Sequential因为这里的层结构是线性堆叠没有多输入或多输出函数式 API 反而让代码更长from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def build_lstm_model(time_steps30, feature_dim2): model Sequential([ # 输入形状固定为 (30, 2)LSTM容量64 LSTM(64, input_shape(time_steps, feature_dim), return_sequencesFalse), Dropout(0.2), Dense(32, activationrelu), Dense(1) # 回归任务输出下一个交易日收盘价 ]) model.compile(optimizeradam, lossmse, metrics[mae]) return model逻辑说明LSTM 层的 64 表示隐状态维度输入形状里 time_steps30 对应当前窗口长度feature_dim2 对应 close 和 volume 两列特征。return_sequencesFalse 表示模型只取最后一个时间步输出的隐状态向量股票预测任务不需要在每个时间步都输出结果。Dropout(0.2) 在 LSTM 输出后丢弃 20% 的神经单元防止模型死记训练样本对日线级别的小数据量很有必要。后续两层 Dense 把 LSTM 输出压缩到一个标量loss 用 mse 是回归任务的标准配置mae 可以作为训练过程中的参考指标。2.3 训练入口与收敛观察训练主入口一般在 train.py 里核心是 fit 调用。需要强调的一个细节是 shuffleFalse。深度学习框架默认会在每个 epoch 开始前把训练样本打乱图像分类任务里这是合理的但在股票窗口样本里打乱会让未来窗口混进训练集破坏时间顺序验证集指标会虚高。history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs60, batch_size64, shuffleFalse, # 保持序列顺序防止时间窗口交叉 callbacks[EarlyStopping(patience10, restore_best_weightsTrue)] )逻辑说明epochs60 是最大训练轮数实际会因为早停提前结束。batch_size64 每次喂 64 个窗口样本这个值在窗口样本数量几千条量级时不需要调太大batch 太小训练起伏大batch 太大收敛慢。EarlyStopping(patience10) 表示连续 10 轮验证 loss 没有刷新就停止restore_best_weightsTrue 会把权重回滚到验证 loss 最低的那一轮避免最后若干轮过拟合。训练完成后把 history 里的 loss 曲线画出来能快速判断训练是否正常。正常情况是训练 loss 逐步下降验证 loss 在某个点触底后开始回升如果一开始就剧烈震荡问题通常出在学习率或输入特征量级上和模型结构关系不大。3. 评估指标与超参调优把方向准确率当主要筛选标准3.1 评估MAE、RMSE、方向准确率评估指标不能只看 loss 数值。我训练回归模型时会同时算三个指标MAE、RMSE 和方向准确率。方向准确率衡量的是模型预测的“涨跌方向”是否正确这部分对实盘策略最有参考价值。from sklearn.metrics import mean_absolute_error, mean_squared_error def evaluate_prediction(y_true, y_pred): # 预测值要先用训练时保存的scaler逆变换回原始价格尺度 y_true np.squeeze(y_true) y_pred np.squeeze(y_pred) mae mean_absolute_error(y_true, y_pred) rmse np.sqrt(mean_squared_error(y_true, y_pred)) # 方向准确率相邻两个交易日收盘价的差分符号是否一致 true_dir np.sign(np.diff(y_true)) pred_dir np.sign(np.diff(y_pred)) direction_acc np.mean(true_dir pred_dir) return {mae: mae, rmse: rmse, direction_acc: direction_acc}逻辑说明y_pred 在送入该函数之前需要用训练时保存的 scaler.inverse_transform 把标准化还原成真实价格否则 MAE 的单位是标准化后的尺度看起来像个小数值却没有交易含义。方向准确率计算时先对真实序列和预测序列做一阶差分再比较符号是否一致这个指标不受价格绝对量级影响更能反映模型对趋势的判断能力。实盘里 RMSE 再小如果方向总是猜错策略无法获利反之方向准确率到了 55% 以上绝对值误差大一些也可以依赖。3.2 超参表与调参组合训练完第一版后调参才是真正花时间的部分。下面这份表记录了我给项目做实验时的参考配置读者可以直接拿去当起点参数名本次实验取值对训练的影响time_steps30过去一个月交易日太短捕捉不到趋势太长会稀释局部特征hidden_size64LSTM 隐层维度增大容量但容易过拟合dropout0.2防止 LSTM 层过拟合值过大反而欠拟合batch_size64批样本数小批次训练更稳但耗时learning_rate0.001Adam 默认学习率Loss 震荡时先降到 0.0003epochs60配合 EarlyStopping不必设太大从这份表延伸出去我常调的一组合适配置是time_steps45、hidden_size128、dropout0.3、learning_rate0.0005。序列长度从 30 提到 45模型能看到更多天的价格形态hidden_size 提一倍以匹配更长的序列dropout 提一点是为了对冲 hidden_size 增大带来的过拟合风险。需要提醒的是股票数据长度有限盲目加 LSTM 层数收益很低两层以上在日线数据上基本不会带来稳定提升我实测两层 LSTM 只是让训练时间翻倍验证集指标几乎没变化。3.3 预测可视化判断模型是否真的学到东西评估指标之外我强烈主张把预测曲线和真实曲线画在一张图上。指标是数值一条曲线能暴露指标看不见的问题预测线是不是整体滞后了几天是不是在拐点处特别容易失灵import matplotlib.pyplot as plt def plot_prediction(dates, y_true, y_pred, save_pathprediction.png): plt.figure(figsize(14, 5)) plt.plot(dates, y_true, labelreal, linewidth1.2) plt.plot(dates, y_pred, labelpred, linewidth1.2, alpha0.8) plt.legend() plt.tight_layout() plt.savefig(save_path, dpi150) plt.close()逻辑说明dates 对应测试集的真实交易日期y_true 和 y_pred 都应是逆标准化后的真实价格。如果预测线相对真实线有整体滞后说明模型学到的其实不是趋势规律只是在“抄前一天的价格”这种情况下方向准确率往往只有 50% 出头模型基本不可用。4. 常见问题与排查数据泄漏、归一化与复现性相关的五个坑这一章把训练和复现时最常遇见的五个问题按踩坑顺序列出来数据泄漏最隐蔽、归一化错位最直接、训练不收敛最磨人、随机性最难排查、预测滞后最打击信心。每条我都按现象、原因、解决三步记录。4.1 训练误差极小实盘预测却失效现象训练集的 MAE 已经压得很低验证集上也看着正常但拿模型去预测新行情时输出值几乎是一条水平线或者一整天都贴着前一天价格。原因最常见的是数据泄漏。一是切分前做了全量标准化测试集的统计信息进了训练过程二是窗口构造时把当天的高低价或成交量放进了特征而这些信息在交易日结束前是不可知的三是滑动窗口时随机打乱了样本顺序测试集的“邻居”样本混进了训练集。解决把标准化、切窗、shuffle 三个环节全部改成时间顺序语义。先按时间切分再做标准化特征里去掉任何包含当天未来信息的字段训练时 shuffleFalse。改完之后再跑同一份测试集你会发现验证集指标反而变差了不要慌这通常是因为此前指标虚高。4.2 预测曲线整体上偏形状对但数值不对现象预测曲线和真实曲线形态一致方向基本正确但整体平移了一个固定的偏移量例如预测值明显高于真实价。原因逆标准化时用错了 scaler。如果训练时用训练集均值做了标准化预测结果也要用同一个 scaler.inverse_transform 还原如果误用了测试集的均值或者忘记还原直接画图就会看到整体偏移。还有一种场景是模型学到的均值偏高这通常是训练数据和预测数据的分布漂移造成的。解决先确认预测阶段调用的是否为训练时保存的 scaler 对象如果是分布漂移考虑对预测目标做差分处理把回归目标从“价格”改成“涨跌幅”模型更容易适应市场风格变化。4.3 Loss 横在平台期不动现象训练到第 20 个 epoch 左右训练 loss 和验证 loss 都停在一个值附近不再下降且这个平台的 loss 水平明显偏高。原因学习率偏大导致参数在最优解附近来回震荡或者梯度在 LSTM 层传播中衰减序列长度太长加剧了梯度问题。股票数据本身信噪比低loss 曲线比图像分类任务毛躁得多平台期不一定代表模型坏了。解决先把学习率从 0.001 降到 0.0003并让模型前若干步保持小学习率的预热期减少开头震荡再把 time_steps 从 30 降到 20 试试序列变短梯度传导路径也变短最后检查输入特征是否包含绝对值过大的字段比如成交量原始值可能是价格的几十倍会压过价格特征。4.4 同一份代码隔天运行结果完全变样现象代码没改数据没变昨天跑出一版结果今天再跑训练损失和预测曲线完全不一样甚至差别很大。原因深度学习框架默认随机初始化GPU 上的浮点累加和 CPU 不完全一致TensorFlow 在 GPU 上对某些算子默认使用低精度模式会引入额外的不确定性。这不算 bug但直接影响实验可对比性调参时容易把随机波动误判成参数效果。解决固定全局随机种子并将模型初始化、数据加载也一并固定如果要并发跑实验统一关闭低精度加速开关或强制使用 CPU 单线程来复现基准。我习惯在每个实验开始前把 seed、数据版本、当前 commit 一并记录到实验日志否则横跨几周的结果没法对比。4.5 预测曲线贴在真实曲线后面一天现象预测线看起来和真实线高度重合但把两条线放大看预测点明显是这几天前真实价格的平移仿佛模型永远慢了一拍。原因模型学到的其实是“昨天价格近似明天价格”。在均方误差的约束下连续价格序列的保守预测就是把当前值当作未来值模型没有真正学到趋势只是找到一个最小的误差解。解决把训练目标从“预测下一日收盘价”改成“预测下一日涨跌幅”或“预测收盘价的一阶差分”。目标变成相对量之后模型必须对方向做出判断否则误差会放大这样方向准确率才会被真正约束住。5. 二次开发思路把单股单步预测改造成多股滚动预测5.1 三处值得动手的改造点首先把这个项目的输入从“单只股票 CSV”改成“多股票接口 本地存储”。我一般会把 data 目录按股票代码组织每天增量追加当日 K 线清洗逻辑沿用 2.1 节里的函数只多一步按代码过滤。接着改窗口构造把单只股票的循环扩展到多只def make_multistock_dataset(all_stocks: dict, time_steps30): xs, ys, stock_ids [], [], [] for stock_id, data in all_stocks.items(): x, y make_window_dataset(data, time_steps) xs.append(x) ys.append(y) stock_ids.extend([stock_id] * len(x)) return np.concatenate(xs), np.concatenate(ys), np.array(stock_ids)逻辑说明all_stocks 是以股票代码为键、以清洗后的二维 ndarray 为值的字典。返回的 xs 拼接后会变成一个大矩阵维度是 (总窗口数, 30, 2)ys 对应每个窗口的下一日收盘价。stock_ids 数组用于后续按股票拆分评估结果对分析单只股票的模型表现很有用。多股并行的好处是数据量明显变大LSTM 不容易过拟合小样本噪声坏处是不同股票的波动区间差异大标准化时最好按股票分别处理再合并避免高价股的特征值压过低价股。第二个改造点是输出端。上面代码的 y 只有一个标量如果想把模型从“预测下一日收盘价”改成“预测未来 5 日高点”只需要调整 y 的取值下标并把 model 的最后一层从 Dense(1) 改成 Dense(5)损失函数不变。更精细的做法是把输出改为未来 5 日每日涨跌幅让模型学习相对变化率这样对价格绝对量级的敏感度更低。第三个改造点是把特征从 close/volume 扩展到更多衍生因子。5 日均线偏离度、20 日均线偏离度、换手率、RSI 等指标可以拼接到原来的两维特征后面feature_dim 从 2 变成 6 或 7LSTM 的 input_shape 同步修改即可。需要特别注意的是所有衍生因子在窗口切分时都必须只利用当前窗口及之前的信息千万不能把未来 N 天的均值算进去这类数据泄漏比标准化泄漏更隐蔽。5.2 改造后怎么验证改完不能只跑一次历史回测就完事。我的习惯是留出一段最近两周的数据不参与训练模拟“如果我在两周前用这套策略做决策每天收盘后拿到预测信号下周的真实走势是什么”然后把模拟信号和后续真实走势逐日对齐对比。这个验证方法的核心是让模型经历真实的“推理时延”历史回测里容易用到的信息在推理时根本拿不到比如当天收盘后才公布的成交额、次日才能确认的复权因子。强制把这些未来信息从验证数据里剥离出来模型才会真正显示实力。从那以后我每次改特征或调窗口都强制走一遍这个流程先检查数据泄漏再跑模拟预测连续对冲一周才敢把参数固定下来希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑