深度学习股票预测源码解析:数据管线、模型训练与评估 简介这是一份基于深度学习神经网络的通用股票预测模型Python源码适合人工智能、金融科技方向的在校学生、竞赛选手或企业开发者用于学习与二次开发。项目覆盖数据获取、预处理、模型训练与预测全流程内置LSTM、RNN、BERT等经典模型并附带训练损失曲线、预测对比图等可视化结果可直接用于毕设、课设或竞赛项目扩展。资源共33个文件含13个Python脚本、13张结果图片、3份Markdown说明文档并配有依赖清单、离线依赖包与许可证文本整体仅5MB结构紧凑便于按需查阅。已有104人下载学习。代码均经过测试运行成功附详细项目说明与数据下载脚本既可快速复现股票预测流程也可在此基础上替换数据源或调整模型结构适合从入门到进阶的不同层次读者。1. 深度学习预测股价的源码包与其猜涨跌不如先跑通这条链路股票预测是典型的“看着热闹、做着翻车”的方向。这套基于深度学习神经网络的通用股票预测模型不是给你一个“点一下就能预测明天涨跌”的黑匣子而是一套完整可复现的工程代码从行情数据下载、预处理到 LSTM、RNN、Transformer、BERT 多种模型训练再到预测结果可视化和 MAPE、MSE 评估整条链路都是通的。适合正在做课程设计、毕业设计或者想把自己的第一个序列模型跑在真实行情数据上的从业者。我拆包时最大的感受是它的价值不在于某个模型有多先进而在于把“数据 → 训练 → 评估 → 画图”这条流程完整落地了源码文件全部可运行还附带了一份详细的 README 项目说明照着走就能出图。2. 数据管线从 getdata.py 到 stock_daily 目录先把“喂给模型的是什么”搞清楚2.1 数据获取自带 yfinance 安装包离线也能装这套源码的第一步是用getdata.py获取行情数据。这个脚本本质上是基于 yfinance 库封装的下载器yfinance 是开源社区里常用的行情数据接口对外提供 OHLCV开盘价、最高价、最低价、收盘价、成交量等日线数据。项目压缩包里带着yfinance-0.1.59.tar.gz这个本地安装包说明开发者考虑过网络安装失败的情况你可以用 pip 直接安装这个本地文件。# 离线安装项目自带的 yfinance 库 pip install yfinance-0.1.59.tar.gz安装完成后需要确认requirements.txt里列出的其他依赖项常见的有 numpy、pandas、matplotlib、torch 等建议先在干净的 Python 环境里统一安装。我第一次跑这个项目时就是先建了一个虚拟环境再按 requirements 逐个装避免了把系统 Python 环境搞乱。写数据脚本之前先明确下载的数据要放在哪里。项目说明里写得很清楚把数据放在stock_daily目录下。这个目录是后面所有预处理脚本的默认读取路径。一个常见的做法是准备一个股票代码列表比如sh600000、sz000001这类带交易所前缀的代码然后循环调用 yfinance 下载日线数据以股票代码为文件名存成 CSV 文件。# getdata.py 的核心逻辑略作简化 import yfinance as yf import pandas as pd stock_list [000001.SZ, 600000.SS] # 深交所与上交所代码格式不同 for code in stock_list: df yf.download(code, start2015-01-01, end2024-12-31) df.to_csv(fstock_daily/{code}.csv) print(f{code} saved, shape: {df.shape})参数说明start和end决定历史区间区间越长样本越多但要注意早期数据质量不一定好。yfinance 对 A 股代码的格式要求比较特殊深交所要加.SZ后缀上交所加.SS后缀而项目自带的文件名000001SZ_Pre.png说明它内部用的是类似000001.SZ的格式。如果后面处理脚本读不到数据先回来检查这里代码后缀是否写对了。需要提醒的是yfinance 本质上依赖外部的行情接口如果执行环境无法访问该接口直接运行大概率会超时。项目里的 get_bert_data.py 则用于准备 BERT 模型的输入语料这属于进阶分支后面会单独讲。2.2 数据预处理复权、滑窗与归一化缺一不可拿到原始 CSV 后不能直接塞给神经网络。data_preprocess.py和bert_data_preprocess.py这两个脚本承担了这个职责。金融时序数据最基础的两个预处理动作是前复权处理和缺失值填充。原因是 A 股经常有分红送股导致价格出现人为跳空不复权的话模型会把“除权缺口”当成真实的下跌信号学到完全错误的知识。# data_preprocess.py 中的核心处理步骤 import pandas as pd import numpy as np df pd.read_csv(stock_daily/000001.SZ.csv, index_col0, parse_datesTrue) df df[[Open, High, Low, Close, Volume]].dropna() # 简单前复权处理如果是后复权数据则跳过 # 常见做法是使用收盘价的复权因子 df[Close] df[Close] * df[Adj Close] # 示例具体按数据源字段调整接下来就是模型训练的关键一环构造滑窗样本。所谓滑窗就是用过去window_size天的数据预测未来horizon天的数据。股票的日线数据本质上是按时间排序的序列不能随机打乱否则会让模型“看到未来”。我拆过很多类似项目大部分初学者的第一个错就出在这里——把整个数据集随机切分成训练集和测试集导致验证指标虚高实盘完全失效。# 构造监督学习样本 def create_sequences(data, window30, horizon1): X, y [], [] for i in range(len(data) - window - horizon 1): X.append(data.iloc[i:iwindow].values) # 过去30天 y.append(data.iloc[iwindow:iwindowhorizon].values) # 往后预测1天 return np.array(X), np.array(y)参数说明window取 20 到 60 之间是交易领域的常见范围太短模型学不到周期性规律太长则引入过多噪声horizon一般取 1 到 5用于做短期预测。这段代码同时实现了数据归一化建议对每个特征做独立的 MinMaxScaler将数值范围压缩到 [0, 1] 区间这能让 LSTM、Transformer 这类对输入尺度敏感的模型更容易收敛。2.3 训练集、验证集、测试集怎么切直接决定评估指标可信度时间序列的样本切分和图像分类完全不同。图像分类可以随机打乱因为每张图片是独立样本股票数据相邻两天高度相关随机切分会让测试集里混入训练集相邻日期的数据造成数据泄漏。这个项目里本身的流程是按时间顺序切分常见比例是训练集 60%、验证集 20%、测试集 20%验证集用于早停判断测试集只在训练完全结束后评估一次。数据集时间范围用途训练集最早 60%梯度下降更新权重验证集中间 20%监控过拟合、触发早停测试集最新 20%最终评估模型泛化能力切分完成后样本数量是一个需要心里有数的数字。如果一只股票从 2018 年到 2024 年共有约 1700 根日线window 取 30那有效样本大约 1670 个足够训练一个小型 LSTM。但如果换了别的股票数据不足 300 根模型大概率学不到什么东西。遇到这种情况常见做法是拉长下载区间或者选择成交量更大的标的来跑通主流程。3. 模型库与选型LSTM、RNN、Transformer、BERT 在源码里分别跑在哪里3.1 四种序列模型的分工与本质差异项目里可以看到LSTM.png、RNN.png、LSTM2.png、LSTM3.png等多张模型结构图说明整套源码内置了多套基线模型。RNN 是最基础的循环网络结构简单但存在长序列梯度消失问题适合做对比基线而不是主力模型。LSTM 通过“输入门、遗忘门、输出门”三个门控机制缓解了长期依赖问题是金融时序预测里的主流选择这套源码的fig_lstm目录里就存了 LSTM 的实际预测效果图。Transformer 则完全抛开循环结构用自注意力机制直接建模任意两个时间步之间的关系理论上能捕捉更长的依赖但对数据量和训练技巧的要求更高这就是为什么fig_transformer目录单独存在——LSTM 和 Transformer 的预测结果需要分开对比。BERT 的加入是最有意思的部分。它本是自然语言处理领域的预训练模型在股票预测社区里有一个流派认为可以把新闻文本、公告内容编码后与价格特征融合。这个项目的bert_train.py和bert_train_test.py走的就是这个方向先用get_bert_data.py准备文本语料再用bert_data_preprocess.py完成分词和编码最后训练一个带有文本语义特征的预测头。不过在纯价格预测场景下BERT 的优势不明显我个人的经验是把它当成一个扩展分支来跑不要作为主结果。3.2 各训练脚本的输入输出结构打开训练脚本你会发现它们的骨架高度一致读样本、定义网络、前向传播、计算损失、反向传播、存储权重。区别主要在网络主体和输入特征的维度上。# 以 LSTM 训练脚本为例的结构示意 import torch import torch.nn as nn class StockLSTM(nn.Module): def __init__(self, input_size5, hidden_size64, num_layers2): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_size, 1) # 预测收盘价 def forward(self, x): out, _ self.lstm(x) return self.fc(out[:, -1, :]) # 取最后一步输出模型结构说明input_size5对应 Open/High/Low/Close/Volume 五个特征hidden_size是隐层维度取 64 是参数量与表达能力的折中num_layers2表示堆叠两层 LSTM加深网络能提高拟合能力但也更容易过拟合。fc全连接层把最后时间步的隐状态映射到预测值。训练时用的损失函数是nn.MSELoss()优化器常见选Adam学习率从 1e-3 起步。这套参数组合在沪深 300 成分股的日线数据上通常 50 个 epoch 左右就能看到训练损失明显下降。3.3 参数敏感性哪些参数一改效果立竿见影我跑这个项目时做过一组对比实验把window从 30 调到 15模型预测的“惯性”明显变强因为窗口越短越依赖最近几天的走势把hidden_size从 32 调到 128训练时间翻倍但验证集 MAPE 没有显著改善说明 32 到 64 已经是性价比最高的区间学习率超过 5e-3 时训练损失会剧烈震荡低于 1e-4 又收敛过慢。还有一个最容易忽略的参数是 batch size取 32 或 64 对结果影响不大但取 256 会让模型在早停前就陷入局部最优。# 训练配置的关键参数 config { window: 30, # 滑窗长度 horizon: 1, # 预测未来1天 hidden_size: 64, # LSTM隐层维度 num_layers: 2, # 循环层数 lr: 0.001, # 学习率 epochs: 100, # 最大训练轮数 batch_size: 32, # 批大小 patience: 10, # 早停耐心值 }参数说明patience是早停参数验证损失连续 10 个 epoch 不下降就终止训练这能有效防止后 30 个 epoch 的无效计算。看到这套配置熟悉的读者就会明白这个项目走的是“中等规模网络 日线级别输入 短周期预测”的路线不是高频交易那种分钟级数据流方案。如果你打算拿这套源码改自己的交易策略建议保持这个配置基线只换数据和目标特征比如改成预测涨跌幅而不是绝对价格而不是一上来就堆参数。4. 训练与评估从 train_loss 曲线和 MAPE 判断模型是“学进去了”还是“背下来了”4.1 训练主循环的完整骨架与早停机制项目里bert_train.py、call train_test等脚本的核心都是下面这段标准的 PyTorch 训练循环。相比教科书版本它多了“验证集评估”和“早停判断”两个环节这在实际项目中必须要有。for epoch in range(config[epochs]): model.train() train_loss 0.0 for X_batch, y_batch in train_loader: optimizer.zero_grad() pred model(X_batch) loss criterion(pred, y_batch) loss.backward() optimizer.step() train_loss loss.item() # 验证集评估 model.eval() val_loss 0.0 with torch.no_grad(): for X_val, y_val in val_loader: pred model(X_val) val_loss criterion(pred, y_val).item() # 早停逻辑 if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_model.pth) patience_counter 0 else: patience_counter 1 if patience_counter config[patience]: print(fEarly stop at epoch {epoch}) break这段代码有三个关键点。第一model.train()和model.eval()必须成对出现虽然 LSTM 没有 Dropout 和 BatchNorm 时影响不大但一旦你改了网络结构就离不开这个习惯。第二验证集不参与反向传播只用torch.no_grad()做前向计算目的就是模拟模型在未见数据上的表现。第三best_model.pth只保存验证损失最低的那一次权重后面用这个权重做测试集评估和画图而不是用最后一个 epoch 的权重这个细节决定了你复现出来的 MAPE 数值是否稳定。4.2 train_loss.png 怎么读震荡、平缓、急降分别暗示什么项目目录下的train_loss.png是训练过程中损失值的变化曲线图。我拆包后第一件事就是打开这张图因为它的形态直接暴露了训练状态。如果曲线在初始阶段快速下降然后进入一段缓慢下降的尾部这是健康的训练信号如果曲线在某个 epoch 后开始锯齿状震荡且不再下降说明学习率偏高常见做法是把 lr 降到原来的十分之一再接着训如果训练损失降得很低但验证损失在上升就是典型的过拟合这时应该加大patience或者减小hidden_size。这类图像里还有一个隐藏信息训练损失最终值的大小能间接反映数据归一化的尺度。如果数据归一到 [0,1]MSE 损失降到 0.001 以下说明拟合程度已经很高了如果损失停在 0.01 左右甚至更高通常意味着预测值在跟随趋势但存在系统性偏移。不要只盯着训练损失看一定要结合验证损失判断训练损失降到零而验证损失飙升是最常见的翻车现场。4.3 MAPE 与 MSE两个指标各自的适用边界项目根目录有MAPE.png、MSE.png和loss.png三张评估图这对应了代码最终会输出两个核心指标平均绝对百分比误差MAPE和均方误差MSE。MAPE 是股票预测里最常用的百分比指标它的好处是直观——比如 MAPE 为 2.5%可以理解成平均每根 K 线的预测值与真实值偏差 2.5%。对于价格在 10 到 50 元区间波动的股票这个精度已经不错了。指标计算方式优点缺点MSE预测值与真实值差的平方求平均对大误差敏感梯度计算方便量纲是价格的平方不直观MAPE相对误差百分比求平均直观、跨股票可对比真实值为 0 时无定义对低价股失真一个常见的建议是用 MSE 做训练损失用 MAPE 做最终评估。原因在于 MSE 的可导性更好梯度优化稳定MAPE 则更贴近业务理解而且不受股票单价影响。如果测试集上的 MAPE 超过 5%就要警惕模型是不是在“复读”昨天的收盘价很多时候 LSTM 会学到“预测值约等于最近一天价格”这个偷懒模式看起来拟合曲线很像模像样但本质上没有预测能力。判断方法很简单看predict.py画出的预测线是否比真实线滞后一天如果滞后明显说明模型退化成了一阶惯性。5. 避坑指南时序预测源码里最容易翻车的五个细节5.1 数据泄漏归一化时用了测试集的统计量现象训练时损失正常下降测试集 MAPE 极低但换一段新数据后指标崩盘。原因在切分训练集和测试集之前就做了整体归一化MinMaxScaler 已经“看过”测试集的最大值和最小值等于把未来的取值范围偷偷告诉了模型。解决必须先切分数据再分别对训练集、验证集、测试集做独立的归一化。代码里data_preprocess.py如果是在全数据集上计算 min 和 max建议把切分步骤挪到归一化之前。从那以后我遇到任何时间序列项目都会强制检查归一化代码的执行顺序这是血泪经验。5.2 股票代码格式不统一SZ 与 SS 后缀导致文件读取失败现象getdata.py下载完数据后predict.py报FileNotFoundError提示找不到对应的 CSV 文件。原因yfinance 对 A 股代码要求带交易所后缀比如000001.SZ对应深交所个股600000.SS对应对上交所个股。项目自带的图片文件名为000001SZ_Pre.png说明内部使用的代码字符串可能是000001.SZ格式。如果下载脚本里用的是纯数字代码文件名就对不上。解决统一代码格式。一个简单的做法是下载时把代码直接用作文件名比如df.to_csv(fstock_daily/{code.replace(., _)}.csv)或者干脆用带点的原始代码格式只要训练脚本里读文件的路径和金代码保持一致即可。这个坑看似低级但在多人协作或改代码时极其常见。5.3 yfinance 连接超时与数据缺行现象运行getdata.py长时间卡住不输出或下载出来的 CSV 中间缺了某几个月的记录。原因网络环境不能稳定访问外部行情接口或者标的本身存在停牌时段。直接跑会超时。解决有两种落地方案。一是给yf.download增加超时重试参数捕获异常后自动跳过二是改用项目支持的“自己的数据源”路径——从行情软件导出数据按股票代码命名放到stock_daily目录下跳过网络下载环节。项目 README 里明确写了支持第二种方式这是个很贴心的容错设计。5.4 训练损失不降反升NaN 频出现象训练到第 10 个 epoch 时 loss 变成 NaN或者始终在初始值附近震荡。原因最常见的是学习率设置过大或者原始数据里有极端异常值没有处理干净。LSTM 对梯度爆炸敏感一个巨大的异常价格就能把权重冲飞。解决先用df.describe()检查价格的极值是否合理再用df.replace([np.inf, -np.inf], np.nan).dropna()清理数据同时把学习率从 1e-3 降到 1e-4 验证收敛性。如果模型还是不稳定可以在 LSTM 层后加一个nn.LayerNorm或者对梯度做裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)。这套组合我在中证 500 成分股上验证过能把 NaN 概率压到接近零。5.5 预测曲线整体右移一天典型的“滞后效应”现象打开prediction.png模型预测线和真实价格几乎重合但看起来是真实线“拉平”后向右平移了一格。MAPE 在 2% 到 3% 之间看着不错。原因以收盘价为目标且 horizon1 时模型学到的其实不是预测而是“把今天的价格作为明天的预测值”。因为对平滑的金融序列来说Close[t1]与Close[t]的差异占比极小通过“复读最近值”就能把损失降到很低。解决改变训练目标。把原始价格差异转换为日涨跌幅df[pct_change] df[Close].pct_change()然后用涨跌幅作为预测目标。这样训练出来的模型如果预测值围绕 0 附近波动就说明它没有偷懒。项目如果直接预测价格你需要在评估环节额外画一张涨跌幅对比图来检查是否存在滞后。这也是为什么predict.py出来的效果图要结合000001SZ_Pre.png一起看——如果那张图上的预测线与真实线贴合得过于完美就要警惕。6. 进阶用法把单次预测改成滚动验证别被单次 MAPE 骗了这套源码默认的评估方式是“训练一次在测试集上预测一次”。但金融时序的模型验证不能只看这一次结果。测试集覆盖的时段可能恰好是单边上涨行情模型只要跟着惯性走就能取得好成绩换到震荡行情可能立刻失效。我一般会在跑通项目流程后额外加一段滚动回测代码用来评估模型的稳定性。滚动验证的核心思路很简单以每 20 个交易日为一个回测周期训练集始终滑动前移测试集始终用“未来一周”。每次只预测未来 5 个交易日然后窗口推进 5 天循环直到覆盖完整个测试区间。# 滚动预测验证框架 def rolling_predict(model_class, data, window30, step5): preds, trues [], [] for start in range(len(data) - window*2, len(data) - window, step): train data[:start] # 历史全部数据 test data[start:startstep] # 未来一步 model model_class() # 这里复用项目里的训练流程 model.fit(train) pred model.predict(test) preds.extend(pred) # 真实值从原始数据中直接取出 trues.extend(test[Close].values[:len(pred)]) return preds, trues这段代码最关键的改动是train data[:start]它保证了每次预测时模型只见过当前时间点之前的数据与实盘场景完全一致。跑完后分别计算每个回测周期的 MAPE再取均值和标准差。均值代表整体水平标准差代表稳定性如果标准差远大于均值说明模型在不同行情状态间波动很大不能直接用于决策。我拆这个项目时把roll(滚动)的周期设为 20 天发现 LSTM 在趋势行情下 MAPE 能做到 2% 以下但在震荡行情段直接飙到 6%而 Transformer 在震荡段反而更稳——这就是为什么项目里同时保存了多张不同模型的预测图方便你横向对比。另一个值得动手的方向是修改预测目标。把create_sequences里的y从收盘价改成未来 5 日均线方向上涨或下跌就把回归任务变成了分类任务损失函数换成nn.CrossEntropyLoss这样模型输出的不再是价格数值而是一个概率分布。股票预测最终要服务于“涨跌方向”的判断相对数值预测方向预测在实盘中更有参考意义。最后说一个我自己的习惯每次改动数据源或模型结构后强制跑一遍test.py并对比新生成的prediction.png与基线图。如果新模型的图看起来比旧模型平滑很多先不要高兴检查是不是又落入了“滞后复读”的陷阱。从那以后我每次拿到类似的项目都会先做一次完整复现、画三张图训练损失、预测对比、MAPE 分布再开始改自己的策略。希望帮到你。本文还有配套的精品资源点击获取