简介这是一套面向计算机相关专业学生与项目实战学习者的机器学习股票预测与分析毕业设计资源以Python实现适合作为课程设计、期末大作业或求职作品集参考。项目围绕股票历史数据展开涵盖数据收集、清洗与缺失值处理、模型训练、性能评估及预测结果可视化等完整流程涉及线性回归、决策树、支持向量机以及CNN、LSTM等深度学习模型帮助读者理解机器学习在金融时序预测中的落地方式。资源包共约2000个文件以png图表、csv行情数据、npz与pth模型权重、py源码及md说明文档为主压缩包约693.46MB目录结构清晰便于按数据、模型与文档模块检索学习。已有55人学习下载。随包附详细文档逐项说明关键函数与类的职责及系统运行方式源码经严格调试可运行能帮助读者快速复现实验、理解算法细节并积累金融数据分析经验。1. 从一份股票预测毕设说起机器学习到底能预测什么很多人第一次接触机器学习股票预测都是被用 Python 预测股票涨跌这个说法吸引进来的。我当年做毕业设计也是这个心态觉得只要把历史数据喂给模型它就能告诉我明天该买还是该卖。真正动手之后才发现这件事的难点根本不在模型本身而在于你预测的到底是什么、用什么数据预测、预测结果怎么验证。这三个问题想不清楚代码写得再漂亮也是自欺欺人。这份毕业设计要解决的核心问题是搭建一套完整的数据获取—特征工程—模型训练—回测评估流水线用 Python 把机器学习在股票分析上的典型用法跑通。它适合两类人一类是计算机、大数据、金融工程方向正在找毕业设计选题的同学需要一份结构完整、能讲清楚原理又拿得出手的项目另一类是想入门量化分析、但被各种AI 炒股宣传绕晕的开发者需要一个不吹牛、能复现的起点。下面我按实际做项目的顺序把每个环节拆开讲包括参数怎么设、哪里容易翻车、结果怎么判断靠不靠谱。2. 数据从哪来、特征怎么造股票预测的地基2.1 用 akshare 或 tushare 拉 A 股日线数据数据源是整个项目的第一步。常见做法是用 akshare免费、接口简单、覆盖 A 股和港股美股。tushare 也行但部分接口需要积分。我一般先用 akshare 把数据落地成本地 CSV避免每次跑模型都重新请求。import akshare as ak import pandas as pd # 拉取贵州茅台 2020-01-01 到 2024-12-31 的日线数据 df ak.stock_zh_a_hist( symbol600519, # 股票代码不带交易所前缀 perioddaily, # 日线可选 weekly / monthly start_date20200101, end_date20241231, adjustqfq # 前复权保证价格连续可比 ) # 统一列名方便后续处理 df df.rename(columns{ 日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume, 涨跌幅: pct_chg }) df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) df.to_csv(600519_daily.csv, indexFalse) print(df.shape, df.tail(3))这段代码的关键参数有三个。adjustqfq是前复权除权除息后价格会跳空不复权的话模型会学到假的暴跌信号。perioddaily决定数据粒度日线适合做中短期预测分钟线数据量大且噪声更高毕设阶段不建议碰。symbol只填数字代码akshare 会自动识别交易所填错前缀反而报错。落地成 CSV 之后后续所有实验都读本地文件这样调参时不会因为网络波动导致数据不一致。这一步看起来简单但我见过太多人每次跑都重新拉数据结果两次实验的数据对不上白白浪费一整天排查模型为什么不稳定。2.2 特征工程把 OHLCV 变成模型能吃的因子原始数据只有开高低收和成交量直接喂给模型效果很差。需要构造有金融含义的特征。常见的有几类收益率类、波动率类、均线类、量价关系类。import numpy as np df[ret_1] df[close].pct_change(1) # 日收益率 df[ret_5] df[close].pct_change(5) # 5日收益率 df[ma5] df[close].rolling(5).mean() # 5日均线 df[ma20] df[close].rolling(20).mean() # 20日均线 df[ma_ratio] df[ma5] / df[ma20] # 均线比衡量短期强弱 df[vol_5] df[ret_1].rolling(5).std() # 5日波动率 df[vol_ratio] df[volume] / df[volume].rolling(5).mean() # 量比 # 标签明天收盘价比今天涨为1否则为0 df[label] (df[close].shift(-1) df[close]).astype(int) # 去掉滚动窗口产生的空值 df df.dropna().reset_index(dropTrue) print(df[[date, ret_1, ma_ratio, vol_ratio, label]].tail())这里有几个必须注意的点。pct_change和rolling都会在开头产生 NaN必须dropna否则模型训练直接报错。标签用shift(-1)构造意思是用今天及之前的信息预测明天这个方向不能反反了就是未来函数回测收益会好得离谱但实盘一文不值。ma_ratio和vol_ratio这类比值特征比原始价格更稳定因为股价绝对水平会随时间漂移但比值不会。特征数量不是越多越好。我一般控制在 10 到 20 个之间太多容易过拟合而且毕设答辩时解释不清楚每个因子的含义反而扣分。如果要做特征筛选可以用随机森林的feature_importances_看哪些因子贡献大把重要性接近零的删掉。2.3 时间序列切分为什么不能随机划分训练集这是股票预测里最容易翻车的地方。很多人习惯用train_test_split随机划分但在时间序列上这是严重错误——随机划分会让模型在训练时看到未来的数据测试集上的准确率虚高实盘完全不能用。正确做法是按时间顺序切分前 80% 做训练后 20% 做测试。如果要更严谨用滚动窗口或扩展窗口做交叉验证。split_idx int(len(df) * 0.8) train df.iloc[:split_idx].copy() test df.iloc[split_idx:].copy() feature_cols [ret_1, ret_5, ma_ratio, vol_5, vol_ratio] X_train, y_train train[feature_cols].values, train[label].values X_test, y_test test[feature_cols].values, test[label].values print(f训练集 {X_train.shape}测试集 {X_test.shape}) print(f训练集标签分布{np.bincount(y_train)}) print(f测试集标签分布{np.bincount(y_test)})切分之后一定要打印两边的标签分布。股票涨跌标签通常接近 50/50但如果某段时间单边行情明显比如测试集里 70% 都是涨那模型只要无脑预测涨就能有 70% 准确率这种结果没有意义。看到分布严重偏斜要么换时间段要么用 AUC 而不是准确率来评估。3. 模型选型与训练逻辑回归、随机森林还是 LSTM3.1 三个基线模型的最小实现毕设里不需要一上来就上深度学习。我一般先跑三个基线逻辑回归、随机森林、XGBoost。逻辑回归可解释性强随机森林抗过拟合XGBoost 在表格数据上通常效果最好。三个都跑一遍对比结果答辩时也有内容讲。from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier from sklearn.metrics import accuracy_score, roc_auc_score models { LogisticRegression: LogisticRegression(max_iter1000, C1.0), RandomForest: RandomForestClassifier( n_estimators200, max_depth5, min_samples_leaf20, random_state42 ), XGBoost: XGBClassifier( n_estimators200, max_depth4, learning_rate0.05, subsample0.8, colsample_bytree0.8, random_state42 ), } for name, model in models.items(): model.fit(X_train, y_train) pred model.predict(X_test) prob model.predict_proba(X_test)[:, 1] acc accuracy_score(y_test, pred) auc roc_auc_score(y_test, prob) print(f{name}: 准确率{acc:.4f}, AUC{auc:.4f})参数说明随机森林的max_depth5和min_samples_leaf20是刻意压低的股票数据噪声大树太深必然过拟合。XGBoost 的learning_rate0.05配合n_estimators200是常见的保守配置subsample和colsample_bytree都设 0.8 增加随机性。逻辑回归的C1.0是正则化强度的倒数调小一点比如 0.1 可以增强正则化。跑完之后你会看到一个很现实的结果准确率大概在 50% 到 55% 之间AUC 在 0.5 到 0.55 之间。这就是股票预测的真实水平。如果有人告诉你他的模型准确率 80% 以上要么用了未来函数要么在骗你。毕设里能稳定做到 53% 以上并且解释清楚为什么就已经是合格的工作。3.2 用 LSTM 做序列建模什么时候值得上如果毕设要求体现深度学习可以加一个 LSTM。但要注意LSTM 在股票日线数据上相比 XGBoost 通常没有明显优势而且训练慢、调参难。它的价值在于能自动学习时序模式不需要手工构造太多滞后特征。import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset class LSTMModel(nn.Module): def __init__(self, input_size, hidden_size32, num_layers1): 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 torch.sigmoid(self.fc(out[:, -1, :])) def make_sequences(X, y, seq_len10): xs, ys [], [] for i in range(len(X) - seq_len): xs.append(X[i:iseq_len]) ys.append(y[iseq_len]) return np.array(xs), np.array(ys) seq_len 10 X_train_seq, y_train_seq make_sequences(X_train, y_train, seq_len) X_test_seq, y_test_seq make_sequences(X_test, y_test, seq_len) model LSTMModel(input_sizeX_train.shape[1]) optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.BCELoss() train_ds TensorDataset( torch.tensor(X_train_seq, dtypetorch.float32), torch.tensor(y_train_seq, dtypetorch.float32).unsqueeze(1) ) loader DataLoader(train_ds, batch_size32, shuffleFalse) # 时序数据不要 shuffle 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():.4f})关键点seq_len10表示用过去 10 天的特征预测第 11 天这个窗口可以调但不要太大日线数据 10 到 20 天比较合理。DataLoader的shuffleFalse很重要时序数据打乱会破坏顺序信息。LSTM 的hidden_size32和num_layers1是轻量配置数据量不大的时候层数多了反而过拟合。训练完之后用同样的accuracy_score和roc_auc_score评估和前面的树模型对比。如果 LSTM 没有明显更好毕设里就重点讲树模型LSTM 作为对比实验提一下即可不用死磕。3.3 模型结果不稳定随机种子和交叉验证怎么设热搜里有个词叫模型预测股票涨跌每次结果不一样这是很多人做毕设时遇到的真实困惑。原因通常有三个随机种子没固定、数据切分方式不对、模型本身方差大。import numpy as np import random import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) set_seed(42)固定种子只能保证同一台机器上结果可复现换台机器或换个库版本仍可能有微小差异。更严谨的做法是做时间序列交叉验证看模型在不同时间段上的表现是否稳定。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) auc_scores [] for train_idx, val_idx in tscv.split(X_train): model RandomForestClassifier( n_estimators200, max_depth5, min_samples_leaf20, random_state42 ) model.fit(X_train[train_idx], y_train[train_idx]) prob model.predict_proba(X_train[val_idx])[:, 1] auc_scores.append(roc_auc_score(y_train[val_idx], prob)) print(f5折时序CV AUC{np.mean(auc_scores):.4f} ± {np.std(auc_scores):.4f})如果标准差超过 0.05说明模型在不同时间段上表现差异很大这种模型实盘风险很高。毕设里把这个分析写进去比单纯报一个准确率数字有说服力得多。4. 避坑与排查股票预测毕设里最容易翻车的五件事4.1 准确率突然到 90%先查未来函数现象模型在测试集上准确率 90% 以上AUC 接近 1.0看起来完美。原因几乎可以肯定是未来函数。常见来源包括用shift(-1)构造特征而不是标签、在归一化时用了全量数据的均值和方差、随机划分了时间序列、把收盘价当成了当天可获取的信息。解决逐列检查特征构造逻辑确保每个特征在预测时刻都是已知的。归一化要用训练集的统计量去变换测试集不能fit_transform全量数据。标签和特征的时间关系画一张表逐个确认。4.2 回测收益曲线一路向上检查交易成本和滑点现象按模型信号做多空回测净值曲线平滑上升年化收益 50% 以上。原因没有扣交易成本。A 股买卖一次印花税加佣金大约千分之一到千分之三如果模型频繁交易成本会吃掉大部分收益。另外没有考虑滑点实盘成交价和信号价会有偏差。解决回测里加入commission0.001和slippage0.001重新跑一遍。如果收益大幅缩水甚至转负说明模型的高收益来自高频交易实盘不可行。毕设里把含成本和未含成本的曲线都画出来对比是很加分的分析。4.3 训练集表现好测试集差过拟合的三种解法现象训练集准确率 70%测试集只有 50%。原因模型太复杂、特征太多、数据量太少。股票数据信噪比极低过拟合几乎是默认状态。解决第一降低模型复杂度树模型减深度神经网络减层数第二加正则化逻辑回归调小 CXGBoost 加reg_alpha和reg_lambda第三减少特征数量用特征重要性筛选。三个方法一起上通常能把训练集和测试集的差距压到 5 个百分点以内。4.4 数据对不齐停牌和除权导致的日期错位现象模型训练时报错或者预测结果和实际走势完全对不上。原因A 股有停牌停牌期间没有数据直接rolling会把停牌前后的价格连在一起算产生错误特征。除权除息如果不复权价格会跳空。解决拉数据时统一用前复权。停牌处理有两种方式一是直接删除停牌日但要注意rolling窗口会因此跨越更长的时间二是保留日期但用前值填充同时加一个is_suspended标志列。我一般用第一种简单且不容易出错。4.5 答辩被问这模型能赚钱吗准备好诚实的回答现象答辩老师问模型实盘能不能盈利答不上来或者吹牛被追问到崩。原因没有想清楚学术项目和实盘交易的差距。解决诚实回答。股票预测模型在学术上能做到 53% 到 55% 的准确率已经不错但实盘还要考虑交易成本、流动性、市场冲击、风控。毕设的价值在于完整走通流程、理解每个环节的原理和局限不在于证明能赚钱。把回测结果、成本分析、模型局限性都写进论文反而显得严谨。5. 把毕设做出区分度三个能加分的进阶技巧5.1 用 SHAP 解释模型让答辩老师看到你懂业务树模型最大的优势是可解释。用 SHAP 库可以把每个特征对预测结果的贡献可视化答辩时展示这张图比只报准确率有说服力得多。import shap model RandomForestClassifier( n_estimators200, max_depth5, min_samples_leaf20, random_state42 ) model.fit(X_train, y_train) explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_test) # 汇总图看哪些特征整体贡献大 shap.summary_plot(shap_values[1], X_test, feature_namesfeature_cols)shap_values[1]取的是涨这一类的贡献值。汇总图里每个点是一个样本横轴是 SHAP 值颜色代表特征值高低。如果ma_ratio的 SHAP 值分布很宽说明均线比对模型判断影响大如果某个特征的点都挤在 0 附近说明这个特征没什么用可以考虑删掉。这张图能让老师一眼看出你不仅会调库还理解每个因子的金融含义。5.2 滚动回测比单次切分更接近实盘单次训练测试切分只能看一个时间段的表现。滚动回测模拟每隔一段时间重新训练模型的真实场景结果更有说服力。def walk_forward_backtest(df, feature_cols, train_window500, test_window60): results [] for start in range(0, len(df) - train_window - test_window, test_window): train df.iloc[start:starttrain_window] test df.iloc[starttrain_window:starttrain_windowtest_window] model RandomForestClassifier( n_estimators200, max_depth5, min_samples_leaf20, random_state42 ) model.fit(train[feature_cols], train[label]) prob model.predict_proba(test[feature_cols])[:, 1] results.append({ start: test[date].iloc[0], end: test[date].iloc[-1], auc: roc_auc_score(test[label], prob), acc: accuracy_score(test[label], (prob 0.5).astype(int)) }) return pd.DataFrame(results) bt walk_forward_backtest(df, feature_cols) print(bt) print(f滚动回测平均AUC{bt[auc].mean():.4f})train_window500表示每次用过去 500 个交易日训练test_window60表示预测未来 60 天。这两个参数可以调窗口太短模型学不够太长则无法适应市场变化。滚动回测的 AUC 均值如果和单次切分接近说明模型稳定如果波动很大说明模型对市场状态敏感需要在论文里讨论。5.3 特征重要性筛选从 20 个因子砍到 8 个特征太多不仅过拟合答辩时也讲不清楚。用随机森林的特征重要性做一轮筛选保留贡献最大的几个。import pandas as pd model RandomForestClassifier( n_estimators200, max_depth5, min_samples_leaf20, random_state42 ) model.fit(X_train, y_train) importance pd.DataFrame({ feature: feature_cols, importance: model.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance) selected importance[importance[importance] 0.05][feature].tolist() print(f筛选后保留特征{selected})阈值 0.05 是我常用的经验值低于这个值的特征对模型贡献很小删掉通常不会明显降低 AUC反而能减少过拟合风险。筛选后用剩下的特征重新训练一遍对比 AUC 变化。如果 AUC 下降不超过 0.01就果断用精简版论文里也更好写。做这个毕设最大的体会是股票预测的瓶颈从来不在模型而在数据质量和评估方法。我见过太多人花一周调 LSTM 参数却不肯花半天检查标签有没有用未来函数。把数据管线做扎实、把回测做严谨、把模型局限性讲清楚比追求一个漂亮的准确率数字重要得多。希望帮到你。本文还有配套的精品资源点击获取
