ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

LSTM股票收盘价预测Python源码实战:从数据到回测的完整流程

LSTM股票收盘价预测Python源码实战:从数据到回测的完整流程 简介这是一份面向计算机相关专业学生与项目实战学习者的深度学习实战源码以LSTM网络为核心完成股票收盘价格的预测任务适合用作毕业设计、课程设计或期末大作业的参考方案。项目经导师指导并获评审98分认可整体思路完整、代码可运行能帮助读者理解时序数据建模、特征构造与模型训练评估的完整流程。压缩包共8个文件约122KB包含1个核心Python脚本、1个Markdown说明文档、2个txt数据文件、2个xml配置、1个png预测效果图及gitignore等辅助文件结构精简、便于快速上手。资源已有406人学习下载读者可从中获取LSTM预测股票的完整实现代码、数据样例、依赖清单与效果可视化结果并参考README梳理项目运行与调参思路适合作为深度学习入门到实战的练手素材。1. 用 LSTM 预测股票收盘价一份 Python 源码能跑出什么又跑不出什么很多人第一次拿到「基于深度学习算法 LSTM 来进行股票收盘价格预测 Python 源码」这类项目第一反应是打开 K 线图想着能不能靠它算出明天的收盘价。我当年也是这么想的结果第一次跑完就翻车——模型在训练集上拟合得漂漂亮亮一到测试集就变成一条滞后一天的平移曲线。这不是代码写错了而是 LSTM 做时间序列预测这件事本身有它的边界。LSTM长短期记忆网络擅长的是从历史序列里学出短期依赖和趋势惯性它能告诉你「如果过去 60 天的量价形态延续下一日的价格大概率落在哪个区间」但它不会凭空知道明天有没有突发消息。这份源码真正能交付的是一套可复现的端到端流程数据获取与清洗、滑动窗口构造、LSTM 建模、训练、预测、反归一化和误差评估。适合谁适合已经会一点 Python、想拿一个完整深度学习项目练手的人也适合做量化入门、想把传统机器学习模型换成 LSTM 做对比的从业者。下面我按自己复现过多次的路径把每一步的参数和坑讲清楚。2. 数据准备与滑动窗口LSTM 股票预测的地基怎么打2.1 为什么收盘价不能直接丢进 LSTM股票收盘价是典型的非平稳序列均值方差随时间漂移。直接把原始价格喂给 LSTM网络会被绝对数值量级带偏梯度要么爆炸要么消失。常见做法是先做差分或对数收益率转换再做归一化。我一般用「对数收益率 MinMax 归一化」的组合因为对数收益率近似平稳且能削弱极端涨跌的影响。另一个必须想清楚的点是预测目标是什么。是预测下一日的收盘价还是预测下一日的涨跌方向源码里通常是回归预测收盘价但你要知道回归出来的数值误差 1% 和方向判断错误是两回事。特征维度上只用收盘价单变量也能跑但加入成交量、最高价、最低价、换手率后模型对形态的刻画会明显更好。这里有个血泪经验特征不是越多越好相关性高的特征堆进去只会增加过拟合风险一般控制在 5 到 8 个以内。2.2 用 pandas 拉数据并构造监督学习样本下面这段代码是我常用的数据准备骨架以单只股票日线为例字段名按常见行情接口的习惯命名。import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler # 读取行情数据至少包含 date, open, high, low, close, volume df pd.read_csv(stock_daily.csv, parse_dates[date]) df df.sort_values(date).reset_index(dropTrue) # 用对数收益率替代原始价格削弱非平稳性 df[log_ret] np.log(df[close] / df[close].shift(1)) df[vol_chg] df[volume].pct_change() df df.dropna().reset_index(dropTrue) feature_cols [log_ret, vol_chg, high, low] target_col log_ret # 预测下一日对数收益率 scaler MinMaxScaler(feature_range(-1, 1)) scaled scaler.fit_transform(df[feature_cols [target_col]]) def make_windows(data, lookback60, horizon1): X, y [], [] for i in range(len(data) - lookback - horizon 1): X.append(data[i:i lookback, :-1]) # 特征窗口 y.append(data[i lookback horizon - 1, -1]) # 目标值 return np.array(X), np.array(y) LOOKBACK 60 X, y make_windows(scaled, lookbackLOOKBACK, horizon1) print(X.shape, y.shape) # 形如 (样本数, 60, 4) 和 (样本数,)逻辑说明先算对数收益率和成交量变化率再统一归一化避免训练集和测试集分布不一致。make_windows把连续序列切成「过去 60 天特征 → 第 61 天目标」的监督样本。参数说明lookback是回看窗口日线常用 30 到 120太短学不到趋势太长会引入噪声且显存吃紧horizon是预测步长1 表示预测下一日改成 5 就是预测一周后。注意归一化必须只用训练集 fit再 transform 测试集否则就是典型的数据泄露这一点后面避坑章节还会展开。2.3 训练集、验证集、测试集怎么切才不骗自己时间序列不能随机打乱切分必须按时间先后切。我一般按 70% / 15% / 15% 顺序切或者用滚动窗口做 walk-forward 验证。随机切分会让模型在训练时「看到未来」评估指标虚高上线就崩。切完之后训练集用于拟合验证集用于调参和早停测试集只在最后评估一次不要反复拿测试集调参否则测试集就变成了验证集。3. 搭一个能收敛的 LSTM 模型层数、单元数和正则化怎么定3.1 用 PyTorch 定义 LSTM 回归网络import torch import torch.nn as nn class LSTMRegressor(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, 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.fc nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Dropout(dropout), nn.Linear(32, 1), ) def forward(self, x): out, _ self.lstm(x) # out: (batch, seq_len, hidden) last out[:, -1, :] # 取最后一个时间步 return self.fc(last).squeeze(-1) model LSTMRegressor(input_sizeX.shape[2], hidden_size64, num_layers2, dropout0.2)逻辑说明LSTM 层输出整个序列的隐状态回归任务通常取最后一个时间步因为它聚合了整段窗口的信息。batch_firstTrue让输入形状是(batch, seq_len, feature)符合直觉。参数说明hidden_size控制记忆容量日线数据 32 到 128 足够再大容易过拟合num_layers一般 1 到 2 层超过 2 层收益递减且训练变慢dropout在多层 LSTM 里才生效0.1 到 0.3 之间比较稳。注意input_size必须等于特征数量写错会直接报维度错误。3.2 损失函数、优化器和早停的配置from torch.utils.data import TensorDataset, DataLoader device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) X_t torch.tensor(X, dtypetorch.float32) y_t torch.tensor(y, dtypetorch.float32) n len(X_t) train_end, val_end int(n * 0.7), int(n * 0.85) train_ds TensorDataset(X_t[:train_end], y_t[:train_end]) val_ds TensorDataset(X_t[train_end:val_end], y_t[train_end:val_end]) test_ds TensorDataset(X_t[val_end:], y_t[val_end:]) train_loader DataLoader(train_ds, batch_size64, shuffleFalse) val_loader DataLoader(val_ds, batch_size64, shuffleFalse) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) best_val, patience, wait float(inf), 10, 0 for epoch in range(200): model.train() for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() loss criterion(model(xb), yb) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() model.eval() val_loss 0.0 with torch.no_grad(): for xb, yb in val_loader: xb, yb xb.to(device), yb.to(device) val_loss criterion(model(xb), yb).item() * len(xb) val_loss / len(val_ds) if val_loss best_val: best_val, wait val_loss, 0 torch.save(model.state_dict(), best_lstm.pt) else: wait 1 if wait patience: print(fearly stop at epoch {epoch}) break逻辑说明时间序列的 DataLoader 不要 shuffle否则窗口之间的时间顺序被打乱虽然单样本仍合法但会破坏批次内的时序统计。clip_grad_norm_是 LSTM 防梯度爆炸的后悔药max_norm 设 1.0 到 5.0 都常见。参数说明学习率 1e-3 是 Adam 的稳妥起点验证损失不降就减半weight_decay是 L2 正则1e-5 到 1e-4 之间早停 patience 设 10 到 20避免训练震荡时误停。注意保存的是验证集最优权重不是最后一个 epoch 的权重。3.3 预测结果反归一化与误差评估model.load_state_dict(torch.load(best_lstm.pt)) model.eval() with torch.no_grad(): pred_scaled model(X_t[val_end:].to(device)).cpu().numpy() # 反归一化目标列在 scaler 中的最后一列 target_min scaler.data_min_[-1] target_max scaler.data_max_[-1] pred_ret pred_scaled * (target_max - target_min) target_min true_ret y[val_end:] * (target_max - target_min) target_min from sklearn.metrics import mean_absolute_error, mean_squared_error mae mean_absolute_error(true_ret, pred_ret) rmse np.sqrt(mean_squared_error(true_ret, pred_ret)) print(fMAE{mae:.6f}, RMSE{rmse:.6f})逻辑说明模型输出的是归一化后的对数收益率必须用训练集 scaler 的对应列参数反变换回原始量纲才能和真实收益率比较。参数说明data_min_和data_max_是 MinMaxScaler 在训练集上拟合出来的边界直接用即可。评估指标上对数收益率的 MAE 通常在 0.01 到 0.03 量级RMSE 略大。注意不要用价格级别的 MAE 去衡量收益率模型量纲不同没有可比性。4. 避坑与排查LSTM 股票预测最常见的 5 个翻车现场4.1 现象测试集预测曲线整体滞后一天原因模型学到的是「昨天价格约等于今天价格」这种平凡映射本质是特征里包含了强自相关的收盘价且没有做差分。解决改用对数收益率作为目标或在特征里去掉原始 close只保留收益率和量能类特征。4.2 现象训练损失一直降验证损失很早就开始升原因过拟合。股票数据信噪比极低模型很容易记住训练段的噪声。解决减小 hidden_size 和层数增大 dropout加 weight_decay并严格执行早停。如果还不行减少特征数量。4.3 现象归一化后指标很好反归一化回价格就离谱原因归一化用了全量数据 fit或者反归一化时用错了列。解决scaler 只在训练集 fit测试集 transform反归一化时确认目标列在 scaler 中的索引别把特征列的边界套到目标上。4.4 现象换一只股票或换一个时间段模型完全失效原因模型学到的参数和特定股票的波动率、价格量级绑定泛化性差。解决做滚动训练定期用最近数据重新 fit scaler 和模型或者改用收益率这类相对量降低对绝对价格的依赖。4.5 现象训练时 loss 变成 nan原因学习率过大、序列过长导致梯度爆炸或者数据里有缺失值和无穷大。解决先检查数据 dropna 和 inf再降低学习率到 1e-4加梯度裁剪缩短 lookback。5. 把预测变成可用信号滚动回测与方向准确率的验证技巧模型跑通只是第一步真正决定这个方向值不值得投入的是它能不能在滚动回测里稳定跑赢基准。我一般会做两件事一是把预测的对数收益率还原成「下一日收盘价」的估计和真实收盘价比方向准确率二是用滚动窗口做 walk-forward每预测一段就往后挪一段重新用历史数据训练。方向准确率能到 55% 以上就算有微弱信号50% 附近基本等于随机。下面这个滚动回测骨架可以直接套用。def walk_forward(X, y, scaler, window500, step20): results [] for start in range(0, len(X) - window - step, step): tr_X, tr_y X[start:start window], y[start:start window] te_X, te_y X[start window:start window step], y[start window:start window step] # 这里复用前面的训练流程仅示意切分逻辑 results.append((te_y, te_X)) return results # 方向准确率预测收益率符号与真实符号一致的比例 direction_acc np.mean(np.sign(pred_ret) np.sign(true_ret)) print(f方向准确率: {direction_acc:.4f})逻辑说明walk-forward 的核心是「只用过去预测未来」每次窗口后移都重新训练或至少重新归一化避免任何未来信息泄露。参数说明window是单次训练用的历史长度日线一般 500 到 1000 个交易日step是前进步长20 表示每 20 天重新评估一次。方向准确率比 RMSE 更贴近交易场景因为价格回归误差小不代表方向对。我自己的习惯是任何 LSTM 股票预测项目先看方向准确率再看累计收益曲线最后才看 RMSE。如果方向准确率长期在 50% 附近晃别急着加层加特征先回头检查数据泄露和归一化。这套流程我踩过最多的坑就是「指标好看但不可交易」希望帮到你。本文还有配套的精品资源点击获取
返回列表