
简介基于机器学习实现股票价格预测的完整项目源码与数据集专为毕业设计、机器学习课程大作业及期末项目打造也适合希望入门LSTM时序预测的初学者对照学习。压缩包为zip格式共11个文件整体约154KB主要包括5个Excel格式的训练集、测试集及预测结果数据文件3个Python核心脚本含LSTM模型构建、可视化展示、基金净值数据获取另有1个README说明文档和1张预测结果趋势图目录结构清晰便于按模块阅读和二次开发。目前已有707人学习下载。项目代码含有详细注释从数据加载、特征处理、模型训练到预测评估均有完整实现简单部署后即可运行适合快速上手。代码还包含数据获取脚本可自行扩展其他股票或基金数据进一步验证模型效果整个项目脉络完整是课程设计、毕业设计拿高分的好参考。1. 股票价格预测大作业的“高分”关键先定义预测目标再谈模型同样是“Python基于机器学习实现的股票价格预测”这个题目有人拿到 95 分有人却连答辩都过不了差距往往不在模型先进性上而在数据切分和标签定义上。不少同学直接下载一份日线 CSV把开盘价、收盘价塞进随机森林就开训验证集准确率一出来有 98%结果老师问一句“你的测试集里有没有未来数据”就直接卡壳。做股票预测大作业第一件事不是装库、不是挑模型而是想清楚你预测的到底是“未来几天的收盘价数值”还是“未来一段时间股价是涨还是跌”。这两种定义对应回归和分类两套完全不同的评估方式也决定了你的源码里该出现哪些指标。这篇内容面向正在完成机器学习课程大作业、需要能跑通还要能讲明白的读者从数据集构造、特征工程、模型选型到答辩呈现走一遍在本地用 Python 就能复现的完整流程。2. 股票数据集构造与特征工程从原始行情到可训练样本2.1 数据源选择与最小可用数据集股票预测项目的第一步是拿到干净、可解释的行情数据。常见做法是用 akshare 或 tushare 这类 Python 库直接拉取也可以下载 CSV 文件作为本地数据集。对课程大作业来说建议优先选一只流动性好的股票或指数时间范围取最近 5 到 10 年日线数据字段至少包含 date、open、high、low、close、volume 这六列。你不需要一上来就搞几百只股票的截面数据单标的的时间序列足够把整个流程讲透。# 使用 akshare 拉取数据需先通过 pip install akshare 安装 import akshare as ak import pandas as pd # 拉取平安银行000001近 8 年的日线前复权数据 df ak.stock_zh_a_hist( symbol000001, perioddaily, start_date20160101, end_date20231231, adjustqfq ) # 只保留建模需要的列并统一成英文名 df df.rename(columns{ 日期: date, 开盘: open, 最高: high, 最低: low, 收盘: close, 成交量: volume })[[date, open, high, low, close, volume]] df[date] pd.to_datetime(df[date]) df df.set_index(date).sort_index() print(df.head()) print(df.shape)这段代码的关键在于adjustqfq。前复权会修正分红送股造成的价格跳空如果不复权遇到除权除息日模型会以为股价暴跌产生大量错误样本。start_date和end_date决定样本量5 到 10 年的日线大约是 1200 到 2400 根 K 线这个量级足够训练一个特征数不多的机器学习模型。2.2 技术指标特征把量价关系转成模型能懂的数值模型本身不认“K 线形态”它只认数值。所以要把原始行情转成常用的技术指标特征这一步直接决定模型上限。我一般会构造均线、波动率、RSI、MACD 和成交量变化率这几组特征。它们计算成本低、解释性强答辩时也容易说清楚“为什么选这些特征”。# 特征工程基于原始行情计算技术指标 import numpy as np def make_features(df: pd.DataFrame) - pd.DataFrame: data df.copy() # 5 日、10 日、20 日收盘价均线 for win in [5, 10, 20]: data[fma_{win}] data[close].rolling(windowwin).mean() # 收益率当日收盘相对前一日收盘的变化率 data[ret_1] data[close].pct_change() # 滚动波动率最近 20 日收益率的标准差 data[volatility_20] data[ret_1].rolling(window20).std() # RSI(14)相对强弱指标衡量近期涨跌力度 delta data[close].diff() gain delta.clip(lower0).rolling(window14).mean() loss (-delta.clip(upper0)).rolling(window14).mean() rs gain / (loss 1e-9) # loss 可能为 0加一个极小量避免除零 data[rsi_14] 100 - (100 / (1 rs)) # 成交量变化率 data[volume_ratio] data[volume] / data[volume].rolling(window5).mean() return data feature_df make_features(df) feature_df feature_df.dropna() print(feature_df.columns.tolist())注意两个细节。第一所有窗口类特征都只能使用历史数据pct_change()默认就是用前一行计算不会偷看未来。第二dropna()会删掉前 20 行因为滚动窗口需要积累足够数据这不属于数据泄露属于合理截断。RSI 计算里给loss 1e-9是为了防止连续上涨时 loss 为 0 导致除零这种数值稳定性处理在答辩时是可以加分的细节。2.3 标签构造回归预测价格还是分类预测涨跌标签设计是整个大作业最需要讲清楚的地方。如果预测目标是“未来第 N 天的收盘价”这是一个回归任务评估用 MAE、RMSE如果预测目标是“未来第 N 天相对今天是涨还是跌”这是一个二分类任务评估用准确率、F1。我建议做分类问题原因很实际股价本身的随机游走特性会让回归模型的误差非常大而“涨/跌”这种粗粒度预测更符合机器学习模型的真实能力边界。# 构造分类标签未来 5 个交易日是否上涨 def make_label(df: pd.DataFrame, horizon: int 5) - pd.DataFrame: data df.copy() # 未来第 horizon 天的收盘价 data[future_close] data[close].shift(-horizon) # 标签1 表示上涨0 表示下跌或持平 data[label] (data[future_close] data[close]).astype(int) # 删除最后无法构造未来标签的样本 data data.dropna(subset[future_close]) return data labeled_df make_label(feature_df, horizon5) print(labeled_df[label].value_counts())shift(-horizon)是往上取未来数据dropna会删掉最后 5 行没有未来价格的样本这属于正常处理。分类问题里正负样本的比例值得关注如果 label 的 0 和 1 接近 5:5说明标签没有严重的样本不平衡如果某类超过 65%就要考虑调整 horizon 或改用三分类涨、跌、平。三分类的阈值一般是 0.5% 到 1% 的涨跌幅低于阈值视为“平”这样模型不用去预测微小的噪声波动。2.4 数据泄露红线训练集和测试集禁止混用归一化参数股票预测大作业最隐蔽的坑是归一化时把全量数据的均值和标准差用在训练集上。正确的做法是只在训练集上拟合 scaler然后用同一套参数转换验证集和测试集。这一点虽然在很多教程里被反复强调但实际操作中仍有大量学生因为图省事在 DataFrame 上直接(df - df.mean()) / df.std()而丢分。from sklearn.preprocessing import StandardScaler # 按时间顺序切分前 80% 训练后 20% 测试 split_idx int(len(labeled_df) * 0.8) train_df labeled_df.iloc[:split_idx] test_df labeled_df.iloc[split_idx:] feature_cols [c for c in train_df.columns if c not in (label, future_close)] scaler StandardScaler() # 只用训练数据拟合 scaler train_X scaler.fit_transform(train_df[feature_cols]) test_X scaler.transform(test_df[feature_cols]) train_y train_df[label].values test_y test_df[label].values注意股票数据只能按时间顺序切分不能随机打乱。随机打乱等于让模型从未来样本里学习当天规律验证集准确率会虚高到不可信。这是股票预测与普通分类任务最大的差异也是答辩时老师最可能追问的点。3. 股票预测模型选型与源码实现线性回归、XGBoost 与 LSTM 的取舍3.1 各模型定位与选型对比课程大作业不需要追求“预测最准”而是要展示你理解不同模型的适用边界。基于表格型特征做股票涨跌预测我建议至少跑一个线性模型做 baseline再跑一个树模型最后视时间决定要不要上 LSTM。三者对数据和算力的要求差异明显选型时可以参考下面的对比。模型任务类型训练速度可解释性主要优势主要风险线性回归 / Logistic回归 / 分类极快强系数直接反映特征影响难以捕捉非线性关系随机森林 / XGBoost分类 / 回归快中特征重要性可展示对时序依赖建模能力弱LSTM回归 / 分类较慢弱能捕捉长期依赖样本少时过拟合明显选型逻辑要跟着数据规模和任务复杂度走。比如你只有 1000 个样本LSTM 很容易过拟合反过来你做了 60 分钟级别的分钟数据样本量到 5 万以上LSTM 的优势才能显现。以下三组代码可以依次跑通并对比结果这样你的源码里就有了完整的对照实验这是“高分大作业”的基本配置。3.2 基线模型Logistic 回归线性模型的价值在于给后续模型一个参照物。如果 XGBoost 比 Logistic 准确率高不到 2 个百分点那说明特征工程没有给模型提供足够的信息问题出在特征而不是模型。from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, f1_score # 逻辑回归做二分类基线最大迭代次数设大一些防止不收敛 lr LogisticRegression(max_iter2000, C1.0) lr.fit(train_X, train_y) train_pred lr.predict(train_X) test_pred lr.predict(test_X) print(Logistic 训练集准确率:, round(accuracy_score(train_y, train_pred), 4)) print(Logistic 测试集准确率:, round(accuracy_score(test_y, test_pred), 4)) print(Logistic 测试集 F1:, round(f1_score(test_y, test_pred), 4))Logistic 回归在特征标准化之后表现更稳定C1.0是正则化强度的默认值数值越小正则化越强。如果训练集准确率明显高于测试集说明模型过拟合可以下调 C 值如果两者都低于 55%说明特征和标签之间确实没有强线性关系这本身也是一个有价值的实验结论。3.3 进阶模型XGBoost 分类器XGBoost 是这类表格特征分类任务里性价比最高的模型。它自带特征重要性输出答辩时可以直接展示“哪个特征对预测贡献最大”的柱状图。需要注意的是XGBoost 对特征尺度不敏感所以用原始特征或标准化特征结果差异不大但建议保持和基线模型一致的数据输入这样对比实验才公平。from xgboost import XGBClassifier # n_estimators 是树的数量max_depth 控制树的复杂度 xgb XGBClassifier( n_estimators200, max_depth4, learning_rate0.05, subsample0.8, colsample_bytree0.8, random_state42, eval_metriclogloss ) xgb.fit(train_X, train_y) xgb_train_pred xgb.predict(train_X) xgb_test_pred xgb.predict(test_X) print(XGBoost 训练集准确率:, round(accuracy_score(train_y, xgb_train_pred), 4)) print(XGBoost 测试集准确率:, round(accuracy_score(test_y, xgb_test_pred), 4)) # 输出特征重要性用于答辩展示 importance sorted( zip(feature_cols, xgb.feature_importances_), keylambda x: x[1], reverseTrue ) print(Top 5 特征重要性:) for feat, imp in importance[:5]: print(f {feat}: {imp:.4f})subsample0.8和colsample_bytree0.8是让每棵树随机使用 80% 样本和 80% 特征能有效抑制过拟合。learning_rate0.05配合 200 棵树相当于用更小的步长学更多轮比大学习率少棵树更稳。如果训练集准确率接近 100% 而测试集只有 50% 多优先调大subsample的随机性而不是减小max_depth。3.4 序列模型用 PyTorch 搭一个轻量 LSTMLSTM 处理的是原始序列而非手工特征所以使用 LSTM 时通常直接输入过去 N 天的收盘价、成交量让模型自己学习特征表示。这一节在大作业里可以写成“扩展实验”属于加分项但不强求如果时间不够可以只放代码和结论不放入最终对比表。import torch import torch.nn as nn class StockLSTM(nn.Module): def __init__(self, input_size2, 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) self.sigmoid nn.Sigmoid() def forward(self, x): # x shape: (batch, seq_len, input_size) out, _ self.lstm(x) # 取最后一个时间步的隐状态 out self.fc(out[:, -1, :]) return self.sigmoid(out).squeeze()input_size2 表示每个时间步输入“当日收益率”和“当日成交量变化率”两个特征seq_len 通常取 20 或 30代表用过去一个月左右的日线做判断。LSTM 的一个通用陷阱是数据量不够时很容易过拟合所以 hidden_size 不要超过 64num_layers 优先用 1除非你确信数据量足够支撑更深的网络。4. 股票预测训练评估与防过拟合滚动切分、回测和常见翻车点4.1 时间序列交叉验证用扩张窗口替代 KFold股票数据不能直接调用KFold或train_test_split的随机切分模式因为那会破坏时间顺序。常见做法是使用扩张窗口或者滑窗验证。扩张窗口的意思是先用前 60% 数据训练验证后 10%再把训练集扩到前 70%验证后 10%依此类推。这样可以模拟模型在不同市场环境下的表现比单次切分得出的准确率更可信。def expanding_window_evaluate(model_factory, X, y, n_splits3): 扩张窗口时间序列验证 n len(X) step (n - int(n * 0.6)) // n_splits acc_list [] for i in range(n_splits): train_end int(n * 0.6) i * step val_start train_end val_end min(val_start step, n) X_train_fold X[:train_end] y_train_fold y[:train_end] X_val_fold X[val_start:val_end] y_val_fold y[val_start:val_end] clf model_factory() clf.fit(X_train_fold, y_train_fold) acc accuracy_score(y_val_fold, clf.predict(X_val_fold)) acc_list.append(acc) print(fFold {i1}: train {train_end} 行, val {val_end - val_start} 行, acc{acc:.4f}) return np.mean(acc_list), np.std(acc_list)这个函数的关键在于train_end和val_start相等训练集和验证集在时间上完全连续不重叠也不留缝隙。输出中的准确率波动可以用于判断模型稳定性如果三个 fold 的准确率分别是 0.58、0.51、0.49说明模型在最近的数据上表现变差可能是市场风格变化也可能是特征失效了这个结论在答辩中非常有价值。4.2 回归任务的评估指标以及为什么准确率会被高估如果你的大作业选择的是回归任务评估指标需要额外谨慎。均方误差MSE对异常值非常敏感一只股票某天因为突发利空跌了 8%这一个样本会把整个 MSE 拉高而平均绝对误差MAE受异常值影响小得多。还有一种常用指标是 MAPE计算每个样本的百分比误差后取平均它更适合向非技术背景的人解释“平均误差大约是多少”。指标含义优点风险MAE预测与真实价格的绝对误差均值直观、抗异常值对小幅波动不敏感RMSE误差平方后取均值的开方放大较大误差对异常值敏感MAPE误差占真实价格的比例适合跨股票比较真实价格接近 0 时会爆炸准确率预测涨跌方向正确比例适合分类任务涨跌分布不均时虚高准确率虚高的典型场景是测试集里 70% 的样本在涨那么模型全部预测“涨”也能拿到 70% 准确率。这也是为什么只报准确率而不报 F1 的作业容易被扣分。建议在源码里同时输出分类报告包含 precision、recall、F1 和支持向量样本数。4.3 模拟回测从预测标签到模拟买卖收益分类模型的准确率只说明方向判断对错不代表能赚钱。这里可以把预测结果模拟成一次简单的交易策略预测上涨就持有预测下跌就空仓。回测结果是指导航能否自圆其说的关键证据。# 模拟回测按预测标签决定是否持有 def backtest(prices, pred_labels, transaction_cost0.001): prices: 每日收盘价 Series pred_labels: 与 prices 对齐的预测标签 transaction_cost: 单次交易手续费 position 0 # 0 空仓, 1 持仓 cash 1.0 shares 0.0 for i in range(1, len(prices)): # 预测上涨且当前空仓则全仓买入 if pred_labels[i] 1 and position 0: shares cash * (1 - transaction_cost) / prices[i] cash 0 position 1 # 预测下跌且当前持仓则全部卖出 elif pred_labels[i] 0 and position 1: cash shares * prices[i] * (1 - transaction_cost) shares 0 position 0 # 期末若仍持仓按最后一天价格平仓 if position 1: cash shares * prices[-1] * (1 - transaction_cost) return cash回测代码里的transaction_cost0.001是双边千分之一的手续费假设如果测试结果比“买入持有”策略还差说明模型的方向性预测没有覆盖交易成本。另一个细节是策略假设当天信号收盘后、第二天开盘才交易避免用当天收盘价买入当天预测又是一种未来函数。4.4 常见翻车点与排查清单一个排查思路是先看训练集和测试集的各项特征分布是否一致。如果测试集里均线特征均值和训练集差异非常大说明市场环境变了模型失效是正常的。其次是检查特征列是否包含未来信息比如有的同学不小心把future_close留在了特征列表里模型实际上读到了答案这类错误在数据预处理阶段就要拦截。# 数据泄露自查确保 label 和 future_close 不在特征列中 assert label not in feature_cols, label 不能作为特征 assert future_close not in feature_cols, future_close 不能作为特征 print(特征列检查通过共, len(feature_cols), 个特征)还有一种低频但致命的错误前复权因子在时间序列里发生了跳变。如果原始数据是用qfq方式下载的而你在中间步骤又重新调用了一次复权接口可能导致整段价格被重复调整。处理方式是全程只保留一次复权口径不建议在本地对已复权数据再做一次价格修正。5. 高分大作业的验证呈现与答辩细节可视化、对比实验和边界说明5.1 三个必画的可视化图以及怎么画才能加印象分第一张图是“真实涨跌与预测涨跌对比图”横轴是时间纵轴是标签值真实标签用灰色散点、预测用黑色散点错分的样本用红色圈出来让观者一眼看出模型在哪些时间段连续犯错。第二张图是“特征重要性 Top 10 柱状图”XGBoost 的feature_importances_在上一节已经拿到了直接按从大到小排序画即可。第三张图是“回测净值曲线”把回测结果和买入持有策略画在同一条坐标轴里系一曲线明显压在另一条上方比“准确率 62%”这种数字更有表现力。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 回测净值对比 test_prices labeled_df[close].iloc[split_idx:] backtest_value backtest(test_prices, xgb_test_pred) # 买入持有策略净值期末价格 / 期初价格 bh_value test_prices.iloc[-1] / test_prices.iloc[0] plt.figure(figsize(10, 5)) plt.plot([1, backtest_value], labelXGBoost 策略, markero) plt.plot([1, bh_value], label买入持有, markerx) plt.legend() plt.title(回测净值对比) plt.savefig(backtest_compare.png, dpi150)画图时注意字体设置Linux 服务器没有 SimHei 时可以改用系统自带的 DejaVu Sans避免中文乱码在最终报告里留下硬伤。图片保存用dpi150以上插入 Word 或 PDF 后放大也不会糊。5.2 对比实验表用一张表让老师看到你的工作量答辩时最直接加分的是对比实验表。不要只放一个模型的最终结果至少要有三行Logistic、XGBoost、LSTM如果做了。每一行放准确率、F1、回测净值、特征数量、训练耗时。即使 LSTM 效果不如 XGBoost也要如实放上并说明“LSTM 在样本量有限时过拟合调低 hidden_size 后有所缓解但整体仍不及树模型”。这比隐藏失败实验、只报结果 0.62 要可信得多。模型测试准确率F1回测净值训练耗时Logistic 回归0.560.551.211sXGBoost0.620.611.438sLSTM (hidden32)0.570.561.1845s回测净值统一用初始资金 1.0 计算注意要和买入持有策略对齐同一时间段否则缺乏可比性。耗时是相对值不同机器差异很大但能体现你考虑过“实际部署的算力成本”。5.3 答辩高频问题与防线设计答辩时老师最常问的就是你预测的准确率只有 61%这个模型在实际交易中能用吗这种时候越诚实越能拿分。你可以明确说这个大作业定位是验证机器学习流程在金融时序数据上是否有效61% 意味着比随机猜测高 11 个百分点在考虑了手续费后策略净值跑赢买入持有说明信号有统计意义上的优势但远不足以直接作为交易决策来源。另一个高频问题是为什么不直接用 LSTM 处理原始 K 线而要做技术指标这个问题如果从特征工程角度回答说你希望先验证手工特征的有效性、再用 LSTM 做对照比单纯说“LSTM 效果不好”有力得多。最后一件事保证源码能一键运行。在 README 里写清楚依赖库版本、Python 版本、按顺序执行的脚本名以及拉不到数据时的本地 CSV 替换方案。老师打开源码的第一眼看到的是清晰的项目结构而不是一个打不开的 notebook这本身就是最重要的印象分。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。