简介:这是一套面向计算机相关专业学生与项目实战学习者的机器学习股票预测与分析毕业设计资源,包含完整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(drop=True) # 统一列名,防止不同文件大小写或空格差异 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(window=5).mean() df['MA10'] = df['Close'].rolling(window=10).mean() # 构造波动率特征 df['Volatility'] = df['Return'].rolling(window=5).std() # 去掉因滚动窗口产生的空值行 df = df.dropna().reset_index(drop=True) 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(window=5).mean()算 5 日均线,rolling(window=5).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_col='Return', 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_depth=5, random_state=42) 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_depth=5是防止树太深过拟合,你可以试 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, window=20): xs, ys = [], [] for i in range(len(X) - window): xs.append(X[i:i+window]) ys.append(y[i+window]) 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, dtype=torch.float32) y_train_t = torch.tensor(y_train, dtype=torch.float32).view(-1, 1) X_test_t = torch.tensor(X_test, dtype=torch.float32) y_test_t = torch.tensor(y_test, dtype=torch.float32).view(-1, 1) class LSTMModel(nn.Module): def __init__(self, input_size, hidden_size=64, num_layers=2): super().__init__() self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) self.fc = nn.Linear(hidden_size, 1) def forward(self, x): out, _ = self.lstm(x) # 取最后一个时间步的输出做回归 return self.fc(out[:, -1, :]) model = LSTMModel(input_size=X_train.shape[2]) criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) loader = DataLoader(TensorDataset(X_train_t, y_train_t), batch_size=32, shuffle=True) 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(f'Epoch {epoch+1}, 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))参数说明:window=20表示用 20 天历史,你可以改成 10 或 30,但要注意样本量会随之变化。hidden_size=64、num_layers=2是常见起点,数据量小就减到 32 和 1,否则容易过拟合。lr=0.001是 Adam 的常用学习率,训练不收敛就降到 0.0005。batch_size=32影响训练稳定性,样本少可以改 16。注意 LSTM 输入形状是(样本数, 时间步, 特征数),batch_first=True保证这个顺序。训练完记得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, name='Model'): 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='真实值', alpha=0.7) axes[0].plot(pred, label='LSTM预测', alpha=0.7) axes[0].set_title('测试集预测对比') axes[0].legend() # 误差分布 errors = y_test - pred axes[1].hist(errors, bins=50, edgecolor='black') axes[1].set_title('预测误差分布') axes[1].set_xlabel('误差') plt.tight_layout() plt.savefig('prediction_result.png', dpi=150) plt.show()font.sans-serif设成SimHei是为了中文标签正常显示,Linux 下如果没有这个字体,换成WenQuanYi Micro Hei或直接写英文标签。dpi=150保证导出图清晰,放进论文不糊。误差分布如果接近正态、均值接近 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/2020,pd.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 试。另外检查输入里有没有NaN,dropna之后还要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(seed=42): 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', ascending=False))这段代码把每个标的的 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 曲线怎么变。这些记录在答辩时就是你的「实验日志」,老师问细节你能直接翻出来,比空口说「我调过参」有说服力得多。从那以后我每次拿到这类毕设源码,都强制自己先跑通基线,再逐项改参数并记录,绝不一次性大改然后对着报错发呆。希望这套拆解能帮你少走点弯路,把这份资源真正用起来。
本文还有配套的精品资源,点击获取