
简介面向正在准备毕业设计的计算机相关专业学生以及想通过项目实战补足经验的开发者这是以机器学习实现股票预测与分析的高分毕业设计项目包含完整源码和说明文档。项目覆盖从数据读取、特征工程到模型训练与结果可视化的完整流程经过严格调试可直接运行也可用于课程设计或期末大作业。压缩包共2000个文件约693.45MB1675张png图像用于展示预测曲线、回测结果与分析图表156个csv文件存放多只股票的历史行情数据112个npz为中间处理后的Numpy数据39个pth为训练好的模型权重另有少量py源码、配置文件和说明文档。整体目录结构清楚便于对照学习和二次开发。目前已有613人学习下载。除完整可运行代码外压缩包还提供训练好的模型、中间数据和多阶段输出能帮助使用者快速理解数据流转与特征构造思路并快速复现整套实验对完善毕业设计或搭建股票预测Demo都很有价值。1. 股票预测不是玄学先想清楚这个题目到底在考什么很多同学第一次跑通“Python实现基于机器学习的股票预测和分析”这个毕业设计时都会发现一个诡异的现象模型预测股票涨跌每次结果不一样。同一个数据集、同一套代码今天跑出准确率 62%明天变成 55%最后自己都开始怀疑人生。这个现象不是 bug而是机器学习随机性的直接体现也是这道毕业设计题目的第一道坎。这道题的核心词是“机器学习”不是“预测股票”。导师给你的任务本质是走通一条完整的机器学习应用流程数据获取、特征工程、模型训练、评估回测、源码整理和文档说明。预测得准不准反而没有那么重要重要的是你能不能把每一个环节讲清楚、把坑填上、让代码可以复现。这篇文章会从数据准备开始一路写到模型选型、回测评估、踩坑清单和源码组织适合所有把这道题当毕业设计或课程设计来做的人。2. 数据与特征工程特征怎么造、数据怎么切才不返工2.1 数据源怎么选akshare 和 tushare 的取舍做股票预测的第一步是拿数据。国内常见的免费数据源有两个tushare pro 和 akshare。tushare pro 功能全、接口稳定但新用户注册后有积分限制积分不够时很多接口调不动尤其是分钟级和财务类数据。akshare 不需要 token也不用积分直接 pip 安装就能拉数据对毕设来说是最省事的。我自己做这个题目一般用 akshare因为它没有任何注册门槛换台电脑重新配环境也能立刻跑通。下面是获取某只股票日线数据的代码import akshare as ak import pandas as pd df ak.stock_zh_a_hist( symbol000001, # 股票代码这里是平安银行 perioddaily, # 日线数据 start_date20200101, end_date20240101, adjustqfq # 前复权消除除权除息带来的跳空缺口 ) df.columns [date, open, close, high, low, volume, amount, amplitude, pct_change, change, turnover] df df[[date, open, close, high, low, volume, amount, turnover]] df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) print(df.head())这段代码里的关键点是adjustqfq。如果不做复权处理股票在分红送股的那一天价格会突然向下跳空机器学习模型会把这个跳空当成真实的下跌信号导致特征失真。前复权是以当前价格倒推历史价格能让序列连续可比。如果你坚持用 tushare注意先要在官网注册拿到 token然后在代码里配置ts.set_token(你的token)再用ts.pro_bar()拉数据。两个库的数据结构略有差异建议确定一个就不要换否则后面特征工程的代码要改两遍。2.2 特征工程不要只拿收盘价糊弄事拿到原始日线数据后下一步是构造特征。只把 open、close、volume 直接丢进模型基本等于裸奔。股票预测能用的特征大致分三类技术指标类、滞后收益类、量价关系类。技术指标常见的有均线 MA5、MA20、RSI、MACD、布林带等滞后收益类特征是过去 N 天的收益率比如过去 3 天、5 天、10 天的累计涨跌幅量价关系类特征是成交量的变化率、换手率等。下面这段代码演示了最基础的特征构造def make_features(df): data df.copy() # 均线5 日均线和 20 日均线 data[ma5] data[close].rolling(window5).mean() data[ma20] data[close].rolling(window20).mean() # 过去 3 天和 5 天的累计收益率 data[ret_3] data[close].pct_change(3) data[ret_5] data[close].pct_change(5) # 成交量变化率当天成交量相对 5 日均量的比值 data[vol_ratio] data[volume] / data[volume].rolling(window5).mean() # 涨跌幅绝对值过滤微涨微跌的噪声 data[label] (data[close].shift(-3) data[close]).astype(int) # 删除前 20 行因为 rolling 窗口未满特征为 NaN data data.dropna().reset_index(dropTrue) return data这里最容易被忽视的是label的构造方式。我用的是未来第 3 天的收盘价和今天的收盘价做比较涨了就是 1跌了就是 0。shift(-3)的意思是取未来第三天的数据一定不要写成shift(3)那是取过去第三天的数据会让标签失去预测意义。另一个常见做法是预测未来 5 日收益率是否大于 0。窗口越长样本的区分度越好但噪声也越大。我自己一般用 3 日窗口因为 5 日窗口会让训练样本之间的重叠度太高造成“伪样本”问题后面评估时准确率虚高。2.3 数据切分时间序列不能用 random_split很多同学习惯写train_test_split(X, y, test_size0.3, random_state42)在股票预测里这是个致命错误。股票数据是时间序列用随机切分会把未来的数据混进训练集模型等于提前看到了答案测试集上的准确率再高也没有说服力。正确做法是按时间顺序切分。比如前 70% 做训练、后 30% 做测试并且测试集的起始时间必须晚于训练集的结束时间。更严格的验证方式是 TimeSeriesSplit 滚动切分这一点后面专门讲。下面是按时间切分的代码train_size int(len(data) * 0.7) train, test data.iloc[:train_size], data.iloc[train_size:] feature_cols [ma5, ma20, ret_3, ret_5, vol_ratio, volume, turnover] X_train train[feature_cols].values y_train train[label].values X_test test[feature_cols].values y_test test[label].values如果你的数据只有两三年的日线大约 500 到 700 个交易日建议不要把测试集切得太后。测试集太短会导致评估结果波动大跑几次准确率差异十几个百分点答辩时不好解释。切 70/30 是一个比较稳的比例。3. 模型选型与训练从随机森林到 LSTM 的取舍3.1 四个候选模型的真实表现股票预测领域最常见的机器学习算法有四个逻辑回归、随机森林、XGBoost 和 LSTM。我按毕设的实操难度和效果排个序模型优点缺点毕设推荐度逻辑回归训练快可解释性强只学线性关系效果一般适合做 baseline随机森林不容易过拟合有特征重要性对时间序列的时序性不敏感首选XGBoost精度高特征工程兼容性好调参多训练慢一点加分项LSTM理论上适合时序数据数据少必过拟合训练玄学不建议作为主模型很多同学看到“股票预测”就直接上 LSTM觉得神经网络才高级。实际项目里一两千条日线数据喂给 LSTM训练集损失降到 0.01测试集准确率只有 50%这类翻车我见得太多了。LSTM 需要大量数据而且要非常小心地做归一化和序列切分对新手来说是黑匣子。毕设里拿 LSTM 做对比实验没问题但主力模型用随机森林和 XGBoost 更稳。3.2 用随机森林跑通第一版模型随机森林是集成学习里的 bagging 算法核心思想是训练多棵决策树最后投票决定结果。它的优点是几乎不用做数据归一化对噪声容忍度高而且能直接输出特征重要性方便你在文档里写“哪个特征对预测影响最大”。下面是最小可运行的训练代码from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, f1_score model RandomForestClassifier( n_estimators200, # 决策树数量 max_depth6, # 限制树的深度防止过拟合 min_samples_leaf5, # 叶子节点最少样本数 random_state42, # 固定随机种子保证结果可复现 n_jobs-1 ) model.fit(X_train, y_train) y_pred model.predict(X_test) print(Accuracy:, accuracy_score(y_test, y_pred)) print(F1:, f1_score(y_test, y_pred))这里三个参数要重点关注。n_estimators是树的数量太小精度不够太大训练变慢且收益递减200 到 500 之间比较合理。max_depth控制树的最大深度深度过深会过拟合一般 4 到 8 就够用。min_samples_leaf是叶子节点最少样本数设大一点能防止模型学到最后几个样本的特殊模式。跑完这个模型顺手输出一下特征重要性这个结果可以直接写进文档importance pd.Series(model.feature_importances_, indexfeature_cols).sort_values(ascendingFalse) print(importance)我见过很多毕设只贴训练代码完全不提特征重要性答辩时导师一问“你觉得哪个特征最重要”就答不上来。特征重要性分析是随机森林自带的功能不需要额外计算展示出来既能体现你对模型的理解又能让文档更充实。3.3 模型预测股票涨跌每次结果不一样随机种子问题开头提到的问题这里终于要正面解决了。同一个模型跑两次结果不一样原因有两类一是随机森林、XGBoost 这类算法内部有随机抽样过程不固定随机种子每次切的样本不完全相同二是某些环境里浮点数计算的顺序有细微差异。解决办法是在代码开头统一设置随机种子import numpy as np import random def set_seed(seed42): random.seed(seed) np.random.seed(seed) set_seed(42) # 如果用了 sklearn模型里也要带 random_state 参数 # 如果用了 PyTorch还要加 torch.manual_seed(seed)注意random_state必须同时设置在数据切分和模型初始化两处。如果你用 train_test_split也要传random_state否则数据切分每次不一样模型结果自然不会一样。另外有些同学发现固定了随机种子结果还是不同那大概率是数据里有重复行或者某些特征列在训练前被重新排序过先去查数据清洗。4. 回测与评估准确率高不代表能赚钱4.1 评估指标别拿准确率当唯一标准股票数据天然存在类别不均衡问题。回顾一下你的标签分布上涨样本占比可能在 55% 左右下跌样本占 45%。如果模型把所有样本都预测为上涨准确率也有 55%看起来不错实际上一文不值。所以在评估阶段我一般看四个指标准确率、精准率、召回率、F1外加一个自己定义的投资策略收益。指标含义关注原因Accuracy预测正确的比例容易虚高必须和随机概率做对比Precision预测上涨中真正上涨的比例避免频繁假信号Recall实际上涨中被预测出的比例避免漏掉大行情F1-score综合精准率和召回率的调和平均最推荐的单一指标策略收益按预测信号买卖的累计收益答辩最关心的落地效果代码输出所有这些指标很简单用sklearn.metrics里的函数即可。重点是解释如果你的准确率是 58%而随机猜测的概率是 50%提升只有 8 个百分点这个提升是否显著建议顺手做一次“预测全部为涨”的 baseline 对比计算它的准确率和 F1让读者看到你的模型确实学到了东西而不是靠运气。4.2 回测代码怎么写排除未来函数干扰回测的目的是模拟真实交易环境今天收盘后根据模型预测明天开盘时执行买卖。很多同学的回测翻车点在于用了当天收盘后的信息做当天决策这在真实世界里根本做不到。下面这段回测代码演示了最基础的信号回测import numpy as np def backtest(df, pred_proba, threshold0.55, initial_cash100000): df df.copy() df[pred] pred_proba # 信号预测上涨概率超过阈值才买入否则空仓 df[signal] (df[pred] threshold).astype(int) # 用第二天的开盘价成交模拟 T1 df[next_open] df[open].shift(-1) df[next_close] df[close].shift(-1) cash initial_cash position 0 # 0 表示空仓1 表示持仓 equity [] for i in range(len(df) - 1): if df.loc[i, signal] 1 and position 0: # 第二天开盘买入全仓 position cash / df.loc[i, next_open] cash 0 elif df.loc[i, signal] 0 and position 0: # 第二天开盘卖出 cash position * df.loc[i, next_open] position 0 # 每天记录当前总资产 current_value cash position * df.loc[i, next_close] equity.append(current_value) return equity这个回测里最关键的设计是“用第二天的开盘价成交”。信号在第 i 天收盘后产生最早只能按第 i1 天的开盘价买入这就避免了未来函数。threshold0.55是买卖阈值意思是预测上涨概率超过 55% 才买入。阈值设高一点会减少交易次数、降低风险但也会错失一些机会。回测结果不要只看最终收益还要算最大回撤。最大回撤是曲线最高点到后续最低点的最大跌幅它反映了这个策略在实盘中可能遇到的最坏情况。一段连续亏损会让你的模型“看起来聪明、实际很惨”用 numpy 就能算equity np.array(equity) peak np.maximum.accumulate(equity) drawdown (peak - equity) / peak max_drawdown drawdown.max() print(f最大回撤: {max_drawdown:.2%})4.3 结果可视化一图胜千言毕业设计文档里图比代码更值钱。最少要画三张图预测概率曲线、累计收益曲线、买卖点标注图。用 matplotlib 就行别为了画图引入 plotly 增加环境复杂度。import matplotlib.pyplot as plt plt.figure(figsize(12, 6)) plt.plot(df[date][:len(equity)], equity, label策略净值) plt.plot(df[date][:len(equity)], 100000 * (df[close][:len(equity)] / df[close].iloc[0]), label买入持有) plt.xlabel(日期) plt.ylabel(资产净值) plt.legend() plt.title(策略收益 vs 买入持有收益) plt.show()如果策略收益曲线跑不赢“无脑买入持有”说明你的模型在实盘意义上没有价值。这点必须在文档里如实写不要只挑好看的图放。导师看重的是你能否客观分析结果不是你能不能造出一条漂亮但不真实的分红线。5. 避坑指南毕业设计最常见的 5 个翻车点5.1 数据泄露用了未来信息还浑然不知现象训练集和测试集上准确率都很高模型看起来无敌但换一只股票、换一个时间段效果直接崩盘。原因特征或标签里混入了未来数据。最常见的是用当天的“收盘价”去预测当天的“涨跌”或者用shift(-1)方向搞反让模型在训练时看到了目标答案。解决构造标签和特征时全部使用shift做滞后处理。特征统一用截止到当天收盘的历史数据标签统一用未来至少 1 天的数据。在特征工程完成后打印出最后 5 行数据人工检查一遍确认没有某一列的值“穿越”到了过去。5.2 类别不均衡模型永远只预测“涨”现象测试集准确率看起来有 58%但预测结果里全是 1几乎没有 0精确率和召回率一个极高一个极低。原因股票数据里上涨天数本来就多于下跌天数模型发现全预测“上涨”就能拿到不错的准确率于是偷懒。解决有两种手段。第一种是给模型加权在随机森林里设置class_weightbalanced。第二种是调整预测阈值不默认用 0.5 作为涨跌分界而是用验证集跑一遍找一个让 F1 最高的阈值。我在 4.2 里的threshold0.55就是这个思路具体数值要通过实验确定不是拍脑袋。5.3 过拟合训练集 99%测试集 50%现象训练集准确率高达 99%测试集准确率只有 50%和随机猜测差不多。原因特征太多、树太深、数据量太少模型把训练集里的噪声当成了规律。解决先减少特征数量保留 5 到 10 个最重要的特征同时限制模型复杂度max_depth不要超过 8min_samples_leaf可以设到 10。做完这两个调整训练集和测试集的差距应当缩小到 10 个百分点以内。差距还在 20 个点以上那说明模型不可用不是调参能解决的。5.4 归一化泄漏验证集信息混进了训练集现象用了 StandardScaler 归一化之后测试集效果非常好但部署到新数据上就崩。原因先对全量数据fit了 scaler再做数据切分。这样测试集的均值和方差参与了变换信息泄漏到了训练过程。解决先切分数据再在训练集上fit用同一个 scaler 去transform训练集和测试集。正确顺序是from sklearn.preprocessing import StandardScaler # 错误做法先 fit 再 split scaler StandardScaler() X_scaled scaler.fit_transform(X) # 测试集均值参与了计算泄漏 # 正确做法先 split 再 fit scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test)随机森林和 XGBoost 其实不受归一化影响不需要这一步。但如果你后续加 LSTM 或逻辑回归做对比实验这个坑一定会踩到。5.5 回测收益惊人实盘一塌糊涂现象回测曲线完美年化收益超过 100%最大回撤不到 5%看起来是无风险套利策略。但回测代码里根本没计入手续费和滑点。原因回测逻辑有缺陷常见的是用当天收盘价买入、当天最高价卖出或者用收盘信号当天成交。解决回测里统一用“次日开盘价”成交并在每次买卖时扣除千分之一的手续费。别小看这笔费用高频交易策略一年交易几百次手续费足以吞掉大部分利润。答辩时把这个细节主动讲出来导师会认为你真的理解交易流程。6. 让代码和文档经得起答辩源码组织与模型验证技巧6.1 源码目录怎么组织才像“高分”作品一个 main.py 文件写 800 行代码能跑但绝对拿不了高分。导师看源码的第一眼就是目录结构。按功能拆分既方便自己调试也方便评委理解stock_predict/ ├── data/ │ └── get_data.py # 数据获取脚本 ├── features/ │ └── build_features.py # 特征工程 ├── models/ │ └── train_model.py # 模型训练与评估 ├── backtest/ │ └── backtest.py # 回测逻辑 ├── utils/ │ └── config.py # 全局参数配置 ├── main.py # 主入口一键运行全流程 └── README.md # 项目说明文档config.py放全局变量比如股票代码、时间范围、模型参数、随机种子避免在多个文件里改同一个参数。主入口main.py只需要调用各个模块的函数让整个流程可见可控。6.2 文档说明怎么写答辩追问全在这文档说明至少要覆盖五个问题数据从哪来、特征怎么造的、模型为什么选它、效果怎么评估验证的、代码怎么运行。“数据从哪来”要写清楚数据源和获取时间比如“使用 akshare 获取 2020-2022 年平安银行日线数据”。“特征怎么造”要列出特征列表并说明每个特征的公式。“模型为什么选它”要对比至少两个模型的实验效果用表格展示准确率和 F1。“效果怎么验证”要展示回测收益曲线和最大回撤。“代码怎么运行”要在 README 里写清楚pip install -r requirements.txt和python main.py两步。6.3 用滚动训练证明模型不依赖特定时间段最后一个进阶技巧是时间序列滚动验证。简单的时间切分只能证明模型在“这一段测试时间”上有效导师如果质疑“换个时间段还行不行”你可以用 TimeSeriesSplit 来回应from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) scores [] for train_idx, test_idx in tscv.split(X): X_tr, X_te X[train_idx], X[test_idx] y_tr, y_te y[train_idx], y[test_idx] m RandomForestClassifier(n_estimators200, max_depth6, random_state42) m.fit(X_tr, y_tr) scores.append(m.score(X_te, y_te)) print(每个时间段的准确率:, [round(s, 3) for s in scores])如果每个时间段的准确率都稳定在 55% 到 60% 之间说明模型预测能力不是偶然。如果第 3 段开始崩到 50%说明市场结构变了或特征失效了这时候就需要在文档里诚实分析原因并考虑加入更长时间维度的特征。我当年做这个题目的最大教训是一直盯着测试集准确率调参最后调出一个“完美”的模型却在另一个时间段上直接翻车。后来我养成习惯每次训练都会用滚动验证跑一遍然后再决定这个模型能不能写进文档。愿你也能少走这个弯路希望帮到你。本文还有配套的精品资源点击获取