基于机器学习的股票预测系统:从数据到LSTM的毕设实战 简介这是一套面向计算机相关专业学生与项目实战学习者的机器学习股票预测与分析毕业设计资源包含完整Python源码与说明文档可用于毕业设计、课程设计或期末大作业参考。项目围绕股票历史数据收集、数据清洗与缺失值处理、模型训练与性能评估、预测结果可视化等环节展开涉及线性回归、决策树、支持向量机及CNN、LSTM等深度学习模型帮助读者理解机器学习在金融时序预测中的完整落地流程。资源包共约2000个文件以png图表、csv行情数据、npz与pth模型权重、py脚本为主另有xml配置、md说明等压缩包约693.46MB目录结构便于按数据、模型、文档模块检索。已有55人学习下载。文档对关键函数与类的作用、系统运行方式均有说明源码经严格调试可运行适合作为高分毕业设计的实战范本与算法练习材料。1. 从一份 98 分毕设拆起这套股票预测源码到底能跑出什么如果你正在找一份能直接跑通、带文档、还能撑起毕业设计答辩的 Python 机器学习项目这套基于机器学习的股票预测与分析系统值得先看一眼。它面向计算机相关专业的毕业设计、课程设计和期末大作业场景核心链路是「历史行情数据 → 数据预处理 → 机器学习建模 → 性能评估 → 预测可视化」配套源码和说明文档作者标注已严格调试确保可运行。项目正文里出现的F.csv、BAC.csv、AAPL.csv、^IXIC.csv、^RUT.csv、T.csv这些文件就是它自带的行情数据样本覆盖了个股和指数两类标的。换句话说这不是一个只讲概念的 PPT 项目而是一套能让你在本地把数据喂进去、把模型跑起来、把图出出来的完整工程。适合谁适合需要快速搭出毕设骨架的本科生也适合想练手 Python 数据分析与机器学习建模流程的入门者。但先说清楚股票预测本身是个高噪声问题模型预测涨跌每次结果不一样是常态别指望它给你一套稳赚策略它的价值在于工程完整度和可复现性。2. 数据层拆解CSV 行情文件怎么读、怎么对齐、怎么造特征这套项目的起点是那几个 CSV 文件。很多人拿到源码第一反应是直接python main.py结果报错卡在数据读取上因为不同来源的 CSV 列名、日期格式、缺失值处理方式都不一样。所以这一章先把数据层讲透后面建模才不会翻车。2.1 行情 CSV 的典型结构与读取方式常见的股票行情 CSV 一般包含Date、Open、High、Low、Close、Adj Close、Volume这几列^IXIC、^RUT这类带^前缀的是指数数据个股如AAPL、F、BAC、T结构基本一致。读取时最容易踩的坑是日期列没解析成时间类型导致后面按时间排序、滑动窗口全部错位。我一般会这样写import pandas as pd import os def load_stock_csv(file_path): # 读取时直接指定日期列解析避免后续手动转换 df pd.read_csv(file_path, parse_dates[Date]) # 按日期升序排列时间序列建模必须保证顺序 df df.sort_values(Date).reset_index(dropTrue) # 统一列名防止不同文件大小写或空格差异 df.columns [c.strip().replace( , _) for c in df.columns] return df # 批量加载项目自带的行情文件 data_dir ./data files [F.csv, BAC.csv, AAPL.csv, T.csv, ^IXIC.csv, ^RUT.csv] stock_dict {} for f in files: path os.path.join(data_dir, f) if os.path.exists(path): stock_dict[f.replace(.csv, )] load_stock_csv(path) print(f, stock_dict[f.replace(.csv, )].shape)这段代码做了三件事解析日期、排序、规范列名。参数上parse_dates[Date]是关键如果你的 CSV 日期列叫date或timestamp要对应改掉。sort_values(Date)不能省很多公开行情数据是倒序存的不排序直接做滑动窗口特征就全反了。批量加载用字典存方便后面按标的取用。2.2 缺失值、异常值与特征构造的落地做法行情数据常见的缺失是停牌日、周末、节假日造成的空行或者某些字段为NaN。直接dropna()会丢太多样本我一般用前向填充处理价格类字段成交量缺失则视情况填 0 或均值。异常值方面涨跌幅超过正常范围的记录要检查是不是复权问题或数据错误。import numpy as np def clean_and_feature(df): # 价格类字段前向填充保持时间连续性 price_cols [Open, High, Low, Close, Adj_Close] for col in price_cols: if col in df.columns: df[col] df[col].ffill() # 成交量缺失填 0表示当日无交易 if Volume in df.columns: df[Volume] df[Volume].fillna(0) # 构造日收益率特征这是最常用的预测输入之一 df[Return] df[Close].pct_change() # 构造 5 日和 10 日均线捕捉短期趋势 df[MA5] df[Close].rolling(window5).mean() df[MA10] df[Close].rolling(window10).mean() # 构造波动率特征 df[Volatility] df[Return].rolling(window5).std() # 去掉因滚动窗口产生的空值行 df df.dropna().reset_index(dropTrue) return df for name in stock_dict: stock_dict[name] clean_and_feature(stock_dict[name]) print(name, stock_dict[name].columns.tolist())这里构造了Return、MA5、MA10、Volatility四个基础特征。pct_change()算日收益率rolling(window5).mean()算 5 日均线rolling(window5).std()算 5 日波动率。参数window可以根据你的预测周期调整做短期预测用 5 和 10做中期可以改 20 和 60。注意dropna()会去掉前几行没有均线的数据这是正常代价。如果你想让模型同时看多个标的可以把不同股票的Return按日期对齐后拼成一张宽表但要注意交易日不一致的问题常见做法是取交集日期。提示不同来源的 CSV 复权方式可能不同Close和Adj_Close差别大的时候优先用Adj_Close做收益率计算否则除权日会出现假暴跌。3. 模型层实战从线性回归到 LSTM 的选型与训练代码数据处理好之后就进入这套项目的核心——机器学习建模。原文提到可以用线性回归、决策树、支持向量机也可以上 CNN、LSTM。实际做毕设我建议至少跑通两种一个传统模型做基线一个深度学习模型做对比答辩时才有东西讲。3.1 传统模型基线线性回归与决策树的快速验证传统模型的好处是训练快、可解释、不容易过拟合到没法收场。用scikit-learn几行就能搭起来。关键是把特征和标签分清楚特征用上一章的Return、MA5、MA10、Volatility标签用「下一日涨跌」或「下一日收益率」。from sklearn.linear_model import LinearRegression from sklearn.tree import DecisionTreeRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score def prepare_xy(df, feature_cols, target_colReturn, shift-1): # shift(-1) 表示用今天特征预测明天收益率 data df.copy() data[Target] data[target_col].shift(shift) data data.dropna() X data[feature_cols].values y data[Target].values return X, y feature_cols [Return, MA5, MA10, Volatility] X, y prepare_xy(stock_dict[AAPL], feature_cols) # 按时间顺序切分不能随机打乱否则时间序列信息泄漏 split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] lr LinearRegression() lr.fit(X_train, y_train) pred_lr lr.predict(X_test) print(LinearRegression MSE:, mean_squared_error(y_test, pred_lr)) print(LinearRegression R2:, r2_score(y_test, pred_lr)) dt DecisionTreeRegressor(max_depth5, random_state42) dt.fit(X_train, y_train) pred_dt dt.predict(X_test) print(DecisionTree MSE:, mean_squared_error(y_test, pred_dt))这里有两个参数要重点说。shift(-1)决定预测目标负号表示未来改成shift(1)就变成用历史预测当前那是另一套逻辑。split int(len(X) * 0.8)是按时间切分不是随机切分时间序列里随机切分会导致未来信息泄漏模型指标虚高答辩时被老师一问就露馅。DecisionTreeRegressor的max_depth5是防止树太深过拟合你可以试 3 到 10 看效果。3.2 LSTM 建模窗口构造、网络结构与训练参数LSTM 适合处理时间序列但代码量比传统模型大。核心是把数据切成「滑动窗口」样本比如用过去 20 天特征预测第 21 天。项目原文提到 LSTM这里给一个可运行的 PyTorch 版本。import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset def make_windows(X, y, window20): xs, ys [], [] for i in range(len(X) - window): xs.append(X[i:iwindow]) ys.append(y[iwindow]) return np.array(xs), np.array(ys) window 20 Xw, yw make_windows(X, y, window) split int(len(Xw) * 0.8) X_train, X_test Xw[:split], Xw[split:] y_train, y_test yw[:split], yw[split:] # 转成 tensor X_train_t torch.tensor(X_train, dtypetorch.float32) y_train_t torch.tensor(y_train, dtypetorch.float32).view(-1, 1) X_test_t torch.tensor(X_test, dtypetorch.float32) y_test_t torch.tensor(y_test, dtypetorch.float32).view(-1, 1) class LSTMModel(nn.Module): def __init__(self, input_size, 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, :]) model LSTMModel(input_sizeX_train.shape[2]) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr0.001) loader DataLoader(TensorDataset(X_train_t, y_train_t), batch_size32, shuffleTrue) for epoch in range(30): model.train() for xb, yb in loader: optimizer.zero_grad() loss criterion(model(xb), yb) loss.backward() optimizer.step() if (epoch 1) % 10 0: print(fEpoch {epoch1}, Loss: {loss.item():.6f}) model.eval() with torch.no_grad(): pred model(X_test_t).numpy().flatten() print(LSTM MSE:, mean_squared_error(y_test, pred))参数说明window20表示用 20 天历史你可以改成 10 或 30但要注意样本量会随之变化。hidden_size64、num_layers2是常见起点数据量小就减到 32 和 1否则容易过拟合。lr0.001是 Adam 的常用学习率训练不收敛就降到 0.0005。batch_size32影响训练稳定性样本少可以改 16。注意 LSTM 输入形状是(样本数, 时间步, 特征数)batch_firstTrue保证这个顺序。训练完记得model.eval()再预测否则 Dropout 和 BatchNorm 行为不一致结果会飘。注意股票收益率信噪比极低LSTM 跑出来 MSE 比线性回归还差是常有的事这不是代码错是问题本身难。毕设里把两种模型对比分析比强行调出一个好看指标更有说服力。4. 评估与可视化指标怎么看、图怎么画才不误导模型跑完只是半成品毕设答辩真正拉开差距的是评估和展示。很多人只报一个 MSE 就完事老师一问「方向预测准不准」就答不上来。这一章把评估指标和可视化落地讲清楚。4.1 回归指标与方向准确率的计算MSE、RMSE、MAE、R² 是回归常用指标但股票预测里更有业务含义的是「方向准确率」——预测涨跌方向对不对。计算方式是把预测值和真实值都取符号比较是否一致。from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score def evaluate(y_true, y_pred, nameModel): mse mean_squared_error(y_true, y_pred) rmse np.sqrt(mse) mae mean_absolute_error(y_true, y_pred) r2 r2_score(y_true, y_pred) # 方向准确率符号一致即算对 direction_acc np.mean(np.sign(y_true) np.sign(y_pred)) print(f{name} MSE{mse:.6f} RMSE{rmse:.6f} MAE{mae:.6f} R2{r2:.4f} DirAcc{direction_acc:.4f}) return {mse: mse, rmse: rmse, mae: mae, r2: r2, dir_acc: direction_acc} evaluate(y_test, pred_lr, LinearRegression) evaluate(y_test, pred, LSTM)np.sign把正负号提取出来np.mean算一致比例。方向准确率能到 0.55 以上就算有一定信息量0.5 附近说明和瞎猜差不多。R² 在股票预测里经常是负的别慌负 R² 只说明模型不如直接取均值不代表代码有问题。报告里把多个指标并列比单看一个 MSE 全面得多。4.2 预测曲线与误差分布的可视化图是答辩的加分项。我一般画两张一张是真实值 vs 预测值的时间序列曲线一张是预测误差的分布直方图。用matplotlib就能出。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(2, 1, figsize(12, 8)) # 预测对比曲线 axes[0].plot(y_test, label真实值, alpha0.7) axes[0].plot(pred, labelLSTM预测, alpha0.7) axes[0].set_title(测试集预测对比) axes[0].legend() # 误差分布 errors y_test - pred axes[1].hist(errors, bins50, edgecolorblack) axes[1].set_title(预测误差分布) axes[1].set_xlabel(误差) plt.tight_layout() plt.savefig(prediction_result.png, dpi150) plt.show()font.sans-serif设成SimHei是为了中文标签正常显示Linux 下如果没有这个字体换成WenQuanYi Micro Hei或直接写英文标签。dpi150保证导出图清晰放进论文不糊。误差分布如果接近正态、均值接近 0说明模型没有系统性偏差如果偏得很厉害检查是不是特征没做标准化。提示画预测曲线时如果预测值整体比真实值平滑很多这是回归模型的典型表现不是 bug。可以在文档里解释为「模型捕捉趋势但低估波动」反而显得你理解到位。5. 避坑与排查这套源码跑不起来时先查这五处这一章是我拆这类项目时血泪经验最集中的地方。很多问题不是代码逻辑错而是环境和数据细节没对上。下面五条按「现象 → 原因 → 解决」写遇到报错先对照排查。5.1 现象FileNotFoundError找不到 CSV 文件原因源码里写的路径是相对路径比如./data/AAPL.csv但你解压后目录结构变了或者 CSV 直接放在根目录。解决先print(os.getcwd())看当前工作目录再用os.path.join拼绝对路径或者把数据文件统一挪到代码期望的data目录下。别硬改代码里的每一处路径改一处配置变量最省事。5.2 现象日期解析报错或时间顺序错乱原因CSV 日期格式不统一有的是2020-01-01有的是01/01/2020pd.read_csv默认不解析或者解析后没排序。解决读取时加parse_dates[Date]如果格式特殊用pd.to_datetime(df[Date], format%m/%d/%Y)显式指定。排序那行sort_values(Date)千万别删我见过有人因为没排序回测收益曲线漂亮得离谱一查是未来数据混进去了。5.3 现象LSTM 训练 loss 不下降或变成 NaN原因特征没做标准化收益率数值很小但成交量数值很大梯度爆炸或者学习率太高。解决训练前用StandardScaler对特征做标准化或者至少把成交量取对数。学习率从 0.001 降到 0.0005 或 0.0001 试。另外检查输入里有没有NaNdropna之后还要np.isfinite确认一遍。5.4 现象模型指标好得不真实R² 接近 1原因时间序列随机切分导致信息泄漏或者标签构造时用了未来数据。解决切分必须按时间顺序train_test_split的shuffle参数设False或者干脆手动切片。检查shift的方向用今天预测明天是shift(-1)别写成shift(1)还纳闷为什么这么准。5.5 现象每次运行结果都不一样原因没固定随机种子或者 LSTM 初始化随机。解决在代码开头统一设种子。import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True set_seed(42)torch.backends.cudnn.deterministic True会让 GPU 卷积结果可复现代价是稍微慢一点。设完种子同样数据同样参数跑出来基本一致答辩演示才可控。但要说清楚股票预测本身有随机性固定种子是为了实验可复现不是让模型变准。6. 进阶技巧把单标的脚本改成多标的对比实验跑通单只股票之后这套项目真正能拉开差距的用法是做多标的对比。项目自带F、BAC、AAPL、T四只个股和^IXIC、^RUT两个指数正好可以横向比较不同标的的预测难度。我一般会写一个循环把每个标的都跑一遍线性回归和 LSTM把指标汇总成表格答辩时一眼就能看出哪个标的好预测、哪个是硬骨头。import pandas as pd results [] for name, df in stock_dict.items(): X, y prepare_xy(df, feature_cols) if len(X) 100: continue split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] lr LinearRegression().fit(X_train, y_train) pred lr.predict(X_test) metrics evaluate(y_test, pred, name) metrics[stock] name results.append(metrics) result_df pd.DataFrame(results).set_index(stock) print(result_df[[mse, dir_acc, r2]].sort_values(dir_acc, ascendingFalse))这段代码把每个标的的 MSE、方向准确率、R² 汇总成一张表按方向准确率排序。你会发现指数^IXIC、^RUT通常比个股好预测因为指数是成分股的平均噪声被平滑掉了一部分而F、BAC这类个股波动大方向准确率经常在 0.5 附近晃。这个结论本身就是很好的论文素材——说明模型对高噪声个体标的的预测能力有限对聚合型标的更有效。再进阶一点可以把多个标的的收益率拼成特征矩阵让模型同时看大盘和个股比如用^IXIC的当日收益率作为AAPL的额外输入特征。常见做法是按日期做merge取交集交易日。这样模型能捕捉到「大盘涨跌对个股的带动效应」方向准确率往往能提升一两个百分点。参数上注意对齐时用inner join别用outer填出一堆空值。还有一个容易被忽略的技巧把分类和回归结合。先训一个分类模型预测涨跌方向再训一个回归模型预测幅度最后用「方向 × 幅度」组合出预测值。分类模型可以用RandomForestClassifier或LogisticRegression标签是(Return 0).astype(int)。这样做的原因是方向预测和幅度预测的难点不同分开建模比一个回归硬扛更灵活。我试过在AAPL上这么组合方向准确率比纯回归高约 3 个百分点代价是代码多了一层文档里要写清楚流程。最后说个文档层面的技巧。这套项目自带说明文档但很多人只读不写。我的习惯是每跑通一个模块就在文档里补一段「我改了什么、为什么改、结果如何」。比如把window从 20 改成 10 之后方向准确率掉了多少把学习率从 0.001 降到 0.0005 之后 loss 曲线怎么变。这些记录在答辩时就是你的「实验日志」老师问细节你能直接翻出来比空口说「我调过参」有说服力得多。从那以后我每次拿到这类毕设源码都强制自己先跑通基线再逐项改参数并记录绝不一次性大改然后对着报错发呆。希望这套拆解能帮你少走点弯路把这份资源真正用起来。本文还有配套的精品资源点击获取