ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python基于LSTM预测股市:完整源码、模型与数据集搭建指南

Python基于LSTM预测股市:完整源码、模型与数据集搭建指南 简介这份资源面向金融量化初学者与深度学习爱好者提供一套基于LSTM长短期记忆网络的股市预测完整实现帮助理解循环神经网络在时间序列预测中的应用。压缩包共19个文件约3.92MB包含9个Python脚本、2个CSV与1个Excel数据集、2个H5训练模型、2张预测对比图及说明文档覆盖数据加载、预处理、模型构建、训练、评估与可视化全流程。代码以Keras搭建LSTM对开盘价、收盘价、最高价、最低价和交易量等字段做清洗、归一化与序列化并输出MSE、MAE等指标同时涉及移动平均、指数平滑等技术指标与ARIMA对比思路。已有517人学习下载适合作为深度学习落地金融预测的练手案例读者可据此复现实验、调整参数并迁移到自有股票数据同时需注意市场复杂性模型预测不能替代风险管理与投资策略。1. 用 LSTM 预测股市一套能跑通的源码、模型与数据集该怎么搭很多人第一次搜「Python基于LSTM模型实现预测股市源代码模型数据集」心里想的其实是有没有一份现成的东西我拉下来改改就能看到预测曲线。现实是网上散落的 lstm模型代码 大多只给一个model.fit()就结束数据从哪来、特征怎么造、预测出来的价格为什么永远滞后一天全都没交代。这篇就把这套东西从头搭一遍用 Python 和 PyTorch 写一个 LSTM 时间序列预测的完整流程包含数据获取与清洗、特征工程、模型定义、训练、回测评估以及一套能直接复用的目录结构。适合会一点 Python、想入门 python量化交易策略代码 的从业者也适合已经跑过 lstm预测 但结果不对劲、想搞清楚边界的人。全程不碰任何实盘接口只做离线可复现的研究流程。2. 数据与特征LSTM 预测股市的输入到底怎么造2.1 为什么原始收盘价直接喂给 LSTM 会翻车把每日收盘价当成一维序列丢进 LSTM是最常见也最容易失败的做法。原因有三个。第一股价是非平稳序列均值和方差随时间漂移模型在训练集学到的数值范围到测试集就失效了。第二原始价格量纲大LSTM 的激活函数和梯度对输入尺度敏感不归一化几乎不收敛。第三单靠价格这一列模型学不到成交量、波动率这些真正带信息量的维度。常见做法是先做差分或对数收益率转换把绝对价格变成相对变化再叠加技术指标作为多变量输入。我一般会用「对数收益率 成交量变化率 滚动波动率 MACD 柱」这四类特征既保留了价格动态又引入了量能信息。注意所有滚动类指标都必须用shift(1)把当前 bar 的信息推到下一根否则就是未来函数回测会虚高得离谱。2.2 用 pandas 造多变量特征并做窗口切分下面这段代码负责从原始 OHLCV 数据生成特征矩阵并切成 LSTM 需要的三维张量(样本数, 时间步, 特征数)。数据来源可以是本地 CSV也可以是你自己用 python爬虫 抓下来存好的文件这里只约定列名。import numpy as np import pandas as pd def build_features(df: pd.DataFrame, window: int 30) - tuple: df 需包含列: open, high, low, close, volume索引为日期 window: 回看时间步即用过去多少天预测下一天 df df.copy() # 对数收益率平稳化处理 df[log_ret] np.log(df[close] / df[close].shift(1)) # 成交量变化率加 1 防止除零 df[vol_chg] df[volume].pct_change() # 10 日滚动波动率 df[volatility] df[log_ret].rolling(10).std() # 简易 MACD 柱: 12 日 EMA 减 26 日 EMA再取 9 日 EMA ema12 df[close].ewm(span12, adjustFalse).mean() ema26 df[close].ewm(span26, adjustFalse).mean() dif ema12 - ema26 dea dif.ewm(span9, adjustFalse).mean() df[macd_hist] (dif - dea) * 2 feat_cols [log_ret, vol_chg, volatility, macd_hist] df df[feat_cols [close]].dropna() # 标准化只用训练段统计量避免穿越 split int(len(df) * 0.8) train_mean df[feat_cols].iloc[:split].mean() train_std df[feat_cols].iloc[:split].std() df[feat_cols] (df[feat_cols] - train_mean) / train_std # 构造滑窗样本标签是下一天的对数收益率 X, y, dates [], [], [] values df[feat_cols].values target df[log_ret].values for i in range(window, len(df) - 1): X.append(values[i - window:i]) y.append(target[i 1]) dates.append(df.index[i 1]) return np.array(X, dtypenp.float32), np.array(y, dtypenp.float32), dates, df逻辑说明log_ret是预测目标也是特征之一vol_chg、volatility、macd_hist提供额外维度。标准化用训练段前 80% 的均值和方差这是防止数据泄漏的关键一步很多人图省事用全量统计量回测收益能好看一倍实盘直接打回原形。window30是回看天数A 股一个月约 22 个交易日30 天能覆盖一个多月的信息参数可以按标的调整日内高频数据要相应缩小。参数说明window太小如 5模型记不住趋势太大如 120样本数骤减且梯度容易消失。volatility的滚动窗口 10 是经验值波动剧烈的品种可以调到 20。MACD 参数用经典的 12/26/9不建议在没做参数敏感性分析前乱改。2.3 数据集切分与 DataLoader 的坑时间序列不能随机打乱切分必须按时间顺序切。常见做法是前 70% 训练、15% 验证、15% 测试或者用滚动窗口做 walk-forward 验证。下面用 PyTorch 的TensorDataset和DataLoader封装注意shuffleFalse。import torch from torch.utils.data import TensorDataset, DataLoader def make_loaders(X, y, batch_size64): n len(X) n_train int(n * 0.7) n_val int(n * 0.85) splits { train: (0, n_train), val: (n_train, n_val), test: (n_val, n) } loaders {} for name, (s, e) in splits.items(): ds TensorDataset(torch.from_numpy(X[s:e]), torch.from_numpy(y[s:e])) loaders[name] DataLoader(ds, batch_sizebatch_size, shuffleFalse) return loaders逻辑说明shuffleFalse是时间序列的铁律打乱会破坏时序依赖模型学到的就是噪声。batch_size 设 64 是折中太小梯度震荡太大显存吃紧且泛化变差。切分比例不是死的样本量少于 2000 时建议用 walk-forward每次训练窗口向前滚动评估更贴近实盘。3. LSTM 模型定义与训练从 nn.LSTM 到可收敛的损失曲线3.1 PyTorch LSTM 层的输入输出维度怎么对齐PyTorch 的nn.LSTM输入要求是(seq_len, batch, input_size)除非设batch_firstTrue。很多人第一次写 lstm模型代码 就卡在维度上报错expected input to have 3 dimensions。我一般统一用batch_firstTrue输入变成(batch, seq_len, input_size)和上面造的X形状一致。模型结构上单层 LSTM 加一个全连接输出层就够做基线。隐藏维度hidden_size设 64 或 128层数num_layers从 1 开始试超过 2 层在小数据集上基本过拟合。别忘了dropout但 PyTorch 的 LSTM 只有在num_layers 1时 dropout 才生效单层要自己在输出后加nn.Dropout。import torch.nn as nn class StockLSTM(nn.Module): def __init__(self, input_size4, hidden_size64, num_layers1, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0.0 ) self.dropout nn.Dropout(dropout) self.fc nn.Linear(hidden_size, 1) def forward(self, x): # x: (batch, seq_len, input_size) out, (h_n, c_n) self.lstm(x) # 取最后一个时间步的输出 last out[:, -1, :] return self.fc(self.dropout(last)).squeeze(-1)逻辑说明out[:, -1, :]取序列最后一步的隐藏状态代表模型对整段历史的压缩表示。squeeze(-1)把输出从(batch, 1)压成(batch,)和标签形状对齐。input_size4对应前面四个特征特征增减时这个参数必须同步改否则第一层矩阵乘法直接报错。参数说明hidden_size从 64 起步数据量上万可以试 128num_layers1是基线加到 2 层时记得 dropout 才会生效dropout0.2是常规正则过拟合严重可以提到 0.3~0.5。学习率用 1e-3 配 Adam损失函数用 MSE 或 HuberHuber 对收益率里的极端值更稳。3.2 训练循环与早停损失不降时该看什么训练循环本身不复杂关键是加验证集监控和早停。下面这段是可直接复用的训练骨架包含梯度裁剪LSTM 不加裁剪容易梯度爆炸。import torch import numpy as np def train_model(model, loaders, epochs50, lr1e-3, patience8, devicecpu): model model.to(device) optimizer torch.optim.Adam(model.parameters(), lrlr) criterion torch.nn.HuberLoss() best_val float(inf) wait 0 history {train: [], val: []} for epoch in range(epochs): model.train() train_loss [] for xb, yb in loaders[train]: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() train_loss.append(loss.item()) model.eval() val_loss [] with torch.no_grad(): for xb, yb in loaders[val]: xb, yb xb.to(device), yb.to(device) val_loss.append(criterion(model(xb), yb).item()) tr, va np.mean(train_loss), np.mean(val_loss) history[train].append(tr) history[val].append(va) print(fepoch {epoch1:03d} train {tr:.6f} val {va:.6f}) if va best_val: best_val va wait 0 torch.save(model.state_dict(), best_lstm.pt) else: wait 1 if wait patience: print(fearly stop at epoch {epoch1}) break return history逻辑说明clip_grad_norm_把梯度范数限制在 1.0防止 LSTM 在长序列上梯度爆炸。早停的patience8表示验证损失连续 8 轮不降就停保存验证集最优的权重而不是最后一轮这是避免过拟合的标准操作。HuberLoss在收益率预测上比 MSE 稳因为收益率分布尖峰厚尾MSE 会被极端值带偏。参数说明epochs50是上限实际常被早停截断lr1e-3是 Adam 的常用起点损失震荡可以降到 3e-4patience设 5~10太小会误停太大浪费算力。训练时盯验证损失如果训练损失降、验证损失升就是过拟合该加 dropout 或减 hidden_size。3.3 预测结果怎么还原成价格并评估模型输出的是对数收益率要还原成价格才能和真实收盘价对比。还原公式是pred_price last_close * exp(pred_ret)。评估指标别只看 MSE方向准确率涨跌方向对不对对交易更有意义。def evaluate(model, loaders, df, devicecpu): model.eval() preds, trues [], [] with torch.no_grad(): for xb, yb in loaders[test]: preds.append(model(xb.to(device)).cpu().numpy()) trues.append(yb.numpy()) pred_ret np.concatenate(preds) true_ret np.concatenate(trues) # 方向准确率 dir_acc np.mean(np.sign(pred_ret) np.sign(true_ret)) # 收益率 MSE mse np.mean((pred_ret - true_ret) ** 2) print(f方向准确率: {dir_acc:.4f} MSE: {mse:.8f}) return pred_ret, true_ret逻辑说明方向准确率能到 55% 以上就算有信息量纯随机是 50%。MSE 在收益率尺度上数值很小看相对大小即可。注意测试集必须是最末尾那段不能参与任何训练或标准化统计。4. 避坑与排查LSTM 预测股市最常见的五个翻车点4.1 预测曲线永远滞后一天现象把预测价格和真实价格画在一起预测线像是真实线右移了一格形状几乎一样。原因模型学到的最优策略就是「预测值等于昨天收盘价」因为收益率接近白噪声MSE 损失下这是最稳的解。解决改预测目标从绝对价格改成收益率或涨跌方向损失函数换成方向敏感的或者在特征里加入领先性指标。滞后本身说明模型没学到额外信息不是代码 bug。4.2 验证损失比训练损失还低现象训练损失 0.001验证损失 0.0008看起来模型泛化很好。原因时间序列切分后验证段恰好波动更小或者标准化用了全量统计量导致泄漏。解决检查标准化是否只用训练段统计量用 walk-forward 多折验证看损失是否稳定如果验证段是低波动区间换一段再测。4.3 换了只股票模型就完全失效现象在沪深300上训练好的模型直接预测创业板个股方向准确率掉到 50% 以下。原因不同标的的波动率、量价关系差异大标准化参数和模型权重都不通用。解决要么每只标的单独训练要么在特征里加入横截面标准化如相对行业指数的超额收益让输入分布对齐。跨标的迁移不是不能做但需要更大的数据集和更复杂的结构。4.4 训练损失不下降一直卡在初始值附近现象跑了几十轮损失几乎不动。原因学习率太大导致震荡或者输入没标准化、量纲差异悬殊或者input_size和实际特征数不匹配但没报错某些写法会静默广播。解决先打印一个 batch 的输入统计量确认均值和方差在合理范围学习率降到 1e-4 试检查input_size是否等于X.shape[2]。4.5 回测收益高得离谱现象测试集上的策略年化收益 200%夏普比率 5 以上。原因几乎可以肯定是未来函数。常见来源包括标准化用了全量数据、滚动指标没 shift、用当天收盘价预测当天收盘价、测试集参与了早停选择。解决逐行检查特征生成代码所有用到close的地方确认是否shift(1)标准化统计量严格限定在训练段早停只用验证集测试集只在最后评估一次。5. 把 LSTM 预测做成可复用的研究流程目录、参数与验证习惯一套能长期迭代的代码比一次跑通的结果更重要。我一般会把项目组织成下面这个结构数据、特征、模型、评估各自独立换标的或换特征时只动对应模块。目录/文件职责关键参数data/raw/存放原始 OHLCV CSV列名统一为 open/high/low/close/volumedata/processed/特征矩阵与切分后的 npzwindow、特征列清单src/features.py特征生成与标准化window、滚动窗口、MACD 参数src/model.pyLSTM 定义input_size、hidden_size、num_layers、dropoutsrc/train.py训练循环与早停lr、epochs、patience、batch_sizesrc/evaluate.py方向准确率与回测测试段起止、交易成本假设configs/每个标的的参数 yaml标的代码、日期范围、超参参数管理上我习惯把window、hidden_size、lr这些写进 yaml训练脚本读配置这样跑对比实验时不用改代码。验证习惯上至少做三件事一是用 walk-forward 而不是单次切分看指标在不同时间段的稳定性二是把方向准确率和收益率 MSE 一起看单看 MSE 会被滞后解骗三是每次改特征或超参都在同一测试段上对比避免「这次好像好一点」的玄学判断。最后一个具体技巧把预测目标从「下一天收益率」改成「未来 N 天累计收益率」N 取 3 到 5。这样模型不用纠结单日噪声学的是短期趋势方向准确率通常能提升几个点也更贴近实际调仓频率。代价是样本重叠评估时要用 purged walk-forward 去掉重叠区间否则指标会虚高。这个改动我踩过坑一开始没做 purging回测夏普直接翻倍后来老老实实按时间隔离才拿到可信数字。希望帮到你。本文还有配套的精品资源点击获取
返回列表