
简介一份面向计算机专业学生与机器学习初学者的Python股票预测课程设计/期末大作业源码项目聚焦K线数据获取、特征工程、LSTM与FFT滤波及sklearn单票回测等完整流程解决了选题难、代码不完整、跑不通等典型痛点。压缩包共13个文件以6个Jupyter Notebook为主体配合2个Python脚本与3个CSV数据文件覆盖从数据保存、策略验证到模型回测的各个环节整体仅2.53MB轻量易部署。已有83人学习下载适合需要快速完成高分大作业或进行项目实战练习的人群。内容包含可直接运行的数据抓取脚本、读入K线并入库的Notebook、小市值与盈利指标过滤策略验证、两个LSTM实现以及sklearn机器学习单票回测模板目录结构清晰按步骤分解实验过程可帮助读者理解股票预测的建模思路并快速替换为自己的数据复现。1. 机器学习预测股票涨跌这个“高分大作业”到底在做什么如果期末大作业清单里躺着“基于Python的股票预测系统”你大概率已经搜了不下十次“Python机器学习股票预测源码”。这类项目确实是机器学习课程里最经典的落地选题数据好找、特征好讲、模型随便套一个都能画出预测曲线答辩时还能扯几句量化交易。但真正动手写过的人都知道这题目看着简单实际全是坑。先说一个反直觉的结论同一份代码、同一批数据早上跑和晚上跑预测结果可能完全不一样。更玄的是模型预测股票涨跌每次结果不一样这在机器学习里不是 bug而是随机种子和训练流程没锁死的必然结果。如果你正在写这门课的作业或者准备自己做一个能放进简历的量化方向小项目这篇笔记就把数据、特征、模型、回测和答辩演示完整走一遍。适合已经把 Python 装好、想照着复现并改出自己版本的人不涉及任何“内幕消息”只有特征工程和统计学习能做的事。2. 抓数据与做特征股票预测的特征工程不能偷懒机器学习的课程里大家最重视模型算法但股票预测项目里真正决定分数的是数据预处理和特征工程。同样的随机森林给原始价格和给一套含技术指标的特征效果可以差出一个等级。这个环节也是大作业最容易“看起来工作量不足”的地方所以我会把数据获取、标签构造、特征设计和数据切分拆开讲。2.1 用 yfinance 拉历史行情最小可运行代码常见的做法是用 yfinance 拉 Yahoo Finance 的日线数据免费、无需注册、代码量少适合课程作业。A 股数据可以用 akshare 或 tushare但接口变动频繁评分老师不关心数据源只关心你的代码能不能跑通。因此我最推荐先用 yfinance 跑通全流程确认没有数据陷阱后再考虑换数据源。import yfinance as yf import pandas as pd # 下载贵州茅台不这里用标普500 ETF做演示数据完整、无除权除息干扰 df yf.download(SPY, start2018-01-01, end2023-12-31, auto_adjustTrue) print(df.tail())这段代码会生成一个包含 Open、High、Low、Close、Volume 的 DataFrame。auto_adjustTrue表示拿到的收盘价已经做过复权调整避免分红拆股导致的价格跳变。对课程作业来说直接使用复权价是省心的选择因为模型只关心价格模式不需要处理公司行为。参数说明start和end决定了回测周期一般取 5 年以上保证训练集和测试集都有足够样本。auto_adjust是容易被忽略的参数默认 False 会导致历史价格出现“假跌停”我建议一律设为 True。如果网络不稳定导致返回空数据先检查是否安装了 yfinance再检查本地代理设置。这里不展开网络层面的排查环境干净时多数是库版本太旧。实际上我见过很多大作业在数据这一步就翻车打印出来的df只有几行因为股票代码写错。确认数据拉通后先不要急着上模型下一步是把价格序列变成机器学习能吃的表。2.2 把价格序列转成监督学习样本shift 滞后与涨跌标签机器学习模型不认“时间序列”这个概念它只认特征矩阵 X 和标签 y。股票预测的核心转换思路是用过去 N 天的特征预测未来 M 天的涨跌。这个转换用shift()就能完成但很多人第一次写会直接用未来数据当特征造成“未来函数”泄露。import numpy as np data df[Close].copy().to_frame() # 构造滞后特征昨天、前天、大前天的收盘价 for lag in range(1, 6): data[fclose_lag{lag}] data[Close].shift(lag) # 标签未来5天的收益率大于0记1否则记0 data[future_return] data[Close].shift(-5) / data[Close] - 1 data[label] np.where(data[future_return] 0, 1, 0) # 删除没有滞后特征或没有未来标签的行 data data.dropna() print(data.tail())这里的关键在shift()的符号。shift(5)是把当天数据向后移 5 行所以data[Close].shift(-5)表示未来第 5 天的收盘价。用未来收益做标签是监督学习的标准构造方式但标签本身必须严格滞后于特征否则模型在训练时就能“偷看”到答案。标签构造有讲究想做分类就用 “5天后是否上涨” 作为二分类标签想做回归就直接用future_return做连续标签窗口太长比如 20 天会让样本重叠严重训练集和测试集信息互相污染课程项目建议 35 天。dropna()之后前几行和后几行会被删掉这是正常现象。到这里你已经完成了最核心的监督学习样本构造接下来要做的就是往特征矩阵里加更多变量。2.3 特征工程三件套均线、RSI、MACD 的边界很多“机器学习股票预测算法源码”项目只把过去几天的价格丢进模型效果当然不稳定。技术指标的作用不是预测未来而是把价格走势的形态压缩成几个数值让模型更容易学到规律。最常用、也最容易在答辩时讲清楚的是均线、RSI 和 MACD。# 均线5日、10日、20日均线 data[ma5] data[Close].rolling(5).mean() data[ma10] data[Close].rolling(10).mean() data[ma20] data[Close].rolling(20).mean() # RSI-14相对强弱指标衡量近期涨跌力度 delta data[Close].diff() gain delta.clip(lower0).rolling(14).mean() loss (-delta.clip(upper0)).rolling(14).mean() rs gain / loss data[rsi] 100 - 100 / (1 rs) # MACD差值EMA与信号线EMA的差 ema12 data[Close].ewm(span12, adjustFalse).mean() ema26 data[Close].ewm(span26, adjustFalse).mean() data[macd] ema12 - ema26 data[macd_signal] data[macd].ewm(span9, adjustFalse).mean() # 再删掉滚动窗口产生的NaN行 data data.dropna()参数说明rolling(5).mean()是向前看 5 个交易日不包含当天之后的任何信息所以可以做特征。RSI 的 14 天窗口是常用默认值但 A 股短周期可以改成 7 或 9没有标准答案。答辩时能说出“我测试过窗口长度对结果的影响”会加分。MACD 的 12、26、9 是经典参数来自最初的设计者不是模型调出来的别在答辩时说成“我调的参”。这段代码会引入新的 NaN因为滚动均值在序列开头算不出来。注意特征工程里所有指标都必须只用历史数据计算。如果你用Close.shift(-1)去算某个“当天”指标就是在作弊后面回测分数好看但实盘一定崩。2.4 切分数据时间序列不能随机打乱教科书里教的是train_test_split(X, y, test_size0.2, random_state42)但这里是股票数据随机打乱会把 2020 年的数据混进测试集、2023 年的数据混进训练集回测就会变成“用未来预测过去”。正确做法是按时间顺序切分。# 按时间顺序切分前80%训练后20%测试 split_idx int(len(data) * 0.8) train data.iloc[:split_idx] test data.iloc[split_idx:] feature_cols [close_lag1, close_lag2, close_lag3, close_lag4, close_lag5, ma5, ma10, ma20, rsi, macd, macd_signal] X_train train[feature_cols] y_train train[label] X_test test[feature_cols] y_test test[label]这里有一个很值得注意的细节train_test_split的random_state不是不能用而是要明白为什么不能用。时间序列要求训练集时间早于测试集否则你评估的是“模型还记得未来”而不是“模型能预测未来”。另外滞后期数和滚动窗口会让特征在不同行之间共享信息比如close_lag1和close_lag2在相邻行高度相关。这就是为什么股票预测项目里常见多重共线性而树模型不敏感线性模型则需要留意。我把这个问题放到后面的避坑章节细说你只要记住切分顺序别乱就行。3. 选模型与训模型从线性回归到随机森林的螺旋上升大作业代码里最常见的做法是拿 sklearn 里的模型一个个试哪个准确率高交哪个。这种思路没错但前提是你得知道这些模型在股票预测里的角色和写法。很多“源码项目”只贴一个模型你把它当黑匣子跑完就算完答辩时一问“为什么选这个模型”就卡壳。这个章节从回归到树模型讲清楚选型逻辑和必须调的参数。3.1 预测涨跌还是预测价格两个目标写法不同先分清方向预测明天收盘价是 100 元还是 101 元这是回归问题预测明天涨还是跌这是分类问题。课程作业里“预测股票”这四个字很模糊但我建议你做分类而不是回归。原因很现实回归模型评估指标 MAE、RMSE 容易被价格水平主导而且预测价格是 100.2 元还是 100.5 元对交易决策毫无意义分类模型直接输出涨跌概率可以画收益曲线答辩时更有说服力。from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score model_lr LogisticRegression(max_iter1000) model_lr.fit(X_train, y_train) y_pred model_lr.predict(X_test) print(逻辑回归准确率:, accuracy_score(y_test, y_pred))逻辑回归是这里最好的基线模型因为它既给出概率又足够简单。注意max_iter1000是必须的不然 sklearn 默认迭代次数不够会报“ConvergenceWarning”警告。这里顺便说明一个常见误解准确率超过 50% 不代表模型有用因为股票上涨天数本身可能就超过 50%后面要算基准准确率。3.2 使用线性回归先跑通全流程很多人觉得线性回归是“低级模型”不值得用在股票预测大作业里。实际上线性回归作为基线极其有价值它能验证数据管线是否正确、特征有没有泄露。如果线性回归效果离谱那一定是数据处理有 bug而不是模型不行。from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error # 回归版本用未来5天收益率做连续标签 y_reg_train train[future_return] y_reg_test test[future_return] model_lr_reg LinearRegression() model_lr_reg.fit(X_train, y_reg_train) y_reg_pred model_lr_reg.predict(X_test) print(线性回归 RMSE:, mean_squared_error(y_reg_test, y_reg_pred, squaredFalse))线性回归的好处是模型权重可以直接打印出来告诉你哪些特征正向影响预测、哪些负向影响。如果你想拿线性回归做分类可以把回归输出大于 0 视为涨但效果通常不如逻辑回归我一般直接用逻辑回归做分类基线。3.3 换随机森林与梯度提升非线性特征更吃香价格序列和指标之间的关系不是线性的逻辑回归在前面的特征上表现往往一般这时随机森林和梯度提升树会更有优势。树模型对特征缩放不敏感能自动捕捉交互作用也省了归一化步骤。from sklearn.ensemble import RandomForestClassifier model_rf RandomForestClassifier( n_estimators300, max_depth6, min_samples_leaf10, random_state42, n_jobs-1 ) model_rf.fit(X_train, y_train) y_pred_rf model_rf.predict(X_test) print(随机森林准确率:, accuracy_score(y_test, y_pred_rf)) print(随机森林特征重要性:, pd.Series(model_rf.feature_importances_, indexfeature_cols).sort_values(ascendingFalse))参数说明中比较重要的三个max_depth6限制每棵树深度防止在股票这种噪声很大的数据上过拟合。默认不限制深度树会一直长到纯叶节点训练集准确率 100%测试集一塌糊涂。min_samples_leaf10让叶节点至少保留 10 个样本对平滑预测很有帮助。random_state42是复现结果的关键不设置的话每次运行准确率都不一样这正好呼应当初的热搜词“模型预测股票涨跌 每次结果不一样”。feature_importances_是大作业报告里非常好用的素材因为你可以直接看出 RSI 和 MACD 哪个对模型贡献更大。把这段输出放进实验截图比一百句文字描述更有说服力。3.4 模型预测股票涨跌每次结果不一样随机种子与稳定复现这是机器学习课程里最容易踩的坑也是“模型预测股票涨跌每次结果不一样”这个热搜词背后的技术本质。随机森林、XGBoost、神经网络在训练时都有随机性比如随机森林的列采样、神经网络的参数初始化都会让同一份代码产生不同结果。解决办法是锁定所有随机源。import os import random import numpy as np def set_seed(seed42): os.environ[PYTHONHASHSEED] str(seed) random.seed(seed) np.random.seed(seed) set_seed(42)但要注意只锁 numpy 和 random 是不够的。sklearn 中大多数模型有自己的random_state参数必须在创建模型时显式传入就像前面随机森林代码里的写法。如果你用了 XGBoost还要额外设置xgb_params[random_state]和n_jobs。这一步做完重复运行得到的准确率才会一致。另一个影响结果稳定的因素是数据划分。即便你在同一个 DataFrame 上跑相同的划分如果索引没有重置iloc切分也可能因为索引错位导致训练集和测试集重叠。习惯上做完特征工程就执行data data.reset_index(dropTrue)避免这类隐藏问题。4. 评估与回测用对指标才能拿高分很多大作业的评分标准里有一项“预测效果展示”于是同学直接把accuracy_score打印出来就收工。但其实股票预测的评估体系远比准确率复杂最常见的问题是准确率很高但收益为负。这个章节讲透评估指标、回测写法和基准对比这也是一个高分大作业区别于普通源码项目的分水岭。4.1 不要用 R² 评价涨跌预测上一章如果用的是分类模型你可能直接用准确率。但准确率有两个天然缺陷第一它把“涨 0.1%”和“涨 10%”看成一样的正确第二股票数据里上涨和下跌样本数量往往不平衡。正确做法是同时打印准确率、精确率、召回率和 F1再加一个混淆矩阵。from sklearn.metrics import classification_report, confusion_matrix print(classification_report(y_test, y_pred_rf, target_names[跌, 涨])) print(混淆矩阵:\n, confusion_matrix(y_test, y_pred_rf))这份报告的价值在于你可以看出来模型是不是只会“猜涨”。如果预测结果里绝大多数都是“涨”而实际测试集下跌样本不少那精确率会暴露问题。课程作业答辩时老师必问“你怎么评价模型好坏”你能说出“我主要看 F1 而不是准确率因为样本不平衡”这印象分就上去了。如果做了回归版本不要用 R² 当主要指标因为 R² 对股票这种信噪比极低的数据非常不友好可能只有 0.02会让老师觉得模型没用。改用 RMSE 或 MAE并和“用昨天收盘价预测明天”的基线比较才能说明模型学到了东西。4.2 回测框架按时间滑动预测别一次性预测全部一次性把测试集全部丢进模型预测也只是模型评估不是回测。真正的量化策略回测要模拟“今天决策、明天执行”的过程。常见做法是滚动窗口每 20 个交易日重新训练一次模型然后用最新模型预测未来 5 天的涨跌标签。def rolling_predict(data, feature_cols, start_idx, step20, horizon5): preds [] for i in range(start_idx, len(data) - horizon, step): train data.iloc[i - 252:i] # 用过去一年训练 test data.iloc[i:i step] model RandomForestClassifier( n_estimators200, max_depth6, min_samples_leaf10, random_state42 ) model.fit(train[feature_cols], train[label]) # 预测未来5天取多数标签作为这一期的方向 pred model.predict(test[feature_cols]) preds.append((test.index[0], pred.mean() 0.5)) return preds滚动窗口的参数说明train data.iloc[i - 252:i]用过去约 250 个交易日一年做训练数据避免使用太老的数据适应市场风格变化。step20表示每隔 20 个交易日重新训练一次降低训练开销也避免模型过于频繁更新。pred.mean() 0.5是把测试集 20 条样本的平均预测当成本期信号这个平滑操作能减少噪声。滚动回测比一次性切分慢很多但这是大作业里最能展示工程能力的地方。它证明你理解“模型必须滚动更新”这个核心逻辑而不是把静态模型硬套在动态市场里。4.3 和基准比跑赢“买入持有”才算有效股票预测模型评估里最常见的“效果不错”其实是错觉。假设测试期的股票整体上涨哪怕模型什么都不预测、直接买入持有最终收益也可能为正。因此必须把模型策略的收益曲线和“买入持有”基准画在一起。# 假设每次预测为“涨”时买入持有5天预测“跌”时空仓 test data.iloc[split_idx:].copy() test[pred] model_rf.predict(X_test) # 策略收益预测涨的交易日持有未来5天收益预测跌则收益为0 test[strategy_return] test[pred] * test[future_return] test[market_return] test[future_return] # 买入持有基准 cum_strategy (1 test[strategy_return]).cumprod() cum_market (1 test[market_return]).cumprod() print(策略累计收益:, cum_strategy.iloc[-1] - 1) print(买入持有累计收益:, cum_market.iloc[-1] - 1)这段代码有一个简化假设预测为涨就全仓买入、预测为跌就空仓没有考虑交易手续费和滑点。课程作业这样简化是合理的但你要在报告里写清楚假设条件这比假装“实盘可用”更专业。最后要补充一个常被忽略的点无论策略累计收益多高都要看最大回撤。老师不一定会问但如果你在论文里加了“最大回撤从 30% 降到 15%”这句话项目档次立刻就不一样了。5. 股票预测大作业避坑翻车的五个典型原因在这个章节里写的每一条都是我见过真实发生在课程作业里、也在自己项目里踩过的坑。它们不全是模型问题更多是数据和流程问题。每一条按“现象 → 原因 → 解决”的顺序写清楚。5.1 未来函数第二天涨幅被前一天收盘价泄露现象测试集准确率高得离谱超过 90%甚至接近 100%。原因构造特征时错误地把未来信息当成了输入。最常见写法是用Close.shift(-1)生成特征这样模型在预测当天时已经“偷看”了明天的数据。解决检查每一个特征列确保所有特征只依赖当前时刻及之前的信息。一个快速自查方法是打印特征矩阵的最后几行看close_lag1是否等于前一行的Close。5.2 幸存者偏差用现在的股票池回测过去现象回测曲线一路向上收益翻倍但实盘或换一只股票就崩。原因你用的是“今天还在交易的股票”做历史回测那些中途退市、暴跌的股票从未进入样本回测天然高估收益。解决课程作业里如果只预测单只股票这个问题不严重但如果做多股票池必须明确说“样本存在幸存者偏差”。最稳妥的办法是在报告里限定“本研究仅对当前正常交易的标的进行预测”不要吹嘘自己找到了印钞机。5.3 涨跌样本不平衡准确率虚高的假象现象模型预测全部为“涨”准确率仍有 55%。原因测试期内上涨天数本身就占 55%模型学到的只是“多数类先验”。解决打印分类报告看“跌”这个类别的召回率。如果几乎为 0说明模型根本没学会识别下跌。处理方式有几种一是改用class_weightbalanced让少数类获得更高权重二是只用 F1 作为主要指标三是构造三分类标签“涨/跌/平”过滤掉涨幅绝对值太小的样本减少噪声。# 一个更稳的三分类标签构造 data[return_5d] data[Close].shift(-5) / data[Close] - 1 data[label3] pd.cut( data[return_5d], bins[-np.inf, -0.02, 0.02, np.inf], labels[跌, 平, 涨] ).astype(int) # 注意代码可正常执行但pd.cut的category需要转为数值代码里的label3比二分类更贴近真实交易场景因为“平”的样本被单独分出不会强迫模型在噪声中做判断。但问题也随之而来三分类的类别更加不平衡“涨”和“跌”都变成少数类必须小心评估。5.4 归一化与反归一化价格不敢直接丢进模型现象同一套代码预测结果在股票 A 上很好换到股票 B 上完全失效。原因不同股票价格区间差异大比如茅台接近 2000 元银行股只有几元。如果不对特征做标准化模型会偏向高数值特征。解决对线性模型和神经网络做标准化树模型可以不标准化。标准化要“先拟合训练集再转换测试集”严格禁止用全数据集统计量。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 注意fit只能用训练集测试集只做transform model_lr.fit(X_train_scaled, y_train) print(标准化后的逻辑回归准确率:, accuracy_score(y_test, model_lr.predict(X_test_scaled)))注意这里我用X_test_scaled而不是重新 fit。如果对测试集单独fit_transform测试集数据的均值和方差会泄露到模型评估中效果会虚高。5.5 结果不稳定玄学还是可复现问题现象同一脚本重复运行多次准确率和收益曲线不一样。这是热搜词“模型预测股票涨跌 每次结果不一样”的直接来源也是初次做机器学习的人最容易困惑的点。原因模型算法里存在随机采样或随机初始化。解决全局设置随机种子并在模型参数里逐个传入random_state。如果问题依旧检查是不是用了多个进程。变成“完全可复现”之后我还会多做一步固定数据顺序。pandas 的groupby或drop_duplicates有时会改变行顺序导致同一个random_state下结果不同。我一般会写data data.sort_index().reset_index(dropTrue)保证进入模型前每个样本的顺序稳定。这一步看起来多余却是消除“玄学”结果的最可靠做法。6. 让项目从“能跑”到“高分”滚动预测与模拟交易验证前面的章节解决的是“怎么跑通”和“怎么评估”最后一章专注于提升完成度。很多大作业代码能跑但缺乏结论而高分项目的共同特点是有实验记录、有对比、有用第一人称写的结论。这里给三个可以立刻用上的技巧。6.1 滚动预测脚本模板如果你不想每次跑代码都改参数可以把它封装成函数传入股票代码、训练窗口、预测周期就输出预测结果。一个可复用的脚本模板会让答辩老师直观感受到你的工程能力而不是“背了一段抄来的代码”。def predict_stock(ticker, start, end, horizon5, window252): df yf.download(ticker, startstart, endend, auto_adjustTrue) data build_features(df, horizon) # 复用之前的特征工程函数 data data.dropna().reset_index(dropTrue) split_idx int(len(data) * 0.8) train, test data.iloc[:split_idx], data.iloc[split_idx:] model RandomForestClassifier( n_estimators200, max_depth6, min_samples_leaf10, random_state42 ) model.fit(train[feature_cols], train[label]) last_features test[feature_cols].iloc[[-1]] prob model.predict_proba(last_features)[0][1] print(f{ticker} 最新预测上涨概率: {prob:.2f}) return prob这个函数的好处是你可以对多只股票批量预测生成一张“各股票上涨概率排名表”。这就是从“单一样本预测”升级为“可扩展的量化分析框架”。6.2 模拟交易与收益曲线想要验证模型有没有实战价值只看准确率是不够的。模拟交易的核心是设定一个虚拟初始资金按照模型信号交易记录每一笔买卖后的资金曲线。具体步骤是初始资金 100 万预测为涨时买入持有 5 天后卖出扣掉单边千分之一手续费再计算收益。initial_capital 1000000 capital initial_capital positions [] for i in range(0, len(test) - horizon, horizon): current_pred test[pred].iloc[i] # 模拟第i天的预测信号 forward_return test[future_return].iloc[i] if current_pred 1: trade_return forward_return - 0.001 # 扣手续费 capital * (1 trade_return) positions.append(capital) else: capital * 1 # 空仓资金不变 positions.append(capital) total_return capital / initial_capital - 1 print(模拟交易总收益率:, total_return)这段代码简化了持仓周期和交易成本但足够说明问题。如果你想在报告中画出资金曲线把positions转成 DataFrame用matplotlib画两条线一条是策略资金一条是买入持有资金。看到策略线在熊市阶段能平滑回撤老师的印象就会很好。我在做自己的项目时会把“最大回撤”也打印出来——即使策略收益不如买入持有只要回撤明显更小也能讲出“风险控制更好”的故事。这比单纯追求收益更接近真实量化研究也是一个更成熟的项目调性。6.3 文档与实验记录高分大作业的最后一公里代码写得再好文档写不出来也拿不到高分。我建议你在项目根目录建一个README.md内容只需要四段项目要解决什么问题、数据来源和特征列表、模型对比结果、结论和不足。实验记录里必须包含一个表格模型名称、准确率、F1、策略收益、最大回撤。这张表是整篇报告的骨架。另一个容易被忽略的习惯是每次改动参数都保存一份带时间戳的日志。比如results_1025_rf_depth6.csv避免浪费大量时间却发现“之前的分数是哪次跑出来的”。这是我做量化实验最后悔没有早点做的一件事——没有实验记录就等于没做过实验。希望这篇笔记能帮你少走这些弯路把机器学习股票预测这个经典大作业做出真正能讲的故事。本文还有配套的精品资源点击获取