简介这份资源面向计算机、金融工程等专业的学生与量化入门者提供一套基于机器学习预测股票价格趋势的Python仿真课程设计项目可用于课程设计、期末大作业或自学练手。压缩包共3个文件包含1个py主程序、1个md说明文档和1个ico图标整体约10KB体量轻巧下载后无需修改即可直接运行。项目已获导师指导并通过取得97分的高分评价完整度与可运行性有保障。主程序围绕股票价格趋势预测展开配合说明文档梳理了利用机器学习提高量化交易成功率的思路与实现要点读者可借此理解特征构建、模型训练与趋势判断的完整流程并在此基础上替换数据或调整模型进行二次实验。目前已有746人学习下载适合需要一份结构清晰、拿来即用的机器学习实战案例的读者参考。1. 从一份课程设计压缩包说起股票趋势预测到底在预测什么很多人拿到「基于机器学习实现预测股票价格趋势的Python仿真源码数据课程设计.zip」这类资源第一反应是解压、装依赖、跑main.py然后盯着准确率数字发愣。但真正决定这份代码能不能用、值不值得改的不是那个数字而是它把「趋势」定义成了什么。是预测明天收盘价比今天高还是低还是预测未来五天累计涨跌幅超过某个阈值还是直接回归出一个价格数值再取符号这三种定义对应的标签构造、评估指标、甚至模型选型完全不同。这份课程设计通常走的是二分类路线用历史量价数据构造特征标签是「下一交易日涨/跌」模型在逻辑回归、随机森林、XGBoost 或 LSTM 之间选一个。它适合两类人一是需要交课程设计、想快速跑通完整流程的学生二是想拿一份干净骨架去验证自己特征工程想法的从业者。但如果你指望它直接给出可交易的信号那大概率会失望——仿真源码的价值在于流程完整、可复现、可替换而不是收益曲线好看。下面按「数据怎么来、特征怎么造、模型怎么训、坑在哪」四步拆开讲。2. 数据获取与标签构造把「趋势」翻译成模型能吃的标签2.1 用 akshare 或 yfinance 拉日线数据的最小命令课程设计压缩包里通常已经附带一份 CSV但你要换股票或换时间段就得自己拉。常见做法是用akshare拉 A 股用yfinance拉美股。先装包pip install akshare yfinance pandas numpy scikit-learn拉平安银行0000012020 年至今的日线import akshare as ak import pandas as pd # 拉 A 股日线adjustqfq 表示前复权避免除权跳空污染标签 df ak.stock_zh_a_hist( symbol000001, perioddaily, start_date20200101, end_date20241231, adjustqfq ) df df.rename(columns{ 日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume }) df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) print(df.shape, df.head())逻辑说明adjustqfq是关键参数不复权的话分红送股当天价格会断崖式下跌模型会把它当成真实暴跌。perioddaily对应日线课程设计基本够用。拉完美股用yfinance.download(AAPL, start2020-01-01)字段名不同但后续处理一致。参数说明start_date和end_date格式是YYYYMMDDakshare 不接受横杠。如果你拉的是指数用ak.stock_zh_index_daily(symbolsh000001)。数据量建议至少 3 年否则训练集太短时序切分后验证集只有几十条指标波动极大。2.2 标签构造二分类、三分类还是回归取符号这是整份代码最容易被忽略、也最影响结果的一步。常见三种做法标签方式构造方法优点缺点二分类涨跌label (close.shift(-1) close).astype(int)简单、类别均衡忽略涨跌幅大小噪声大三分类涨超 1% 为 2跌超 1% 为 0其余为 1过滤微小波动中间类样本多需处理不均衡回归取符号预测next_ret再sign()保留幅度信息回归误差大符号未必准课程设计里最常见的是二分类。构造代码# 未来一日收益率 df[next_ret] df[close].shift(-1) / df[close] - 1 # 二分类标签涨为 1跌或平为 0 df[label] (df[next_ret] 0).astype(int) # 最后一行 next_ret 是 NaN必须丢掉否则标签泄漏 df df.dropna(subset[next_ret]).reset_index(dropTrue)注意shift(-1)的方向它把「明天」的信息拉到「今天」这一行所以今天这行的特征对应明天涨跌。最后一行没有明天标签是 NaN不丢会导致训练时报错或悄悄用 0 填充。另外如果你用close.shift(-1) close而不做dropnapandas 会把 NaN 比较结果当 False最后一行被标成 0这是血泪经验里最常见的静默错误。2.3 时序切分为什么不能 random_split很多人顺手写train_test_split(df, test_size0.2)这是翻车重灾区。随机切分会让模型在训练集里见到「未来」的样本验证集指标虚高十几个点。正确做法是按时间切split_idx int(len(df) * 0.8) train df.iloc[:split_idx].copy() test df.iloc[split_idx:].copy() # 特征列排除 date、next_ret、label feature_cols [c for c in df.columns if c not in [date, next_ret, label]] X_train, y_train train[feature_cols], train[label] X_test, y_test test[feature_cols], test[label]逻辑说明前 80% 时间训练后 20% 测试模拟真实「用历史预测未来」。如果你要做滚动验证用TimeSeriesSplit(n_splits5)它每次训练集都在验证集之前。参数上n_splits越大每折训练集越短小数据集建议 3 到 5。3. 特征工程与模型训练从量价指标到树模型和 LSTM3.1 均线、收益率、波动率十个能跑通的基础特征课程设计附带的特征通常比较朴素常见的是收盘价、成交量、几日均线。要提升一点区分度可以补下面这些全部只用历史信息不引入未来import numpy as np df[ret_1] df[close].pct_change(1) df[ret_5] df[close].pct_change(5) df[ma_5] df[close].rolling(5).mean() df[ma_20] df[close].rolling(20).mean() df[ma_ratio] df[ma_5] / df[ma_20] df[vol_5] df[ret_1].rolling(5).std() df[vol_20] df[ret_1].rolling(20).std() df[vol_ratio] df[volume] / df[volume].rolling(20).mean() df[high_low] (df[high] - df[low]) / df[close] df[close_open] (df[close] - df[open]) / df[open] # 滚动窗口产生 NaN统一丢掉 df df.dropna().reset_index(dropTrue)逻辑说明pct_change(5)是 5 日累计收益ma_ratio捕捉短期均线相对长期均线的位置vol_ratio是量比。high_low和close_open是日内波动和日内方向。所有rolling默认向右对齐不会用到未来数据。参数上窗口 5 和 20 是日线常用组合换成 10 和 60 也可以但窗口越大开头丢掉的样本越多。注意dropna()之后要重新reset_index否则后面按位置切分会错位。这一步做完特征大约 10 到 15 列样本量取决于你拉了多少年。3.2 逻辑回归、随机森林、XGBoost 的对比与参数课程设计一般会选一个模型。如果你想对比用同一份特征跑三个from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline from xgboost import XGBClassifier from sklearn.metrics import accuracy_score, f1_score models { lr: make_pipeline(StandardScaler(), LogisticRegression(max_iter1000)), rf: RandomForestClassifier(n_estimators200, max_depth5, random_state42), xgb: XGBClassifier(n_estimators200, max_depth4, learning_rate0.05, subsample0.8, colsample_bytree0.8, eval_metriclogloss, random_state42) } for name, model in models.items(): model.fit(X_train, y_train) pred model.predict(X_test) print(name, acc%.4f % accuracy_score(y_test, pred), f1%.4f % f1_score(y_test, pred))逻辑说明逻辑回归必须标准化否则均线数值和收益率量级差几个数量级收敛慢且系数不可比。随机森林和 XGBoost 对量纲不敏感但max_depth要压住日线噪声大树太深直接过拟合。subsample和colsample_bytree是行和列采样0.8 是常用起点。参数说明n_estimators200在几千条样本上够用再大边际收益低。learning_rate0.05配合 200 棵树比较稳调到 0.1 要相应减树。eval_metriclogloss是二分类默认别改成rmse。如果你发现三个模型准确率都在 50% 到 55% 之间这是正常的日线方向预测本来就接近随机别急着调参先检查标签和切分有没有泄漏。3.3 LSTM 版本输入张量形状和两个必调参数如果课程设计用的是 LSTM核心是把二维表格转成三维张量(样本数, 时间步, 特征数)。常见做法import torch import torch.nn as nn def make_sequences(X, y, seq_len20): 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 20 X_train_seq, y_train_seq make_sequences(X_train.values, y_train.values, seq_len) X_test_seq, y_test_seq make_sequences(X_test.values, y_test.values, seq_len) class LSTMModel(nn.Module): def __init__(self, n_feat, hidden64): super().__init__() self.lstm nn.LSTM(n_feat, hidden, batch_firstTrue) self.fc nn.Linear(hidden, 1) def forward(self, x): out, _ self.lstm(x) return torch.sigmoid(self.fc(out[:, -1, :])) model LSTMModel(X_train_seq.shape[2])逻辑说明seq_len20表示用过去 20 天预测第 21 天对应一个月交易日。batch_firstTrue让输入形状是(batch, seq, feature)不设的话要转置容易搞混。out[:, -1, :]取最后一个时间步的隐状态这是分类常用做法。参数说明hidden64对小数据集够用128 容易过拟合。训练时batch_size设 32 或 64epoch控制在 30 以内配合早停。LSTM 在几千条样本上未必比 XGBoost 好课程设计里用它更多是为了展示深度学习流程别默认它更强。4. 避坑与排查五个让课程设计翻车的细节4.1 准确率 90% 以上先查标签泄漏现象测试集准确率异常高超过 85%。原因特征里混入了未来信息比如用了close.shift(-1)构造的特征或者dropna之前就切分导致训练集包含测试集时间。解决逐列检查特征构造确保每个rolling和shift都是正数或默认切分必须在所有特征和标签构造完成之后按时间一刀切。4.2 预测全是 0 或全是 1类别不均衡没处理现象模型输出单一类别准确率等于多数类占比。原因涨跌样本比例可能 55:45但某些时间段单边行情会让比例到 70:30模型学会偷懒。解决用class_weightbalanced逻辑回归和随机森林都支持或对训练集做欠采样。XGBoost 用scale_pos_weight负样本数/正样本数。4.3 回测收益曲线漂亮但实盘不行交易成本没算现象仿真里按预测信号买卖累计收益翻倍。原因没扣手续费和滑点且用了收盘价成交。解决在回测里加cost0.001每次换手成交价用次日开盘而非当日收盘。课程设计可以不写回测但如果你要展示收益这两点必须补。4.4 换股票后代码报错字段名和复权方式不一致现象akshare 拉的数据列名是中文yfinance 是英文直接拼在一起报 KeyError。原因没做统一重命名。解决拉完数据立刻重命名成date/open/close/high/low/volume并统一用前复权。不同股票停牌日不同dropna后长度会变别假设固定行数。4.5 LSTM 训练 loss 不降输入没归一化现象LSTM 的 loss 在 0.69 附近震荡等于随机猜。原因成交量数值几千几万收益率是小数直接喂进 LSTM 梯度爆炸或消失。解决对所有特征做MinMaxScaler或StandardScaler且 scaler 只在训练集上 fit再 transform 测试集。这一步和逻辑回归的标准化逻辑相同但 LSTM 更敏感。5. 把仿真源码改成自己的验证框架一个滚动回测技巧课程设计交完不是终点。如果你想用这份骨架验证「某个特征到底有没有用」最直接的方法是滚动训练加特征消融。具体做法固定TimeSeriesSplit(n_splits5)每次用前四折训练、第五折验证记录加入新特征前后的 F1 差值。差值稳定为正才说明特征有增量信息。from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import f1_score from xgboost import XGBClassifier tscv TimeSeriesSplit(n_splits5) base_feats [ret_1, ret_5, ma_ratio, vol_ratio] new_feats base_feats [high_low, close_open] def rolling_f1(feats): scores [] for tr_idx, va_idx in tscv.split(df): X_tr, X_va df.iloc[tr_idx][feats], df.iloc[va_idx][feats] y_tr, y_va df.iloc[tr_idx][label], df.iloc[va_idx][label] m XGBClassifier(n_estimators200, max_depth4, learning_rate0.05, eval_metriclogloss, random_state42) m.fit(X_tr, y_tr) scores.append(f1_score(y_va, m.predict(X_va))) return np.mean(scores), np.std(scores) print(base:, rolling_f1(base_feats)) print(with new:, rolling_f1(new_feats))逻辑说明TimeSeriesSplit保证每折验证集都在训练集之后f1_score比准确率更适合类别略不均衡的场景。比较两组特征的均值和标准差如果新特征只提升 0.005 但标准差 0.03那基本是噪声。参数说明n_splits5在 1000 条样本上每折验证约 170 条够算 F1。样本少于 500 条就降到 3 折。XGBoost 参数保持和前面一致别在消融实验里调参否则分不清是特征贡献还是参数贡献。我自己的习惯是任何一份课程设计源码先跑通原版记下基线指标然后只改一个变量——要么换标签定义要么加一组特征要么换切分方式。一次只动一个否则指标变了你也不知道是谁的功劳。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
