基于Python的LSTM股票走势预测:原理、实现与避坑指南
简介这是一份基于Python实现LSTM对股票走势预测的完整项目源码与文档资料面向具备一定Python基础、希望入门深度学习时序预测的读者也适合金融量化方向的学生作为课程设计或毕业设计参考。资源共13个文件以5个Python脚本为核心覆盖数据解析、LSTM模型搭建、训练与评估等主要环节同时包含1个CSV行情数据、1个训练好的pkl模型、2张可视化图片、3个pyc缓存文件以及1份Markdown说明文档整套压缩包仅358KB结构清晰便于快速定位代码、数据与说明。目前已有291人浏览学习。通过该资源读者能直接获得可运行的LSTM股票预测代码了解从数据加载、特征处理、模型训练到预测评估的完整流程并可借助真实的上证指数数据与可视化结果验证模型效果适合在此基础上开展参数调优或更换数据集进行二次开发。1. 拿到这个 zip 先别急着解压LSTM 股票走势预测项目在做什么打开“基于Python实现LSTM对股票走势的预测项目源码文档说明.zip”之前得先搞清楚一件事这类项目解决的不是“预测明天股价精确到分”而是“用历史行情推测未来几天的大致走势方向和相对高低”。LSTM长短期记忆网络是处理时间序列最常用的深度学习模型之一股票价格、成交量、指数走势天然就是时间序列数据所以这个组合在量化入门、毕业设计、个人投资辅助里反复出现。适合谁适合已经会用 Python 和 pandas、但还没碰过深度学习的开发者也适合想快速跑通一个时序预测全流程的从业者。需要先说破一句股票预测本质是高噪声预测模型输出的是统计意义上的参考不是稳赚信号——这个定位不摆正后面所有指标都会越看越玄。2. 先用原理说服自己为什么偏选 LSTM 而不是 ARIMA 或 RNN2.1 股票数据是时间序列但它是非平稳时间序列股票价格序列最麻烦的地方不是“数据量大”而是它不满足经典时间序列模型的平稳假设。拿某只个股的日线收盘价来说它的均值和方差会随着行情阶段明显漂移牛市里整体抬升熊市里波动放大这种序列在时序分析里叫非平稳序列。直接套 ARIMA光“差分到平稳”这一步就会把价格之间的长期依赖削掉一截更别说阶数 p、d、q 的选择本身就很玄学。动手验证非平稳并不难拿到源码包里的 data/stock_data.csv用 pandas 算一组滚动均值和滚动标准差就能看出来import pandas as pd df pd.read_csv(data/stock_data.csv, parse_dates[date]) df df.sort_values(date) df[roll_mean] df[close].rolling(20).mean() df[roll_std] df[close].rolling(20).std() print(df[[date, close, roll_mean, roll_std]].tail(10))rolling(20) 表示 20 个交易日的窗口。如果 roll_mean 和 roll_std 的曲线随行情明显漂移而不是围绕固定水平波动基本可以判断序列非平稳。LSTM 不要求数据平稳它靠门控机制自己学习趋势和长期依赖这就是这类项目普遍选它的第一个理由。ARIMA 和 LSTM 的适用边界可以看这张表选型时思路会清楚很多对比项ARIMALSTM数据平稳性要求严格需先差分不要求直接喂原始序列长期依赖靠差分和滞后阶数上限低门控机制显式建模长期记忆特征扩展只能加外生回归项可拼接多变量输入可解释性系数有统计含义黑匣子靠实验调参数据量要求几百个点也能跑样本越多优势越明显工程上手难度低中需处理 GPU/依赖环境2.2 LSTM 的门控机制遗忘门、输入门、输出门在做什么RNN 处理长序列时有个老毛病反向传播梯度沿时间步逐层相乘容易指数级缩小结果就是模型记不住“三个月前发生了什么”。LSTM 引入了一条 cell state细胞状态传送带让信息可以近乎无损地跨时间步传递梯度也有了直达通道。三个门各管一件事。遗忘门决定上一时刻的旧信息要丢掉多少输入门决定当前时间步的新信息写进 cell state 多少输出门决定最终把 cell state 的哪一部分输出给下一层。遗忘门的计算形式是f_t sigmoid(W_f · [h_{t-1}, x_t] b_f)不用背公式工程上记住一句话LSTM 是给 RNN 装了一个可学习的“记忆读写开关”开关的力度由当前输入和上一时刻的隐状态共同决定。对做预测的人来说真正要选的是两个参数hidden_size 和 num_layers。hidden_size 是记忆容量常见取 32 到 128太小记不住趋势太大会把噪声也背下来num_layers 一般取 2一层学短期形态一层学更高层的趋势再深就容易训练不稳收益也很小。2.3 项目源码的典型结构从 data/ 到 docs/ 的约定这类“项目源码文档说明”的 zip 包解压后结构通常比较固定。常见布局是. ├── data/ # 原始行情 CSV至少包含 date 和 close 列 ├── src/ │ ├── data_loader.py # 读数据、归一化、构造训练样本 │ ├── model.py # LSTM 模型定义 │ ├── train.py # 训练入口输出 loss 曲线和模型权重 │ └── predict.py # 加载权重预测并绘制对比图 ├── docs/ # 文档说明含环境配置和运行步骤 └── requirements.txt # Python 依赖清单拿到包先做三件事。第一解压到纯英文路径别放桌面“新建文件夹”里Windows 下中文路径经常让 pandas 和 torch 加载文件翻车。第二打开 requirements.txt 和 docs 目录下的说明确认 torch 是 CPU 版还是 GPU 版——这直接决定单次训练是几分钟还是几十分钟。第三打开 train.py 看数据路径写的是相对路径还是绝对路径改成你自己的 CSV 文件路径然后先跑通再改参数。不要一上来就调网络结构先把默认配置跑出第一版结果后面所有调优才有参照。3. 从数据到预测完整跑通 LSTM 股票预测的 Python 链路3.1 环境准备Python 版本、依赖安装与虚拟环境这类项目的文档说明里一般会写“需要 Python 3.8”实际评测下来 3.9 到 3.11 都比较稳3.12 偶发 torch 版本匹配问题。不同项目的 requirements.txt 差异很大常见是这几项torch、pandas、numpy、scikit-learn、matplotlib。为了避免把系统 Python 环境搞乱我一律先建虚拟环境cd lstm_stock_project python -m venv .venv source .venv/bin/activate # Windows 下用 .venv\Scripts\activate pip install -r requirements.txtvenv 创建时会自动带上当前 Python 的解释器和 pip装依赖不会污染全局环境。如果你同时做多个深度项目用 conda 管理更方便但只看这一个项目venv 够用且轻量。requirements.txt 里如果锁定了 numpy 的版本比如 numpy2.0那就别为了“最新版”去升级——旧版 torch 对 numpy 2.x 的兼容性有问题典型报错是_ARRAY_API not found回头还得降版本。3.2 数据读取与归一化为什么必须用 MinMaxScalerLSTM 对输入特征的尺度非常敏感。假如价格是 3000 点成交量是几百万手两者直接拼在一起喂给网络梯度更新会被大数值特征主导小数值特征学不动。常见做法是只对最重要的收盘价序列做单变量预测也可以把开盘价、最高价、最低价、成交量一起作为多变量输入。这里按源码包最常见的单变量演示来import pandas as pd from sklearn.preprocessing import MinMaxScaler df pd.read_csv(data/stock_data.csv, parse_dates[date]) df df.sort_values(date) data df[[close]].values.astype(float32) scaler MinMaxScaler(feature_range(0, 1)) scaled_data scaler.fit_transform(data)MinMaxScaler 把价格压缩到 [0,1] 区间公式是 (x - min) / (max - min)。注意 fit_transform 是在完整数据上计算的这一步后面有坑第 5 章专门讲。为什么用 MinMax 而不是 StandardScaler因为价格序列没有确定的下界但上界下界是明确的近期高低点MinMax 能保留价格之间的相对比例关系StandardScaler 适合分布近似正态的特征而收盘价显然不是。3.3 构造监督学习样本lookback 窗口决定了模型看多远的“过去”LSTM 输入不是单日价格而是一个固定长度的历史窗口。把时间序列变成“特征 标签”的过程叫构造监督样本。lookback60 表示用过去 60 个交易日的收盘价预测下一天的收盘价60 个交易日约等于一个季度的自然日对捕捉中短期趋势比较合适。import numpy as np def create_sequences(data, lookback60): X, y [], [] for i in range(lookback, len(data)): X.append(data[i - lookback:i, 0]) y.append(data[i, 0]) return np.array(X), np.array(y) lookback 60 X, y create_sequences(scaled_data, lookback)X 的每一行是连续 60 天的价格序列y 是对应第 61 天的价格。lookback 越小样本越多但模型只能看到近两周的短期波动噪声占比高lookback 越大比如 250模型会把一年前的行情风格也拖进来对当前市场不一定是好事而且样本数直接少掉 250 个。源码包一般给 30、60、90 这几个预设值先跑 60后面再对比。3.4 定义 LSTM 模型PyTorch 实现与维度说明PyTorch 是这类源码包最常用的框架。模型定义集中在 src/model.py 里常见写法是 LSTM 层加一层全连接import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, output_size1): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue ) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x shape: (batch_size, lookback, input_size) out, _ self.lstm(x) # out shape: (batch_size, lookback, hidden_size) out out[:, -1, :] out self.fc(out) return outinput_size1 表示每步只输入收盘价一个特征hidden_size64 是 LSTM 内部记忆容量num_layers2 代表堆叠两层 LSTM。batch_firstTrue 是为了让输入形状更直观先 batch再序列长度最后特征数。out[:, -1, :] 取最后一个时间步的隐状态因为我们要预测的是“未来一个点”不需要每个时间步都输出。训练和预测时还要把数据转成 PyTorch 的 TensorX_tensor torch.tensor(X, dtypetorch.float32).unsqueeze(-1) y_tensor torch.tensor(y, dtypetorch.float32).unsqueeze(-1)create_sequences 返回的 X 形状是 (样本数, lookback)但 LSTM 要求输入是三维(batch, seq_len, input_size)所以用 unsqueeze(-1) 在最后加一个维度变成 (样本数, 60, 1)。这个维度写错运行时会直接报 Expected 3D input, got 2D。3.5 训练循环与预测损失函数、优化器与反归一化模型定义好后训练部分集中在 train.pyfrom torch.utils.data import TensorDataset, DataLoader # 切分训练/测试集注意保持时间顺序 train_size int(len(X_tensor) * 0.8) X_train, X_test X_tensor[:train_size], X_tensor[train_size:] y_train, y_test y_tensor[:train_size], y_tensor[train_size:] dataset TensorDataset(X_train, y_train) loader DataLoader(dataset, batch_size64, shuffleTrue) model LSTMPredictor() criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr0.001) for epoch in range(100): model.train() total_loss 0 for X_batch, y_batch in loader: optimizer.zero_grad() output model(X_batch) loss criterion(output, y_batch) loss.backward() optimizer.step() total_loss loss.item() if (epoch 1) % 20 0: print(fepoch {epoch 1}, loss: {total_loss / len(loader):.6f})损失函数用 MSELoss也就是均方误差它对大误差的惩罚比 MAE 重能让模型更努力拟合偏离较大的点。优化器用 Adamlr0.001 是默认值训练不收敛时优先降到 0.0001而不是调大。DataLoader 的 batch_size64 表示每批喂 64 个样本太小梯度震荡大太大容易卡在局部最优。预测部分在 predict.pymodel.eval() with torch.no_grad(): pred_scaled model(X_test).numpy() # 反归一化还原成真实价格 pred_price scaler.inverse_transform(pred_scaled.reshape(-1, 1)) true_price scaler.inverse_transform(y_test.numpy().reshape(-1, 1))model.eval() 是必须的它把 dropout 和批归一化切到推理模式。torch.no_grad() 告诉 PyTorch 不需要计算梯度推理时能省内存和计算。反归一化是用之前 fit 好的 scaler 把 [0,1] 区间的预测值还原成真实价格想清楚inverse_transform 的输入必须和 fit 时的特征维度一致所以要先 reshape 成 (-1, 1)。4. 怎么判断模型有没有“预测力”指标、曲线和切分顺序4.1 回归指标不是万能的RMSE、MAE、MAPE 的边界训练完成后源码包里的文档一般会打印几个回归指标。常见的是这几种指标计算口径优点坑RMSE误差平方均值的根对大误差敏感个别异常报价会把指标拉高MAE误差绝对值平均稳健不受个别点影响掩盖了极端偏差MAPE百分比误差跨股票对比方便价格接近 0 时数值爆炸R²与均值基准的拟合优度直观看出拟合程度时序数据上参考意义有限指标不是越低越好要看绝对水平。比如股价在 50 到 80 元区间波动测试集 RMSE 是 2.5 元意味着平均预测偏离约 2.5 元这个误差对趋势判断可以接受对精确操盘远不够。如果是 3000 点的指数RMSE 是 20 点那方向参考价值就很弱了。我的习惯是记下指标的同时记下价格区间单看数值没有意义。4.2 方向准确率预测涨跌比预测具体价格更实用预测误差小不代表方向判断准。模型可能整体预测值都贴着昨天价格走误差看着不大但方向还是滞后。对股票走势预测这个标题来说更实用的指标是方向准确率真实值在涨预测值是不是也在涨。import numpy as np true_flat true_price.ravel() pred_flat pred_price.ravel() true_diff np.diff(true_flat) pred_diff np.diff(pred_flat) direction_acc np.mean(np.sign(true_diff) np.sign(pred_diff)) print(fdirection accuracy: {direction_acc:.2%})sign 把涨跌转成 1、-1相等表示方向一致。方向准确率超过 55% 在股票时序里就算有参考价值了超过 60% 是很好的结果。如果方向准确率在 50% 附近徘徊说明模型基本没有预测力这时候该回炉调数据或换训练策略而不是继续堆网络层数。4.3 时序切分顺序为什么不能随机打乱这是最容易踩的坑之一。普通机器学习里用 train_test_split 随机切分没问题但时间序列必须按时间顺序切前 80% 做训练后 20% 做测试。train_size int(len(X) * 0.8) X_train, X_test X[:train_size], X[train_size:] y_train, y_test y[:train_size], y[train_size:]如果用默认的 shuffleTrue 随机切分训练集里混进后面的数据测试集里混进前面的数据等于让模型“提前看到了未来”测试集指标会虚高。这种虚高一旦被你当成真实能力后面实盘或写报告就等着吃苦头。判断一个源码包是否规范先看它的数据切分是不是顺序切分这一眼就能看出作者有没有真跑过实盘验证。5. 五个高频踩坑记录数据泄露、过拟合和 zip 包的小毛病5.1 未来函数归一化时用了全量数据验证集被“剧透”现象训练集 loss 正常下降测试集 RMSE 也比预期好得多但把模型拿到新行情上预测效果立刻崩。原因很多人直接对完整数据调 scaler.fit_transform再切分训练测试。测试集的 min 和 max 已经影响训练数据的缩放比例模型在训练阶段就“偷看”了未来价格区间。解决先切分再在训练集上 fit测试集只用 transformraw_data df[[close]].values.astype(float32) train_data raw_data[:train_size] test_data raw_data[train_size:] scaler MinMaxScaler() train_scaled scaler.fit_transform(train_data) test_scaled scaler.transform(test_data)这段逻辑就是第 4 章切分顺序的延续也是这类项目里最容易翻车的点。检查源码包的 data_loader.py只要看到 fit_transform 用的不是训练集切片直接改掉再跑。5.2 不固定随机种子两次训练结果差到像两个模型现象同一份代码、同一个数据集上午跑一次 loss 收敛到 0.05下午跑一次收敛到 0.12预测曲线完全对不上。原因PyTorch 默认初始化权重是随机的DataLoader shuffle 也引入随机性。解决在 train.py 入口固定随机种子import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) set_seed(42)不算做任何改动就能复现训练。以后调参时固定种子能保证每次改动都是模型结构或参数带来的而不是随机波动。对要写论文或做对比实验的人这一步是底线。5.3 损失曲线震荡不降学习率太大或数据没归一化现象epoch 到 30 以后 loss 不但不降反而在 0.1 附近锯齿状震荡怎么也压不下去。原因通常是两个一是数据没归一化或归一化做了但没应用到输入张量梯度尺度不一致二是学习率太大Adam 在最优解附近来回弹跳。解决先确认 3.2 节的 MinMaxScaler 逻辑确实生效再把 lr 从 0.001 降到 0.0001还不行就加梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)梯度裁剪把梯度的范数限制在 1.0 以内适合股票这种频繁出现极端值的序列。注意 clip 的位置在 loss.backward() 之后、optimizer.step() 之前。5.4 预测曲线整体“滞后一拍”LSTM 在股票高噪声场景的固有表现现象把真实价格和预测价格画在一起预测曲线像真实曲线右移了一格——真实涨预测也在涨但拐点晚了一个交易日。原因单步回归模型用 MSE 逼近真实价格而股票价格近似随机游走模型发现最省力的解就是“预测值等于最近一天的价格”于是预测整体滞后。这不是代码 bug而是任务定义本身的问题。解决不追求精确价格把标签从价格改成涨跌方向做二分类或者用滚动预测看连续趋势第 6 章再或者直接接受这个限制只看预测方向和短期相对高低。认清这一条能省掉大量无效调参。5.5 zip 解压和依赖安装伪加密、missing entry 与版本冲突现象一zip 包按说明解压却提示输入密码或报 CRC 校验错误但文档说没有密码。原因部分项目包压缩时被误设了伪加密标志或者下载过程不完整。解决用 7-Zip 打开看条目是否真的标记加密若是伪加密用修复工具重置标志位。若报missing zip entry这类错误基本可以断定压缩包没下载完整直接重新下载别浪费时间修。现象二pip install 时报 torch 和 numpy 冲突。原因requirements.txt 里锁定的版本和你当前环境已有包冲突。解决先看报错日志通常是_ARRAY_API not found然后按 requirements.txt 的约束降 numpy 或升级 torch不要盲目装最新版。现象三data_loader.py 读 CSV 报路径不存在。原因zip 包在 Windows 下解压到中文路径或 train.py 里的相对路径和当前工作目录不一致。解决保持纯英文路径从项目根目录启动 python而不是从 src 目录里启动。6. 从单步预测到滚动预测把模型用到信号提示里单步预测只能看明天一天实用性有限。把模型嵌入一个滚动循环预测未来 5 到 10 天的走势才更像一个“能用的项目”。滚动预测的逻辑是把最近 lookback 天的数据作为初始窗口每预测出一个新值就把它追加到窗口末尾同时丢掉窗口头部最老的一天再预测下一步。def rolling_forecast(model, last_sequence, steps5): model.eval() current last_sequence.copy() preds [] with torch.no_grad(): for _ in range(steps): inp torch.tensor(current, dtypetorch.float32).unsqueeze(0) # current shape: (lookback, 1) out model(inp).item() preds.append(out) current np.roll(current, -1, axis0) current[-1, 0] out return np.array(preds).reshape(-1, 1)np.roll 把整个窗口向前平移一位最后一行用新预测值填充。这样预测第 2 天时输入窗口里包含了第 1 天的预测值后续每天都依赖前面的预测结果误差会累积所以多步预测的可靠性随步数递减。项目文档里如果给出 5 步滚动预测曲线基本就是这套逻辑。把滚动预测结果变成一个简单的决策辅助信号我一般这样处理计算预测序列的连续变化率连续多天预测涨幅超过阈值时标记为观察上涨连续多天跌幅超过阈值时标记为观察回调。pred_changes np.diff(pred_price.ravel()) / pred_price[:-1].ravel() if np.sum(pred_changes 0.01) 2: signal watch_rise elif np.sum(pred_changes -0.01) 2: signal watch_fall else: signal hold这只是规则式的信号提示不是交易建议。我自己第一次跑这类项目时最深刻的教训是拿测试集 RMSE 去推算实盘收益中间的差距基本就是行情波动本身。后来改成只关心方向准确率和连续趋势心态稳多了。如果你也是从这份代码起步建议先跑通默认配置再改 lookback、hidden_size 和预测步长每改一个参数就记一次指标慢慢形成自己的调参手感。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →