
简介这是一份基于LSTM的股票价格预测Python实现项目适合高校学生用于课程设计、期末大作业或毕业设计参考。项目完整包含模型训练、预测与结果可视化相关源码以及使用说明文档环境配置后可直接运行无需修改适合快速落地演示。压缩包共89个文件约8.89MB以Python脚本与编译缓存文件为主同时配有数据集表格、说明文档、网页展示页面及少量图片样式资源覆盖数据处理、模型构建和结果展示等环节。目前已有738人学习下载。从整套内容可系统了解LSTM在时间序列预测中的应用流程包括数据预处理、特征构造、模型训练、评估与预测结果可视化并可直接基于现有代码扩展其他股票或金融序列实验对理解深度学习方法与完成课程报告均较有帮助。1. 为什么要自己动手写LSTM股票预测从安装依赖到跑通第一版股价预测这事网上能找到一堆“拿历史K线拟合未来”的教程但真正能把LSTM跑通、还能把预测结果当课程设计或期末大作业交上去的项目并没有那么多。很多初学者卡在第一关装了TensorFlow就报CUDA错误读了CSV却不知道时间步长怎么切好不容易训练完了画出来的预测曲线又比真实价格滞后一大截。这套基于LSTM的股票价格预测Python源码我拆过之后确认它就是为“能跑、能解释、能演示”设计的——代码里带Django壳、训练脚本、说明文档不用自己拼装下载解压后按文档走一遍就能出结果。它适合三类人做课程设计需要高分演示的学生、想用LSTM做时间序列入门练手的开发者、以及需要一份完整参考实现来改造自己项目的从业者。下面我按实际拆包顺序把数据怎么喂、模型怎么搭、参数怎么调、坑在哪一次说清。2. 数据准备与时间步长先把价格序列喂给LSTM不报错的四个关键操作2.1 数据源yfinance拉取与本地CSV双通道这套源码的stock_prediction目录下核心是一个Python脚本课程包里叫stock_prediction code.py也可能按Django app拆成多个模块数据加载部分同时支持在线拉取和本地CSV回放两种方式。在线通道用的是yfinance直接按股票代码和日期区间拉历史行情本地通道则读同级目录下的CSV文件字段至少要有Date、Open、High、Low、Close、Volume。我建议第一次跑先走本地CSV因为在线接口受网络和频率限制翻车后不好判断是代码问题还是数据源问题。import pandas as pd import yfinance as yf # 在线拉取yahoo接口国内网络环境不一定稳失败时换本地CSV df yf.download(AAPL, start2015-01-01, end2023-12-31, progressFalse) # 本地读取课程包里自带示例数据时可优先用避免接口限流 # df pd.read_csv(stock_data.csv, parse_dates[Date], index_colDate) # 只保留收盘价做单变量预测这是LSTM最常见也是最稳的切入点 data df[Close].values.reshape(-1, 1)逻辑说明单变量LSTM预测只用Close列避免把Open、Volume等维度过早塞进来导致模型发散在线和本地两条路径用注释切换适合答辩时快速演示“换数据也能跑”。参数说明start和end决定了训练集的时间范围这里近8年的日线数据约2000根K线对LSTM而言属于“够用但不算多”的规模reshape(-1, 1)是因为LSTM要求输入形状是(样本数, 时间步长, 特征数)特征数这里设为1。有个细节值得注意源码里并没有对停牌日做填充读取到的索引是不连续的。LSTM并不强制要求连续时间索引但如果你后续要做步长滑窗缺失日期会导致窗口内样本的“间隔”不一致影响模型对短期趋势的感知。常见做法是df df.asfreq(D).ffill()补成连续日线但课程设计场景下不补也不影响出图我建议按原样跑通后再考虑这一步。2.2 归一化的手法MinMaxScaler的区间与回放股价序列的量纲是“元”直接喂给LSTM会让激活函数饱和梯度消失得很快。源码里用的是MinMaxScaler把价格压缩到0到1之间。这个选择比标准化StandardScaler更适合股票数据因为股价没有明确的均值意义而归一化后每个时间步的输入范围固定模型更容易收敛。from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) scaled_data scaler.fit_transform(data) # fit_transform用于训练集逻辑说明fit_transform在训练数据上计算最小值、最大值再把整个序列变换到指定区间。参数说明feature_range(0, 1)是LSTM输入最常用的区间如果换用(-1, 1)则需配合tanh输出层否则预测值范围对不上。这里有一个新手常犯的错误对全量数据一次性fit_transform。这样做的确能跑但会造成信息泄漏——测试集的最大值参与了训练集的归一化验证时预测误差会被低估。源码为了让“下载即用”跑得好看用的是全量归一化我对这种做法保留意见但答辩时解释成“统一量纲”也说得过去。如果你想让结果更严谨应当先切训练集、测试集再分别fit和transform。2.3 构造序列样本create_dataset的时间步长到底取多少LSTM不能拿一根K线预测下一根它需要连续N根K线组成一个窗口预测第N1根。源码里的create_dataset函数就是干这个的给定一个序列和time_step滑动生成(X, y)对。import numpy as np def create_dataset(data, time_step60): X, y [], [] for i in range(len(data) - time_step - 1): X.append(data[i:(i time_step), 0]) # 取连续time_step个点的收盘价 y.append(data[i time_step, 0]) # 用第time_step1个点做标签 return np.array(X), np.array(y) time_step 60 # 60个交易日 ≈ 3个月覆盖短期趋势的常用窗口 X, y create_dataset(scaled_data, time_step)逻辑说明循环体里data[i:(itime_step), 0]取的是第i到第itime_step-1个点标签落在itime_step位置这样每个样本刚好是“用前60天预测第61天”。参数说明time_step是LSTM项目里最影响效果的参数之一。取20太短模型只学到超短期波动取120太长样本量锐减且训练变慢。源码默认60是日线级别比较折中的值周线数据可以考虑15分钟线数据则需要100以上。2.4 reshape成LSTM能吃的三维张量LSTM层的输入要求是三维(batch_size, time_steps, input_dim)。上面create_dataset返回的X是二维的(样本数, time_step)源码里会把它reshape成(样本数, time_step, 1)。这一步漏掉的报错信息是ValueError: Input 0 of layer lstm is incompatible with the layer看到这个错就知道是张量维度不对。# 将X从(样本数, time_step)调整为(样本数, time_step, 1) X X.reshape(X.shape[0], X.shape[1], 1) # 按8:2划分训练集和测试集时间序列不能乱序切分 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:]参数说明X.shape[0]是样本总数X.shape[1]是time_step第三维的1代表特征数。这里必须强调不能用train_test_split随机切分时间序列一旦打乱顺序模型就学到了“未来信息”预测曲线会异常平滑答辩时一旦被问到这一点很容易穿帮。按时间顺序前80%训练、后20%验证是这类任务的标准操作。3. 模型构建与训练隐藏层、批大小与学习率怎么定才不玄学3.1 Sequential搭LSTM两层结构为什么比单层稳源码里模型主体是一个Sequential容器塞了两层LSTM加一个Dense输出层。单层LSTM对时间序列的抽象能力有限两层可以让上层捕捉更长期的依赖关系同时参数量增加不多训练成本可控。我拆包时注意到第一层LSTM设了return_sequencesTrue这是两层LSTM衔接的关键——第一层要输出完整的时间步序列给第二层而不是只输出最后一个时间步的结果。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model Sequential() model.add(LSTM(units50, return_sequencesTrue, input_shape(time_step, 1))) model.add(Dropout(0.2)) model.add(LSTM(units50, return_sequencesFalse)) model.add(Dropout(0.2)) model.add(Dense(units1)) model.compile(optimizeradam, lossmean_squared_error)逻辑说明units50是每个LSTM单元里隐藏状态的维度Dropout(0.2)在训练时随机丢弃20%的神经元输出防止对训练集的短期波动过拟合。第二层return_sequencesFalse表示只输出最后一个时间步的隐藏状态它汇总了前面所有时刻的信息再接Dense(1)输出预测价格。参数说明units从32到128都有人用50是速度和效果的平衡点Dropout太高0.5以上会让模型欠拟合太低0.1以下几乎起不到正则作用。optimizeradam是序列预测的默认选择它的自适应学习率免去了手工调整动量参数的麻烦lossmean_squared_error对应回归任务预测值和真实价格的平方差越小越好。3.2 训练epoch、batch_size与验证集切分的配合训练部分是这套源码里最“玄学”的地方。epoch设多了过拟合设少了欠拟合batch_size设大了内存扛不住设小了收敛不稳。源码给的参考值是epochs100、batch_size32这两个值在日线股票数据上搭配2层LSTM、约1600个训练样本属于比较稳妥的组合。history model.fit( X_train, y_train, validation_data(X_test, y_test), epochs100, batch_size32, verbose1 )参数说明validation_data直接传测试集每个epoch结束后用它对当前模型做一次评估这样能实时看到val_loss的变化趋势。verbose1会在终端打印进度条和loss值。我一般会多设置一个callbacks参数加EarlyStopping(patience10)——当val_loss连续10个epoch不下降就提前终止训练既省时间又防过拟合。源码没有加这个但你自己加两行代码并不会破坏原有逻辑from tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) history model.fit( X_train, y_train, validation_data(X_test, y_test), epochs100, batch_size32, verbose1, callbacks[early_stop] )3.3 Django壳的作用可视化与交互入口这个项目里出现了django code.py和stock_prediction目录不是用来做Web服务的重框架而是为了给课程设计加一个可视化的Web壳。拆开看Django部分主要承担两件事一是把训练好的模型预测结果渲染成图表页面二是提供一个表单入口让你输入股票代码和日期后重新拉数据、重新预测。这部分的代码结构大致是views.py里调用训练脚本把预测结果传给模板前端用ECharts或Chart.js画收盘价和预测曲线。我自己跑通后的看法是Django壳的价值不在技术难度而在演示效果——答辩时你不用开着Jupyter Notebook翻代码直接在浏览器里输入股票代码就能看到实时预测曲线老师会觉得这个项目“完整度很高”。如果你是纯技术向的开发者也可以完全跳过Django部分直接跑核心训练脚本预测结果用matplotlib画图就够。源码没有强制你先启动Django才能训练两者是解耦的。# 进入项目根目录后先装依赖以CPU版TensorFlow为例 pip install tensorflow-cpu pandas numpy scikit-learn yfinance django matplotlib # 直接跑训练脚本不经过Django适合快速验证 python stock_prediction_code.py参数说明tensorflow-cpu在无CUDA环境的机器上也能稳定跑避免装GPU版TensorFlow后因驱动版本不匹配报libcudart.so错误。yfinance在国内部分网络环境下会超时如果你拉数据失败把代码里的在线拉取换成本地CSV读取即可。3.4 预测曲线的滞后问题为什么预测值总比真实值晚一步跑通训练后最打击人的画面是预测曲线形状和真实价格几乎一致但整体向右平移了一两天——这就是时间序列预测里的“滞后效应”。原因不复杂LSTM是用过去N天预测未来1天而股价短期内有很强的自相关性模型学到的最省力策略就是“把昨天的价格稍微改一下当今天的预测”所以遇到趋势反转时它总是慢半拍。源码里没有专门处理这个问题但你可以通过调整time_step来缓解把60改成30模型会更敏感于近期变化滞后会减轻但预测噪声变大改成90则相反。处理滞后的另一个有效手段是训练多条预测——不只预测下一天而是预测未来3天或5天然后只取最后一天的结果。这与源码的单步预测逻辑不同需要改标签构造逻辑def create_multi_step_dataset(data, time_step60, pred_steps5): X, y [], [] for i in range(len(data) - time_step - pred_steps): X.append(data[i:(i time_step), 0]) y.append(data[i time_step pred_steps - 1, 0]) # 预测第pred_steps天 return np.array(X), np.array(y)4. 避坑LSTM项目跑不出理想曲线的五个常见问题4.1 装不上TensorFlow或import报错现象pip install tensorflow后from tensorflow.keras.models import Sequential直接报ModuleNotFoundError或ImportError。原因Python版本太新3.11以上或太旧3.7以下TensorFlow对Python版本有严格匹配要求而且默认pip install tensorflow会装2.16以上版本部分旧代码用了keras.optimizers里已移除的API。解决先看python --version推荐3.8到3.10然后pip install tensorflow-cpu2.10.0配numpy1.23.5这套组合兼容性最好。4.2 yfinance拉不到数据卡在下载步骤现象代码运行到yf.download(AAPL, start2015-01-01, end2023-12-31)时长期无响应最后报HTTPError或空DataFrame。原因网络环境访问yahoo接口不稳定或股票代码带后缀格式不对A股用600519.SS港股用0700.HK。解决临时切到本地CSV读取路径或者加一个超时参数yf.download(..., timeout10)如果你在A股环境里跑建议直接用akshare拉A股数据但接口字段名和源码预期不完全一致需要做一次列名映射。4.3 训练loss不降反升或剧烈震荡现象loss在几个epoch后从0.01跳到0.5然后一直没规律。原因大概率是学习率过大且没有梯度裁剪LSTM在长序列上容易梯度爆炸另一个常见原因是归一化范围没设置好输入值超过1导致激活函数进入饱和区。解决把optimizeradam改成optimizerAdam(learning_rate0.001, clipnorm1.0)clipnorm1.0会把梯度的L2范数限制在1以内同时检查scaled_data中是否有大于1的值如果有说明前面归一化或reshape出问题了。4.4 预测结果是一条直线或接近常数现象测试集预测曲线几乎水平没有随时间变化的波动。原因模型把所有权重都推向了偏置本质是LSTM没学到有效特征常见于训练数据太少少于500根K线或时间步长设置比数据量还大。解决检查len(data) - time_step - 1是否为正数如果time_step120而数据只有150根样本量只有29个模型根本学不到东西把time_step降到30或扩充数据到2000根K线以上。还有一种情况是Dense层用了activationsigmoid输出被限制在0到1之间后反归一化区分度不足取消激活函数直接线性输出即可。4.5 反归一化后预测价格远低于真实值现象inverse_transform之后的预测价格只有几块钱而真实价格是几十块。原因MinMaxScaler在训练时是按全量数据计算的但训练集和测试集的价格范围差异较大导致测试集的transform出来的值超出0-1范围模型预测结果再反归一化时被压缩。解决把归一化改成按训练集拟合、对测试集只transformscaler MinMaxScaler(feature_range(0, 1)) scaled_train scaler.fit_transform(train_data.reshape(-1, 1)) scaled_test scaler.transform(test_data.reshape(-1, 1))注意这样切分时要先按时间切出train_data和test_data再做归一化而不是先归一化再切分顺序错了等于白改。5. 让预测结果更可信多周期验证与评价指标的小技巧课程设计答辩时老师最爱问的问题是“你这个预测精度怎么样”“凭什么说这个模型有效”如果只拿一张预测曲线图说“你看长得挺像”大概率会被追问到卡壳。我通常会在源码基础上加两个东西一是用三个指标量化误差二是做一次多周期滚动验证让结论从“图看着像”变成“数值上有依据”。先说指标。源码里只计算了损失函数的MSE没打印更直观的指标。我习惯补上RMSE均方根误差、MAE平均绝对误差和MAPE平均绝对百分比误差三者各有侧重RMSE放大大的误差训练时适合关注它MAE给平均偏离幅度MAPE把误差和价格水平挂钩适合解释给非技术背景的人听。from sklearn.metrics import mean_squared_error, mean_absolute_error # y_test_real和y_pred_real是反归一化后的真实价格与预测价格 rmse np.sqrt(mean_squared_error(y_test_real, y_pred_real)) mae mean_absolute_error(y_test_real, y_pred_real) mape np.mean(np.abs((y_test_real - y_pred_real) / y_test_real)) * 100 print(fRMSE: {rmse:.2f}) print(fMAE: {mae:.2f}) print(fMAPE: {mape:.2f}%)参数说明MAPE在5%以内意味着平均预测偏离真实价格不到5%对股票日线来说已经是不错的结果10%以上则说明预测曲线滞后明显或模型欠拟合。需要提前说的是MAPE对接近0的真实值极其敏感如果测试集里出现低价股比如几毛钱的仙股MAPE会直接爆炸这时改用MAE作为主要指标。再说滚动验证。前面那种“前80%训练、后20%测试”的方式只验证了一遍说服力有限——你选的这个测试时段可能恰好走势平缓预测当然准。我一般会把测试集切成多段比如每段30天分别做回测for start in range(train_size, len(X) - 30, 15): end start 30 X_window, y_window X[start:end], y[start:end] pred_window model.predict(X_window, verbose0) # 与反归一化后的真实价格对比记录该窗口的RMSE window_rmse np.sqrt(mean_squared_error( scaler.inverse_transform(y_window.reshape(-1, 1)), scaler.inverse_transform(pred_window) )) print(f窗口 {start}-{end}: RMSE {window_rmse:.2f})这段代码做的事情是从测试集起点开始每15天滑动取30天为一个验证窗口分别预测并计算RMSE。所有窗口的RMSE均值如果和全量测试集的RMSE接近说明模型在不同时段表现稳定如果某个窗口RMSE特别高去看那段时间的行情——大概率是急涨急跌的极端行情LSTM在剧烈波动下预测失真这是模型本身的边界不是参数调得不对。从那以后我每次拿到LSTM预测类项目都会强制走一遍补指标、滚动验证、检查信息泄漏这三步。光是加一个分窗口评估就能把“看着像”变成“算得出”答辩和汇报时完全不一样。这套基于LSTM的股票预测源码本身跑通不难但真正把结果说清楚、把边界摸明白需要你多花这半小时补一段验证代码。希望帮到你。本文还有配套的精品资源点击获取