多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

机器学习股票预测大作业指南:数据处理、模型评估与避坑

机器学习股票预测大作业指南:数据处理、模型评估与避坑 简介一套面向计算机专业期末大作业与课程设计场景的机器学习股票预测算法源码项目出自大三学生之手经导师指导评审获得99分。资源包共13个文件、约2.53MB以6个Jupyter Notebook为主体覆盖K线数据入库、FFT滤波、LSTM预测、小市值结合盈利指标过滤及Sklearn单票回测另有2个Python脚本负责数据采集与特征处理3个CSV文件提供示例数据可直接运行复现。完整代码经过验证能帮助初学者快速跑通“数据获取—特征构建—模型训练—回测评估”全流程通过阅读笔记式Notebook和README还能理解各模块调用关系与排错思路。此外项目保留中间数据与回测结果便于对照验证每一步改动效果适合作为课程设计、期末大作业或机器学习入门实战参考。目前已有83人学习下载属于评分较高、实用性较强的完整项目。1. 机器学习股票预测到底能拿到什么样的“高分大作业”“Python的机器学习股票预测算法源码项目”这个标题在课程作业和毕设里出现频率极高搜索它的人多半不是在找能实盘的赚钱系统而是想交一份让老师挑不出毛病、能讲清楚原理、代码能跑出合理结果的课程项目。现实一点说用机器学习预测股票涨跌准确率做到所谓“稳定盈利”是不现实的但作为一次完整的“数据获取 → 特征工程 → 模型训练 → 结果评估”的大作业它几乎是所有选题里性价比最高的方向数据免费、代码有现成框架、可视化效果好看、答辩时故事也好讲。但有句血泪经验得先说在前头股票预测这个题最大的坑不在模型而在数据处理和评估方式。很多同学第一次跑通代码发现训练集准确率95%以为稳了一测测试集直接50%瞎猜水平甚至发现模型在“用今天的信息预测昨天”。这类问题每一个都足以让答辩翻车。这篇文章把从数据清洗到模型评估的全流程拆开讲每一步给可照抄的代码最后集中列常见踩坑目标是让你拿到一份能运行、能解释、老师问不倒的“高分大作业”。2. 预测什么、用什么预测先把问题的边界定清楚2.1 数据源怎么选tushare、akshare、baostock怎么取舍做股票预测第一步是拿数据。常见的免费数据源有三个tushare、akshare、baostock。从大作业的角度我一般推荐baostock或者tushare的积分接口。原因很简单它们返回的都是pandas DataFrame字段命名规整不需要自己解析JSON而且历史数据覆盖A股从2000年前后到现在的日线行情足够做10年以上的回测。三者的差别在细节上baostock免费、无需注册token但只提供行情和基础财务数据数据更新到当天收盘后接口偶尔抽风重试几次能好。tushare社区积分够用的话很方便日线接口返回的复权因子、涨跌幅字段直接帮你算好省掉自己复权的麻烦。但新注册用户积分不够部分接口调不了。akshare接口最丰富但有相当一部分接口返回的字段名不稳定今天叫date明天叫日期做作业时为了修字段名浪费时间性价比低。我的建议是优先tushare如果没有积分就用baostock。下面代码以baostock为例因为直接能跑通不需要任何注册。import baostock as bs import pandas as pd # 登录baostock这个登录只是建立本地连接不涉及账号 lg bs.login() # 获取平安银行000001从2015年到2023年的日线数据 rs bs.query_history_k_data_plus( sz.000001, date,code,open,high,low,close,preclose,volume,amount,pctChg, start_date2015-01-01, end_date2023-12-31, frequencyd, adjustflag2 # 前复权避免分红送股造成价格跳空 ) data_list [] while (rs.error_code 0) and rs.next(): data_list.append(rs.get_row_data()) df pd.DataFrame(data_list, columnsrs.fields) bs.logout()这里adjustflag2是前复权做股票预测必须复权否则分红除权造成的价格下跌会让模型以为“跌了”其实是正常分红。前复权更适合做技术面特征计算因为历史价格会被调整到当前视角避免了图表上的虚假缺口。2.2 标签怎么定预测涨跌方向比预测价格靠谱得多拿到行情数据后第一个关键决策是预测目标到底是什么。很多新手上来就做回归用LSTM预测明天的收盘价然后发现预测曲线总是比真实曲线“慢半拍”——昨天涨模型预测今天涨本质上只是在做一个滞后一日的跟踪没有任何实际意义。大作业里最稳妥的做法是把它定义成二分类问题明天相对于今天是涨还是跌。这样模型的任务更简单评估指标也更直观答辩时你能够清楚地解释准确率、F1值代表什么。构造标签的代码如下。# 预测目标明天收盘价是否高于今天收盘价 df[close] df[close].astype(float) df[label] (df[close].shift(-1) df[close]).astype(int) # shift(-1)会把明天的价格拉到今天这一行所以最后一行是没有标签的 df df.iloc[:-1].copy()shift(-1)是这里最容易出错的地方。它把明天的收盘价“提前”到今天用来判断今天的收盘价相对明天是高还是低。因为最后一行没有明天的数据必须删掉否则会用NaN参与训练轻则报错重则pandas自动丢弃那一行你还没发现。做完这一步检查一下df[label].value_counts()如果涨跌比例接近1:1说明标签构造正常如果出现极端偏差就要回头查数据是否按时间排序过。2.3 特征怎么造不依赖未来数据的20个技术指标特征特征工程是拿高分的关键。常见的做法是把技术指标当成特征但这里有一个大坑很多开源库计算指标时默认使用了未来数据。比如有些版本的指标计算会对整段序列做中心滑动平均或者用未来一段时间的最高价来算威廉指标——这在高频交易里叫“未来函数”是大作业里最容易翻车的技术细节。我的做法是用一个通用函数基于已收盘的历史数据逐行滚动计算特征保证每一行的特征只依赖于截至当天的数据。以下代码构造了动量、均线、波动率、成交量变化四类代表特征实际作业中可以扩充到20个以上。# 按时间正序排列确保滚动窗口是“过去到当前” df df.sort_values(date).reset_index(dropTrue) # 动量特征过去5日、10日收益率 df[ret_5] df[close].pct_change(5) df[ret_10] df[close].pct_change(10) # 均线偏离度收盘价相对20日均线的偏离百分比 df[ma_20] df[close].rolling(20).mean() df[ma_20_dev] (df[close] - df[ma_20]) / df[ma_20] # 波动率过去10日收益率的标准差 df[volatility_10] df[close].pct_change().rolling(10).std() # 成交量变化今日成交量相对5日均量的比值 df[volume_ratio] df[volume] / df[volume].rolling(5).mean() # 删除前面因滚动窗口产生的NaN行 df df.dropna().reset_index(dropTrue)这里的核心原则是所有特征都只能由“截止到当天收盘已经存在的数据”计算出来。pct_change(5)用的是前5天到今天的收盘价rolling(20).mean()用的是最近20天都不包含未来信息。答辩时如果老师说“你怎么保证没有用未来数据”你直接把这段特征构造逻辑展示出来比嘴上解释一百遍都有说服力。特征数量也不是越多越好。20个维度用随机森林或XGBoost还算稳但如果你后面接了神经网络特征太多而样本只有几万个很容易过拟合。一般大作业控制在15到30个特征之间足够展示工作量又不会因为维度灾难导致模型学不动。3. 模型选型与训练从逻辑回归到LSTM的完整路线3.1 为什么先跑逻辑回归基线模型让你知道上限在哪股票涨跌预测这个题目有一个常见误解模型越复杂越好。实际上真实行情里信噪比极低你再怎么调LSTM准确率也很难超过55%太多。所以正确路线是先跑一个逻辑回归作为基线它训练快、可解释性强、几乎不可能过拟合而且答辩时你可以说“我用了简单模型作为baseline准确率是X复杂模型在此基础上提升了Y。”这套话术本身就比“我用了一个LSTM准确率52%”更有说服力。逻辑回归在sklearn里跑起来也就几行代码但这里必须注意一个数据处理细节要做特征标准化。逻辑回归用梯度下降优化特征尺度不一致会导致收敛慢甚至不收敛。股价是几十块钱的量级收益率是百分之几不标准化的话模型会把注意力全放在大数值特征上。from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.metrics import accuracy_score, f1_score feature_cols [ret_5, ret_10, ma_20_dev, volatility_10, volume_ratio] X df[feature_cols].values y df[label].values # 按时间顺序切分前80%训练后20%测试 split_idx int(len(X) * 0.8) X_train, X_test X[:split_idx], X[split_idx:] y_train, y_test y[:split_idx], y[split_idx:] scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test) # 注意用训练集拟合的scaler直接转换测试集 model LogisticRegression(max_iter1000) model.fit(X_train, y_train) y_pred model.predict(X_test) print(f准确率: {accuracy_score(y_test, y_pred):.4f}) print(fF1: {f1_score(y_test, y_pred):.4f})两个细节值得说。第一切分必须按时间顺序不能直接用train_test_split随机打乱。股票数据是时间序列用未来数据训练再去预测过去准确率虚高没有任何意义。第二scaler只能用训练集拟合然后直接转换测试集如果你在全部数据上做标准化再切分测试集的信息已经渗入训练过程属于典型的数据泄露。3.2 复杂模型怎么加随机森林与XGBoost的调参边界基线跑通后作业的工作量体现就在“模型对比”上。最常见的组合是加一个随机森林和一个XGBoost。这两个模型对特征标准化不敏感可以直接用原始特征但都有各自的过拟合风险。随机森林的n_estimators不要一味加大300到500就够再多只会增加训练时间不提升效果。更重要的是max_depth和min_samples_leaf股票数据噪声大树一旦长得太深就会把历史行情中的随机波动当成规律训练集准确率能到90%测试集立刻打回原形。from sklearn.ensemble import RandomForestClassifier rf_model RandomForestClassifier( n_estimators300, max_depth5, # 限制深度防止过拟合 min_samples_leaf50, # 叶子节点最少样本数让模型学大趋势而非个例 random_state42, n_jobs-1 ) rf_model.fit(X_train, y_train) y_pred_rf rf_model.predict(X_test) print(f随机森林准确率: {accuracy_score(y_test, y_pred_rf):.4f})min_samples_leaf50这个参数在股票预测里几乎是救命稻草。默认值是1意味着每个叶子节点可以只装一个样本模型会把历史中某一天的特殊涨跌完全记住。设成50后模型被迫寻找至少50个样本共同成立的规律泛化能力强很多。如果你发现训练集准确率和测试集差超过15个百分点优先调这个参数而不是加正则化。XGBoost的话learning_rate设0.05左右n_estimators不用早期停止的话设300就够。注意XGBoost对缺失值有自己的处理逻辑但我们的特征已经用dropna()处理过不需要依赖这个特性。3.3 LSTM要不要上小样本下如何避免“深度学习翻车”如果作业要求里写了“可以使用深度学习”或者你想冲刺高分加一个LSTM是有必要的。但必须说清楚LSTM在几千到几万样本的日线数据上效果大概率不如XGBoost这是个正常现象不代表你做错了。答辩时你可以理直气壮地说“LSTM在长序列建模上有优势但日线级别的特征序列长度较短、信噪比低它的优势发挥不出来。”这句话本身就是对模型原理的理解体现。LSTM的输入和传统模型完全不一样。它需要构造三维张量(样本数, 时间步长, 特征维度)意思是每一个样本不是一行特征而是一段连续的历史窗口。比如用过去30天的5个特征来预测第31天是涨是跌那么样本形状是(N, 30, 5)。import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def make_sequence_data(X, y, seq_len30): X_seq, y_seq [], [] for i in range(seq_len, len(X)): X_seq.append(X[i - seq_len:i]) # 过去seq_len天的特征 y_seq.append(y[i]) # 当天的标签 return np.array(X_seq), np.array(y_seq) # 用同样的标准化逻辑注意必须用训练集的scaler X_test_scaled scaler.transform(X_test) # 这里以测试集为例 X_seq, y_seq make_sequence_data(X_test_scaled, y_test, seq_len30) model_lstm Sequential([ LSTM(32, input_shape(X_seq.shape[1], X_seq.shape[2]), return_sequencesFalse), Dropout(0.3), Dense(1, activationsigmoid) ]) model_lstm.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy]) model_lstm.fit(X_seq, y_seq, epochs5, batch_size64, validation_split0.1, verbose0)LSTM这里只写了测试集部分来演示构造流程实际训练时你需要把训练集也转成序列格式。epochs5是我习惯的起点股票预测任务epochs超过10基本就开始过拟合了训练集loss持续下降验证集loss反而上升。LSTM(32)的32是隐藏单元数这个值不要贪大日线数据模式简单32个单元足够捕捉周期规律了。注意input_shape里第一个维度是时间步长第二个是特征维度顺序反了会在fit时报维度错误而且这种错误在报错信息里很不直观排查起来很费时间。4. 机器学习股票预测的避坑清单五个让项目翻车的常见问题4.1 现象准确率极高但结果完全没用训练集准确率95%测试集准确率48%这不是“模型没调好”而是你的数据处理阶段出了问题。最常见的原因是特征或标签包含未来信息。比如用shift(-1)构造标签时如果同时把明天的pctChg当成了特征模型实际上是在“抄答案”。检查方法很简单把feature_cols里的列逐一画时间序列用眼睛对比特征和标签的走势或者把数据按照日期正序排列后随机抽几行人工验证特征是否只依赖当天及之前的数据。解决方式是在特征计算完成后加一道“禁用清单”把所有通过shift()负向移动过的列全部剔除出特征集并且打印特征列名让老师看到。这比任何答辩话术都有说服力。4.2 现象每次运行结果都不一样随机森林的random_state没设固定值LSTM的初始化权重是随机的这些都会导致每次跑出来的准确率略有浮动。有个搜索热词叫“模型预测股票涨跌每次结果不一样”这几乎是股票预测项目里被问得最多的问题。原因就是没有固定随机种子。解决方式是全局设置随机种子。Python层面、numpy层面、tensorflow层面各设一次缺一个都不行。代码放在脚本最顶部确保所有库import之后、任何数据处理之前执行。import random import numpy as np import tensorflow as tf random.seed(42) np.random.seed(42) tf.random.set_seed(42)另外XGBoost和LightGBM内部也有随机性XGBoost里设random_state42即可。这样处理后任何人在同一份数据上跑你的代码结果都是一致的这在大作业评分里是一个很加分的稳定性指标。4.3 现象特征里有大量NaN模型直接报错pct_change(5)会让前5行变成NaNrolling(20).mean()会让前19行变成NaN如果你连续计算多个特征前几十行全是NaN。很多新手在这个环节选择df df.dropna()然后发现自己数据少了几十行——这没问题但如果你的数据是按时间排序的删除的行是最早的几十天这也没问题。问题是有些人为了保留数据量用fillna(methodbfill)把后面的值往前填这等于把未来数据塞进了历史特征模型学到的是“用明天填充的数据预测明天”典型未来函数。解决方式是先排序、再算特征、最后dropna()并用reset_index(dropTrue)重置索引。不要用bfill也不要用全局均值填充滚动窗口产生的NaN。4.4 现象时间序列说得头头是道切分却用了随机打乱from sklearn.model_selection import train_test_split是股票项目里最常见的一行“自杀代码”。train_test_split默认随机打乱数据你的测试集里会出现2021年的数据训练集里会有2023年的数据模型用未来信息训练测试集准确率虚高。这里没有商量的余地股票数据必须按时间顺序切分训练集会早于测试集。解决方式是手动切分或者用TimeSeriesSplit做交叉验证。大作业场景下直接手动按比例切分最简单写代码时记得加注释“按时间顺序切分防止未来信息泄露”让评分老师一眼看到你知道这个坑。4.5 现象用过采样解决涨跌不平衡结果更差了有些同学用SMOTE对训练集做上采样试图把上涨和下跌样本比例拉平。方向上没错但在时间序列上做SMOTE有一个致命问题它会在样本之间插值合成的新样本可能混合了不同时间的特征模式破坏了时间上的因果结构。更实际的问题是A股日线级别的涨跌样本本来就接近1:1你检查一下就知道根本不需要过采样。解决方式是如果你的标签分布比例在4:6到6:4之间不要做任何采样处理直接训练。只有极端不平衡才需要处理而股票日线数据很少出现这种情况。5. 用滚动回测验证模型高分大作业的“最后一公里”5.1 为什么静态切分不够模拟真实的交易节奏前面用的按时间8:2切分只适合展示“模型在未知数据上的表现”但大作业如果止步于此答辩时很容易被追问“你这个模型如果放到真实交易里能不能赚钱”静态切分无法回答这个问题因为它只测了一次。真实场景中模型需要不断用新数据重新训练所以更高阶的做法是滚动回测把5年数据切成多段每段先用前3年训练、后6个月测试然后窗口向前滚动6个月循环多次最后把多次测试结果合并统计。这样才能得到一个有统计意义的评估结论。5.2 模拟手续费和滑点准确率之外你必须算的两笔账光看准确率在股票预测作业里不算完整。真实交易里有交易成本A股按双边万三到万五算手续费还有印花税如果换手率过高就算预测准确率52%收益也可能是负的。建议在回测里加一个简单的成本模型每次预测到“上涨”就买入次日收盘卖出单次交易扣除0.1%的成本。这样回测结果里的收益率才是“可解释的数字”而不是纸上富贵。5.3 命中率稳定性和换手率额外付加分的指标大作业评分的核心参考指标有三个准确率、F1、回测累计收益。但如果你想多拿分建议再输出两个指标预测为上涨时实际下跌的比例看空时误判率以及全样本的换手率。第一个指标衡量模型在“看涨”时的可靠性第二个衡量模型是否过于频繁交易。我自己习惯把这两个指标打印在回测结果的最下方作为“补充风险评估”红笔一勾作业的完成度立刻不一样。5.4 复现性检查一份能锁住随机性的代码才是好作业最后养成一个习惯所有代码跑完后把模型参数和随机种子一并写进一个配置字典保存成JSON文件。下次重跑时加载它保证结果一致。我当时做这个项目最大的教训是没有在项目初期固定随机种子结果中期加入了新特征后所有旧结果都无法复现白白重跑了一天实验。这份配置文件的代码不值得贴在这里但请务必在你的项目里做同样的事设置为seed 42并全局生效。希望帮到你。本文还有配套的精品资源点击获取
返回列表