简介这份资源围绕股票价格分析与预测演示如何用机器学习完成从数据清洗、特征构造到模型训练与评估的完整流程适合具备一定Python基础、希望系统入门金融机器学习的开发者。压缩包体积仅45KB共包含2个文件一个可直接运行的Python实践脚本与一份依赖清单文本前者承载建模主流程后者标明运行环境所需组件。资源已被441人学习属于小而精的实操案例。内容涉及数据预处理中的缺失值与异常值处理、标准化归一化及时间序列的移动平均/差分去趋势特征工程涵盖MACD、RSI等技术指标构造模型层面覆盖线性回归、决策树、随机森林、支持向量机与LSTM/GRU等算法并给出训练/测试集划分、k折交叉验证、MSE/RMSE/R^2评估及网格搜索/随机搜索调参思路便于对照代码理解从数据到预测的每个环节。同时强调政策变化、突发事件等非量化因素对模型的影响提醒结合专业知识与风控策略使用帮助读者建立更全面的建模认知。1. 机器学习算法预测股票价格先绕过那个最诱人的误区每隔一段时间就会有人把「基于机器学习算法的股票市场股票价格的分析及预测」这类项目从网上下载下来打开压缩包看到里面的 .ipynb 和 .py 文件然后问同一个问题这个能让我赚钱吗我的回答通常泼冷水——如果这个包能稳定赚钱作者不会把它传成压缩包。但它依然值得做而且是个极其适合练手机器学习全流程的方向。因为股票数据的分析预测绕不开一整套真实的工作链数据清洗、特征构造、模型选型、回测验证。这套链路的通用性极强做完一次你会真正理解什么是过拟合、什么是数据泄漏、为什么测试集的准确率是假的。这篇文章适合两类人一是准备做课程设计或毕业设计需要一个能讲清楚原理和代码的项目二是刚入门机器学习想找一个结构化程度高、数据免费可得的数据集来练手。目标很具体让你把压缩包里的东西拆开、理解、改造最终跑出一版属于自己的预测流程。2. 股票数据预测的底层逻辑先搞清你预测的到底是什么2.1 预测目标不是涨跌而是价格区间和概率很多人拿到股票数据后第一件事就是跑回归试图直接预测明天的收盘价。这个思路不是不对但落地时会有个很尴尬的问题价格预测的误差评估非常反直觉。今天股价是 10 块模型预测明天是 10.05误差 0.5%看起来挺准。但股价的绝对数值包含了公司基本面、市场情绪、板块轮动等多种因素而这些因素大多数不在你的特征矩阵里。你用一个只包含历史价格和成交量的模型去预测绝对价格本质上是在用一个残缺的信息集预测一个完整信息集决定的结果误差小是运气误差大是常态。我一般会在项目的第一步把预测目标改掉不直接回归收盘价而是构造一个二分类或三分类目标。比如「明日收盘价相对今日收盘价的涨跌幅是否超过 0.5%」或者「未来 5 日最高价是否突破某个阈值」。这样做有两个好处。第一分类问题的评估指标准确率、F1、混淆矩阵在汇报时远比回归的 MSE 好解释第二分类目标天然平滑了价格数值的噪声模型更容易学到稳定的模式。如果你保留回归任务也建议把预测目标从「绝对价格」换成「收益率」或者「对数收益率」这两个序列的统计特性更平稳也更适合作为机器学习的输入。2.2 特征工程是决定模型上限的隐性环节这个项目的特征工程比选哪个模型更值得花时间。我见过太多人直接把「开盘价、收盘价、最高价、最低价、成交量」五列原始数据喂进随机森林然后抱怨准确率上不去。这不是模型的问题是特征里没有信息增量。常见做法是构造三类特征。第一类是技术指标类用 pandas 的 rolling 窗口计算。均线 MA5、MA10、MA20 反映趋势RSI 反映超买超卖MACD 的 DIF 和 DEA 反映动量变化布林带的上中下轨反映波动区间。第二类是时序滞后类把昨日的收盘价、今日的开盘价、过去 N 日的收益率均值作为特征。这类特征能让模型感知到「最近发生了什么」而不仅仅是「当前是什么状态」。第三类是交叉特征比如成交量与价格同时放大的信号、收盘价在布林带中的相对位置。这类特征量少但往往最关键。需要注意的是特征构造有个容易翻车的点窗口计算时的边界填充。pandas 的 rolling 函数默认在窗口不足时产生 NaN很多人直接 dropna() 删掉导致早期样本全部消失。我建议用 min_periods 参数控制最小窗口数比如df[MA10] df[close].rolling(window10, min_periods5).mean()。这样前 4 行用不满 10 日的数据先算出一个弱化的均值不至于直接砍掉一段样本。3. 数据获取与预处理拿到干净可用的股票数据3.1 免费数据源的选择与取舍写这类项目最常用的数据源是 yfinanceYahoo Finance 的 Python 接口和 Tushare。如果面向国内用户做课程设计Tushare 更友好如果追求零配置、跨平台yfinance 更方便。我一般推荐一个混合方案先用 yfinance 快速验证流程再用 Tushare 拉 A 股数据做正式实验。原因是 yfinance 偶尔会出现数据缺失或停牌日处理不规范的情况而 Tushare 的 A 股日线数据质量更稳定但需要注册 token。下面是一个用 yfinance 拉取数据的基准代码import yfinance as yf import pandas as pd # 拉取某只股票近两年的日线数据 df yf.download( 600519.SS, # 贵州茅台A股后缀 .SS start2022-01-01, end2024-01-01, auto_adjustTrue # 自动复权避免除权跳空影响 ) # 只保留需要的列并重命名 df df[[Open, High, Low, Close, Volume]].copy() df.columns [open, high, low, close, volume] # 检查缺失值比例 print(f缺失值数量: {df.isnull().sum().sum()}) print(f数据量: {len(df)} 行)yfinance 的auto_adjustTrue参数值得专门说明。股票在除权除息日会产生价格跳空如果不做复权处理模型会把这种「假价格变化」当成真实信号导致训练出的模型在实盘时频繁误判。auto_adjust会自动按复权因子修正历史价格保证价格序列连续。这个开关一定要打开。3.2 训练集与测试集的分割不能用随机打乱股票数据是有时间顺序的序列数据分割时必须遵循时间顺序绝不能像图像分类那样用train_test_split随机打乱。否则模型会看到未来的数据测试集的准确率会虚高到让你误以为发现了圣杯。这是一个项目中最隐蔽但也最常见的错误。推荐的划分方式是按时间切片# 按时间顺序分割前 80% 训练后 20% 测试 split_idx int(len(df) * 0.8) train_df df.iloc[:split_idx].copy() test_df df.iloc[split_idx:].copy() # 特征列与标签列 feature_cols [open, high, low, volume, ma5, ma10, rsi, momentum] X_train train_df[feature_cols].values y_train train_df[target].values X_test test_df[feature_cols].values y_test test_df[target].values这里有个容易被忽略的坑特征列中的均线、RSI 等指标是用整个数据集的滚动窗口算出来的。如果在分割数据集之后再计算特征那么训练集前 N 行的均线值其实用到了测试集的数据这属于特征泄漏比标签泄漏更隐蔽。我一般会把「构造特征」这个步骤放在分割之前完成但只在训练集上计算 rolling 均值测试集的特征用训练集的统计量来填充。更严格的做法是分两段独立计算特征先用前 80% 的数据算出一个基准窗口再让测试集的特征从训练集末尾延续过来。4. 算法选型与训练从「十大机器学习算法」里挑适合股票预测的4.1 先做算法横向对比用一个小实验定方向如果你在搜索引擎里搜「机器学习算法 股票价格预测」会看到各种帖子把十大机器学习算法列了一遍从线性回归到朴素贝叶斯仿佛每个都能用。但实际上经过我的反复测试真正在股票这类带噪声、非平稳、弱信号的数据上表现稳定的通常集中在三类模型里集成树模型XGBoost/LightGBM 或随机森林、线性模型逻辑回归用于分类目标、以及长短期记忆网络 LSTM。支持向量机在小样本上表现尚可但在金融时序上调整核函数的收益很低性价比不如树模型。我不建议上来就选 LSTM。原因很实际LSTM 对数据量要求高、训练慢、调参维度多课程设计的展示周期内很难调到理想状态。更务实的路线是先用随机森林或 XGBoost 跑出一个基线结果如果效果不够再考虑 LSTM。下面用 scikit-learn 的随机森林配合简单网格搜索做演示from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV from sklearn.metrics import accuracy_score # 定义模型与候选参数 model RandomForestClassifier(random_state42, class_weightbalanced) param_grid { n_estimators: [100, 200], max_depth: [3, 5, 7], min_samples_leaf: [5, 10] } grid GridSearchCV( model, param_grid, scoringf1, # 用 F1 而不是准确率因为涨跌样本通常不平衡 cv5, n_jobs-1 ) grid.fit(X_train, y_train) best_model grid.best_estimator_ y_pred best_model.predict(X_test) print(f最优参数: {grid.best_params_}) print(f测试集 F1: {accuracy_score(y_test, y_pred):.4f})用class_weightbalanced是处理股票预测不平衡问题的关键参数。多数股票在多数交易日里涨跌幅都在 ±0.5% 以内如果按涨/跌二分类正负样本基本接近五五开但如果按「大涨/大跌/震荡」三分类震荡类经常占 70% 以上。不平衡样本下如果直接用准确率做网格搜索的评分模型会倾向把所有样本都预测成多数类F1 能更真实反映少数类的识别能力。4.2 时间序列交叉验证网格搜索里的隐藏漏洞上面的代码里有个隐藏问题GridSearchCV默认使用 KFold 交叉验证会随机打乱样本这在时序数据上是错的。随机打乱意味着每折训练集中都混入了未来数据验证分数会被抬高。正确做法是使用TimeSeriesSplitfrom sklearn.model_selection import TimeSeriesSplit # 用时间序列交叉验证替代默认 KFold tscv TimeSeriesSplit(n_splits5) grid GridSearchCV( model, param_grid, scoringf1, cvtscv, n_jobs-1 )TimeSeriesSplit的机制是第一折用前 20% 训练、后 20% 验证第二折用前 40% 训练、后 20% 验证依此类推。这样每一折都严格保证训练数据在时间上先于验证数据。这种验证方式下的分数通常会比随机 KFold 低几个百分点但更接近真实表现。如果你在汇报时只说「准确率 80%」而没说清楚验证方式内行一听就知道是随机分割的虚高结果。4.3 让 LSTM 作为进阶方案什么时候值得换如果树模型已经能在测试集上拿到稳定高于阈值的 F1我通常不推荐再上 LSTM。但如果你的数据量超过两三年日线数据且希望捕捉更复杂的时序依赖LSTM 可以作为对比模型写进报告。下面是一个用 Keras 实现的最简结构import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout # 构造 3D 输入样本数, 时间步长, 特征数 def make_sequences(X, y, seq_len10): X_seq, y_seq [], [] for i in range(seq_len, len(X)): X_seq.append(X[i - seq_len:i]) y_seq.append(y[i]) return np.array(X_seq), np.array(y_seq) # 需要先对特征做标准化否则 LSTM 收敛很慢 from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) X_train_seq, y_train_seq make_sequences(X_train_scaled, y_train, seq_len10) X_test_seq, y_test_seq make_sequences(X_test_scaled, y_test, seq_len10) model Sequential() model.add(LSTM(64, return_sequencesTrue, input_shape(10, X_train.shape[1]))) model.add(Dropout(0.3)) model.add(LSTM(32, return_sequencesFalse)) model.add(Dense(1, activationsigmoid)) model.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy]) model.fit( X_train_seq, y_train_seq, epochs20, batch_size32, validation_data(X_test_seq, y_test_seq) )LSTM 有几个必调的参数seq_len是时间窗口长度我试过 5、10、20日线数据上 10 天窗口最常见return_sequencesTrue是为了堆叠第二层 LSTM单层时可删除Dropout层必须加否则训练几轮后损失下降很快但验证集不降这是典型的时序过拟合。这里的StandardScaler必须只 fit 训练集不能对整个数据集标准化后再切分否则又是数据泄漏。5. 股票预测项目的避坑指南回测里最常见的 5 个翻车现场5.1 未来函数泄漏正确率 95% 但是废模型现象用随机森林训练后测试集准确率高达 95%怎么看都不对劲。检查特征和标签后发现问题出在「当日收盘价」同时出现在特征和标签里。如果目标变量包含当日收盘价而特征矩阵也包含当日收盘价模型实际上是在读答案。原因构造特征时把目标日期的已知信息误当作特征输入了。最常见的是用 T 日的 close 预测 T 日的涨跌但 close 本身就是涨跌的结果。解决把特征统一前移。典型做法是用 T 日及之前的数据构造特征预测 T1 日的标签。即在代码里执行y df[target].shift(-1)然后丢弃最后一行。这个shift(-1)是股票预测项目里最重要的数据操作之一。5.2 紧贴训练集的测试集模型的记忆性幻觉现象测试集紧挨着训练集的最后一天测试集前几天的预测准确率明显高于测试集后半段。整体指标还行但越往后越差。原因树模型记住了训练集末尾的模式测试集初期与训练集尾部模式相似因而得分虚高。时间序列交叉验证下这个问题会被放大因为每一折的训练集都覆盖到验证集的前一天。解决训练集和测试集之间强制留出隔离带比如空出 10 个交易日不用于训练也不用于验证。这 10 天的数据相当于模型从未见过的过渡期能有效检验模型的泛化能力而不是短期记忆。5.3 技术指标参数不同导致数据不一致现象把数据从日线改为周线后模型表现骤降但训练代码完全没动。原因MA5、RSI 这些指标的窗口参数是按天数设定的改成周线后 MA5 等于 5 周均线含义完全变了。而股票数据缩采样后样本量也缩小数倍模型训练不充分。解决数据频率改变后必须同步调整特征参数同时重新做网格搜索。这个坑不深但容易忘层数多了以后很难排查。5.4 用未来数据填充缺失值现象数据集里某只股票停牌两天用fillna(methodffill)填充后没有报错但最后预测结果漂移严重。原因前向填充在前场数据缺失时是合理的但如果在切分之后才做填充填充值可能用到测试集的信息。更隐蔽的是用interpolate()线性插值插值结果同时参考了前后两个方向的数据。解决缺失值填充必须在数据集切分之前完成并且记录填充规则。停牌日的数据不能凭空造最简单可靠的方案是直接删除停牌日行只在连续交易日的序列上做特征计算。5.5 模型调参只盯训练集准确率现象手动调整随机森林的max_depth从 3 调到 10训练集准确率从 60% 涨到 90%于是认为模型变好了但测试集分数反而下降。原因这是标准的过拟合路径。max_depth增加时模型从「学习模式」变成了「背数据」训练集上的提升是背诵能力的体现不是预测能力的提升。解决每次调参后同时打印训练集分数和测试集分数观察两者的差距。当训练集提升而测试集停滞时立即停止增加复杂度。我在做这类项目时习惯把max_depth控制在 3 到 7 之间min_samples_leaf不小于 5这两个参数是控制随机森林过拟合最有效的旋钮。6. 把预测结果落地成一个可用的信号用序列收益曲线验证策略预测模型跑完不是终点。如果你要拿这个项目去答辩或写报告最有力的交付物不是准确率数字而是一张累计收益曲线。做法很简单把模型预测结果视为交易信号预测「涨」就持币买入预测「跌」就空仓。然后计算按此信号交易后的资金曲线与「买入持有」策略对比。# 生成交易信号预测涨则持有否则空仓 test_df[signal] y_pred test_df[daily_return] test_df[close].pct_change() test_df[strategy_return] test_df[daily_return] * test_df[signal] # 累计收益 test_df[strategy_cum] (1 test_df[strategy_return]).cumprod() test_df[benchmark_cum] (1 test_df[daily_return]).cumprod() # 计算年化收益与最大回撤 def max_drawdown(series): return (series / series.cummax() - 1).min() strategy_annual test_df[strategy_cum].iloc[-1] ** (252 / len(test_df)) - 1 benchmark_annual test_df[benchmark_cum].iloc[-1] ** (252 / len(test_df)) - 1 print(f策略年化收益: {strategy_annual:.2%}) print(f买入持有年化收益: {benchmark_annual:.2%}) print(f策略最大回撤: {max_drawdown(test_df[strategy_cum]):.2%})这段代码的输出有两个关键观察角度。第一策略年化收益不一定高于买入持有能高于就已经很好了因为股票整体是上涨的空仓本身就会跑输。第二最大回撤比年化收益更能说明模型的实际价值。机器学习模型在这类任务中的真正贡献不是每次都预测对而是在市场剧烈下跌时给出正确的不交易信号从而压低回撤。我在复盘这类项目时的最深刻感受是用机器学习做股票分析最大的实际收获不是赚钱策略而是建立了一套「永远区分训练表现和真实表现」的评估习惯。如果回测指标没有把交易成本、滑点和印花税算进去那再漂亮的曲线也只是纸面富贵。加一个固定的每日交易成本比如单边 0.1%你的曲线会立刻诚实很多。这也正是这类压缩包项目的价值所在——它逼迫你去面对很多远比模型本身更影响结果的东西希望这些经验能帮你在自己的项目里少走几步弯路少交一点学费。本文还有配套的精品资源点击获取
