ARTICLE DETAIL

资讯详情

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

Python LSTM股票预测实战:源码拆解与避坑指南

Python LSTM股票预测实战:源码拆解与避坑指南 简介基于Python与LSTM的股票走势预测项目提供完整源码与配套文档面向金融数据分析初学者及深度学习爱好者用来学习如何利用长短期记忆网络对股票指数历史数据进行建模与预测。压缩包共13个文件压缩后约358KB核心为5个Python脚本覆盖数据读取、序列处理、LSTM模型构建、训练与评估等模块另有CSV行情数据、训练得到的模型pkl文件、运行结果图示及Markdown说明文档便于直接对照理解完整流程。目前已有291人学习下载。读者可基于内置数据直接运行训练与预测脚本结合源码理清时间序列切分、模型参数传递和误差评估等关键环节说明文档还能辅助快速复现实验适合作为课程设计、毕业设计或入门深度学习的实战参考。1. 基于Python实现LSTM对股票走势的预测这份源码包到底值不值得跑通打开这份基于Python实现LSTM对股票走势的预测项目源码文档说明.zip你手上其实是一套完整的深度学习入门级量化方案——从tushare或yfinance拉行情、做滑窗构造数据集、训练LSTM神经网络、到输出预测曲线和回测指标全流程代码加一份说明文档。它不是能直接替你赚钱的“自动取款机”但它是理解时间序列预测、搞清楚神经网络怎么处理金融数据的最短路径。这套方案适合两类人一是刚学完Python基础、想找一个能写进简历的深度学习项目的同学二是已经在用传统时序方法ARIMA、移动平均做股票分析、想试试LSTM到底比统计方法强在哪的从业者。它解决的核心问题是怎么把过去N天的开盘价、收盘价、成交量喂给LSTM让它预测未来一天的走势并且让你看得懂预测结果为什么有时候准得离谱、有时候又完全反向。注意这份源码里“预测股票走势”通常做的是价格回归或涨跌二分类不是预测精确到分的股价。文档说明里一般会写清楚数据来源、滑窗长度、网络层数和评估指标。下面我用最常见的工程实现路径把这份源码从头到尾拆开讲包括参数怎么设、坑在哪、以及拿到手后怎么改造成你自己的东西。2. 数据准备是第一个分水岭拉取行情与构造滑窗数据集任何LSTM股票项目代码能不能跑通、模型有没有意义90%取决于数据处理。源码包里如果有数据文件常见的是CSV格式一列日期加OHLCV五列恭喜你省了最麻烦的一步如果只有拉数脚本你要自己去搞数据。下面直接给出一套能落地的方案。2.1 拉取股票数据的三种来源与选型理由常见的做法是这三种tushare、baostock、yfinance。tushare的pro版需要积分部分接口有权限限制baostock免费且国内数据全但文档老旧yfinance不需要注册但有时候连不上而且退市股票数据会消失。我一般首选baostock原因很简单免费、不需要token、A股日线数据够用、返回的DataFrame结构干净。import baostock as bs import pandas as pd # 登录baostock这个登录不是账户认证是建立本地会话 lg bs.login() # 字段date, code, open, high, low, close, preclose, volume, amount, adjustflag rs bs.query_history_k_data_plus( sh.600519, # 贵州茅台沪市股票代码带sh前缀 date,open,high,low,close,volume, start_date2018-01-01, end_date2023-12-31, frequencyd, adjustflag2 # 2代表前复权1代表后复权3代表不复权 ) df [] while (rs.error_code 0) rs.next(): df.append(rs.get_row_data()) data pd.DataFrame(df, columnsrs.fields) bs.logout()这里最关键的参数是adjustflag它直接决定你的收盘价序列长什么样。前复权会把历史价格按最新除权除息信息调整适合训练模型不复权会在除权日出现断崖式跳空模型会把这个“假跌”当成真实行情预测全乱。做LSTM预测必须用复权数据这是第一个血泪经验。2.2 滑窗构造把时间序列变成监督学习样本LSTM不认识“时间”它只认识“序列”。所以你要把原始的日期序列改造成[输入特征, 目标标签]的结构。源码里最常见的做法是用过去lookback天的数据预测未来forward天的数据或者只预测下一天的收盘价。滑窗长度一般取20、30、60对应一个月到三个月的交易日。import numpy as np def create_sequences(data, lookback20, forward1): X, y [], [] # 用前lookback个样本预测未来forward个样本 for i in range(lookback, len(data) - forward 1): X.append(data[i - lookback:i]) # 过去lookback天的所有特征 y.append(data[i forward - 1, 3]) # 未来第forward天的收盘价假设第3列是close return np.array(X), np.array(y) # 假设data是numpy数组每行是[open, high, low, close, volume] features data[[open, high, low, close, volume]].values X, y create_sequences(features, lookback30, forward1) print(X.shape, y.shape) # 比如 (780, 30, 5) 和 (780,)这里X的形状是(样本数, 滑窗长度, 特征维度)刚好是LSTM要求的输入格式。滑窗长度就是LSTM的时间步数它决定了模型“记得”多长的历史。一个常见的翻车场景是把滑窗设成250天一年以为模型能学到更多结果训练集样本数骤减模型反而欠拟合。一般30天到60天是相对稳的区间你要用多少天可以用滑窗扫描实验来定后面第6章会说。2.3 归一化LSTM的命门所有神经网络对输入特征的量纲都敏感股票数据尤其夸张——收盘价是几十块到几百块成交量是几万手到几亿手不归一化训练直接发散。但归一化有个大坑要用训练集的统计量去归一化测试集绝对不能把全部数据混在一起算均值方差再做切分否则就是未来函数泄漏。from sklearn.preprocessing import MinMaxScaler # 先切分再归一化 train_size int(len(data) * 0.8) train_data data.iloc[:train_size] test_data data.iloc[train_size:] scaler MinMaxScaler(feature_range(0, 1)) # 只在训练集上fit train_scaled scaler.fit_transform(train_data[[open, high, low, close, volume]].values) # 测试集用同一个scaler做transform test_scaled scaler.transform(test_data[[open, high, low, close, volume]].values)MinMaxScaler把数据压到0到1之间但它的fit_transform和transform分离是整个项目最容易写错的地方。如果你不小心对全量数据先归一化再切分测试集的信息就提前“看”到了结果就是训练时测试集指标虚高实盘一用就露馅。这个点值得你在看源码时专门检查一下。2.4 文档说明里最该先读的部分拿到zip包别急着跑pip install -r requirements.txt先把文档说明里这几页翻完数据来源表哪个股票、哪段时间、复权方式、数据文件格式列名和单位、训练集测试集切分比例。很多源码包为了“好看”切分比例写成9:1甚至直接随机切分——时间序列不能随机切分必须按时间顺序切否则过去预测未来这个基本逻辑都不成立。文档说明里如果没写切分逻辑你自己动手改的时候务必按时间顺序排。3. 从零搭建LSTM模型Keras还是PyTorch选型与最小可运行代码源码包里的模型部分基本是两种框架之一TensorFlow/Keras或者PyTorch。Keras的上手成本低API封装得狠适合快速验证PyTorch的灵活性强自定义损失函数、调试断点都方便。大部分股票预测项目源码用Keras居多因为代码量少。这一章我以Keras为例按工程习惯把模型代码逐段拆开。3.1 为什么选LSTM而不是RNN或GRU这一步是纯选型问题。普通RNN在序列长的时候梯度消失得厉害学不到远处信息LSTM通过门控机制输入门、遗忘门、输出门把长期依赖保留住但也因此参数多、训练慢GRU是LSTM的简化版参数更少训练更快效果在很多任务上不输LSTM。在股票预测这个场景里我见过用GRU跑出来的效果跟LSTM差不多的但源码标题既然写了LSTM就先用LSTM把主干跑通后面想替换成GRU只需改一行代码。虽然有些研究声称LSTM在金融时序上不如简单线性模型但作为学习项目LSTM的价值在于它逼着你理解“序列建模”这件事而不是真的去找圣杯。3.2 最小可运行模型输入层、LSTM层、Dense层用Keras搭一个能跑的LSTM预测模型通常只需要三层一个LSTM层一个Dropout层一个全连接层。输入形状必须是(batch_size, lookback, n_features)这一点新手最容易搞错——以为输入只要二维。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping model Sequential() # input_shape只需要写(time_steps, n_features)batch_size自动推断 model.add(LSTM(units64, input_shape(X_train.shape[1], X_train.shape[2]), return_sequencesFalse)) model.add(Dropout(0.2)) # 随机丢弃20%的神经元防止过拟合 model.add(Dense(units1)) # 输出一个值预测的下一天收盘价 model.compile(optimizeradam, lossmse, metrics[mae])input_shape里的两个数字分别对应滑窗长度和特征数量这个值必须在create_sequences之后通过X.shape确认否则模型构建直接报维度错误。return_sequencesFalse表示只输出最后一个时间步的隐藏状态用于做单步预测如果你要预测未来多个时间点这个参数要改成True并在后面再接一个TimeDistributed层。这里的Dense层输出维度是1对应单一预测目标如果要做涨跌二分类需要把激活函数改成sigmoid损失函数换成binary_crossentropy。源码包里通常会注释着两种用法或者文档说明里单独讲分类怎么做。3.3 训练参数epochs、batch_size、早停法训练LSTM的参数就那么几个但每个都可能让你翻车。epochs设大了过拟合设小了欠拟合batch_size设大了训练快但梯度震荡小容易陷入局部最优设小了训练慢但泛化可能更好。工程上最稳的配置是配合早停法让它自动决定训练多少轮。# EarlyStopping: 监控验证集损失连续patience轮不下降就停 early_stop EarlyStopping(monitorval_loss, patience20, restore_best_weightsTrue) history model.fit( X_train, y_train, epochs200, batch_size32, validation_split0.1, # 从训练集再切10%做验证 callbacks[early_stop], verbose1 )validation_split0.1是从训练集尾部切出10%不是随机切因为Keras默认就是取最后10%这刚好符合时间序列的顺序要求。patience20意味着连续20轮验证集损失不降低就停止训练restore_best_weightsTrue是关键它会在训练停止后把权重回滚到验证集损失最低的那一轮——否则你拿到的模型是倒数第二轮的状态指标已经变差了你都不知道。有个很典型的坑把epochs写成500但没开早停训练到几百轮之后验证集损失开始一路走高模型直接过拟合到“背答案”的状态。你只看训练集损失那一条曲线它还在往下掉以为一切正常。所以你必须看训练集和验证集两条损失的间距——间距越拉越大就是过拟合在发生。3.4 输出预测与反归一化千万记得还原模型输出的预测值在0到1之间直接拿去和真实股价画图对比是看不出好坏的要先用之前保存的scaler反变换回原始价格。这一步漏掉的后果是你对着归一化的曲线瞎分析半天得出的结论全部无效。# 预测并反归一化 pred model.predict(X_test) # 难点模型只预测了close列但scaler是按5列fit的需要构造完整形状 pred_full np.zeros((pred.shape[0], 5)) pred_full[:, 3] pred[:, 0] # 把预测值放到close列的位置 pred_inverse scaler.inverse_transform(pred_full)[:, 3] # 同理还原真实值 y_test_full np.zeros((y_test.shape[0], 5)) y_test_full[:, 3] y_test y_test_inverse scaler.inverse_transform(y_test_full)[:, 3]这段反归一化是源码里最容易糊弄的地方。有些人直接scaler.inverse_transform(pred)然后报维度错误就改成单独对预测值取最后一列实际上没有还原到原始价格。上面这段代码的思路是构造一个和scaler特征维度一致的临时数组把预测值塞进对应位置还原后再取出来。这个技巧你能用得上因为当你的特征有5列时scaler不会允许你用一列直接做inverse。4. 模型评估不能只看图用数值指标判断预测靠不靠谱训练完模型源码包里通常有一段画图代码把真实收盘价和预测收盘价画在同一个坐标系里。图看着贴身是好事但光看图会被骗——走势方向一致但滞后一天或者数值整体偏移但形状相同这些靠眼睛看不出来。评估必须落到数值指标上。4.1 回归类指标MAE、RMSE、MAPE各代表什么如果你的源码输出的是预测价格那评估指标大概率用的是这些指标公式含义盯它的原因MAE平均绝对误差mean(abs(y_true - y_pred))误差的平均绝对值单位是元直观反映平均差多少钱RMSE均方根误差sqrt(mean((y_true - y_pred)^2))对大误差更敏感异常预测会显著拉高它MAPE平均绝对百分比误差mean(abs((y_true - y_pred) / y_true)) * 100%无量纲可以跨股票比较通常2%以内算不错代码实现非常简单但有个细节MAPE在真实值为0或者接近0的时候会爆炸因为分母趋近于0。股价一般不会出现0但如果你预测的是某些特殊指标就要留个心眼。计算这些指标前确保y_true和y_pred都是反归一化后的原始价格用归一化后的数值算出来的误差单位是“缩放后的单位”完全不具解释性。4.2 方向准确率真正衡量“预测走势”的硬指标股票预测的本质不是预测精确价格而是判断方向。涨跌方向对了你才能做多或做空方向错了价格预测再贴近也没用。源码里如果只画了价格曲线没算方向准确率你可以自己补上。def direction_accuracy(y_true, y_pred): # 比较相邻两天的价格变动方向是否一致 true_dir np.sign(np.diff(y_true)) # 真实走势1涨-1跌0平 pred_dir np.sign(np.diff(y_pred)) # 过滤掉持平的情况不然0会被判错 mask true_dir ! 0 return np.mean(true_dir[mask] pred_dir[mask]) # 使用反归一化后的真实值和预测值 acc direction_accuracy(y_test_inverse, pred_inverse) print(f方向准确率: {acc:.2%})方向准确率超过55%就已经是有统计意义的水平了超过60%属于优秀模型。但你必须理解一件事LSTM预测出来的价格曲线天生比真实曲线平滑得多因为它本质上是学习历史均值趋势这会导致转折点附近的方向预测几乎必然出错。所以方向准确率偏低不一定是代码问题而是LSTM模型的本质局限。4.3 回测框架如果源码包里有这层恭喜你一份完整的LSTM股票预测源码包里文档说明部分通常会附带一个简单的回测逻辑按预测方向决定买入卖出计算最终的累计收益。常见做法是模拟交易预测明天涨就持仓预测跌就空仓或卖出。回测代码不长但坑很多最典型的是没有考虑交易手续费和滑点。一个最简单的无手续费回测示例capital 100000 # 初始资金10万 position 0 # 持仓数量 for i in range(len(pred_inverse) - 1): if pred_inverse[i] y_test_inverse[i]: # 预测下一日上涨 position capital / y_test_inverse[i] # 全仓买入 capital 0 else: capital position * y_test_inverse[i] # 全部卖出 position 0 # 最终资产 final_asset capital position * y_test_inverse[-1] print(f最终资产: {final_asset:.2f})这段逻辑假设你能在收盘价精确成交且不扣手续费。真实市场中印花税、佣金、滑点每笔交易会吃掉千分之一到千分之三频繁交易下收益会被显著拖累。源码包里的回测通常为了教学简化了这个部分你自己跑通之后可以加上commission0.001的参数模拟真实成本。5. LSTM股票预测避坑手册5个把新手搞到崩溃的现场这一章是整份源码包价值密度最高的地方。下面5个问题是我在跑类似项目时真实遇到过、也是论坛上反复出现的求助贴主题。每条按“现象 → 原因 → 解决”写你可以直接用这些排查手顺。5.1 模型损失变成NaN训练曲线直接画不出现象训练刚开始几轮loss变成nan或者前一轮loss是数字、下一轮直接消失。原因分三种第一种是学习率太大Adam优化器默认学习率0.001在特定数据分布下梯度爆炸第二种是数据里有NaN或者无穷大的原始值baostock返回的停牌日数据可能是空字符串转成float后变成NaN第三种是归一化后数据还有极端值比如某个股票一天涨了100倍数据错误让梯度瞬间爆炸。解决先把原始数据data.isnull().sum()检查一遍把所有含NaN的行用data.dropna(inplaceTrue)清掉然后把模型的学习率调低一个量级在compile的时候传optimizertf.keras.optimizers.Adam(learning_rate0.0001)。这两个操作能解决90%的NaN问题。5.2 预测曲线比真实曲线滞后一天现象画出来的预测曲线和真实曲线长得一模一样但整体向右平移了一天方向转折点永远比真实慢一天。这不是bug这是LSTM做单步预测的固有现象。模型学到的是“今天的价格和明天最接近”于是最优策略就是输出昨天的价格。你的模型实际上变成了一个延迟一期的复制器方向准确率在平稳段很高一到转折点就全部踏空。解决不要只用收盘价做特征加入涨跌幅百分比特征或者把目标改成“未来5天的累计涨跌方向”做分类让模型必须学会预测变化量而不是复读当前值。另一种做法是把输入序列做差分处理用data[close].diff()生成新的特征列强迫模型学变化规律。5.3 训练集效果很好测试集完全崩掉现象训练集上loss降到0.001测试集上MAPE直接20%以上方向准确率接近50%——跟抛硬币一样。原因这就是过拟合的教科书现场。一是因为股票数据本身就是强噪声序列可学习的信号弱模型把训练集的噪声当成规律背下来了二是模型参数太多、训练轮次太长三是训练集和测试集分布跨度大比如训练集是2015到2020年测试集是2021到2023年中间的涨跌结构完全不同。解决加大Dropout比例从0.2到0.4给LSTM层加recurrent_dropout0.2把模型层数减到一层把epochs上限压到100并配合早停。另外尝试特征简化——只用收盘价和成交量不要一到手就把5个特征全塞进去。5.4 重启电脑后模型预测全部偏移现象跑完训练保存了h5模型文件第二天重新加载模型做预测结果曲线和昨天跑出来的对不上甚至明显偏移。原因你没有在预测前重新对数据做归一化。保存好模型后scaler对象不会跟着模型一起存。新开环境里如果重新fit_transform了全部数据归一化的均值和方差变了模型接收到的输入分布就跟训练时不一样输出自然偏移。解决保存模型的同时用joblib.dump(scaler, scaler.pkl)把scaler存下来预测时joblib.load加载同一个scaler实例来transform新数据。或者干脆做一个预测脚本把“加载模型加载scaler预测反归一化”串成一条pipeline每次预测执行同一个入口不给中间步骤手动操作的机会。这份源码包里如果没写模型保存逻辑你有必要在跑通后自己补上这是把demo变成工具的必经之路。5.5 zip解压出来的代码跑不通报ModuleNotFoundError现象解压这份zip后第一次运行python train.py报错ModuleNotFoundError: No module named tensorflow或No module named baostock。原因源码包的requirements.txt列了依赖包但你的Python环境里没装或者你用了系统的全局Python而不是给这个项目单独建的虚拟环境。另一个常见原因是Python版本太新比如Python 3.12对TensorFlow的某些版本支持不完整pip装了但import失败。解决严格按文档说明里的环境要求来用python -m venv venv创建虚拟环境激活后执行pip install -r requirements.txt。如果文档没写Python版本你用3.10或者3.9最保险这两个版本对TensorFlow和PyTorch的兼容性最成熟。装不上时优先把pip升级到最新pip install --upgrade pip再安装依赖不要用pip install tensorflow带最新版本的指定一个已知稳定的版本比如pip install tensorflow2.10.0。6. 进阶用法与验证技巧拿这份源码跑完后怎么做才不算白做源码包跑通只是起点你得让它“长”在你自己的数据上才算真正消化了这份代码。6.1 用滑窗扫描确定最优lookback30天还是60天不要猜写一个循环去试。把所有候选的lookback值分别训练一次模型记录验证集上的MAE和方向准确率然后选综合表现最好的值。注意每次训练要固定随机种子np.random.seed(42)tf.random.set_seed(42)否则结果波动大你分不清是参数效果还是随机性。import tensorflow as tf np.random.seed(42) tf.random.set_seed(42) for lookback in [10, 20, 30, 60, 90]: X_train, X_val, y_train, y_val build_dataset(data, lookback, 1) # 训练并记录val_mae画出lookback和val_mae的关系曲线滑窗扫描的结果通常会呈现U形曲线——太短不足以捕捉趋势太长引入过多噪声。你找到U形底部那个点就是适合这只股票的时间记忆长度。一只股票的有效时间步长跟它的股性和交易风格有关波动大的票往往需要更短的记忆窗口。6.2 用滚动训练验证模型的稳定性金融数据的非平稳性决定了你今天训练好的模型三个月后可能就失效了。一个实用的验证方法是滚动回测把数据按时间切成多段每段训练一个模型在下一段验证然后再把下一段并入训练集训练新模型。这样你能看到模型性能随时间的变化趋势而不是被一次训练测试的结果安慰到。源码包里通常没写这层但你要真想在量化方向走下去这个验证模式应该是基本功。6.3 改造方向从价格回归到涨跌分类最后给你的改造建议是把Dense层的激活函数从默认线性改成sigmoid损失函数从mse改成binary_crossentropy目标变量从“明天的收盘价”改成“明天是否比今天收盘价高”。这个改动看似简单但它会改变模型的学习目标——从拟合数值变成拟合方向在真实场景中更有操作价值。我自己的项目经验是分类版LSTM在方向准确率上往往比回归版更稳因为分类任务对噪声的容忍度更高不那么容易被极端值带偏。我自己跑LSTM股票预测时最大的教训就是别信那条训练集损失一路走低的曲线也别信测试集上一次漂亮的结果就以为找到了永动机。股市没有永动机LSTM能帮你的只是在一个局部时段识别出可复用的模式。用上述的滑窗扫描、滚动验证和方向准确率去做冷静评估当某个配置连续几次滚动验证都在55%以上的方向准确率时这个模型才值得你继续投入时间优化。希望这份源码在你的手里不只是一次跑通的经历而是一套真正能换着花样做实验的基线系统。希望帮到你。本文还有配套的精品资源点击获取
返回列表