
简介针对股票价格预测这一金融与AI交叉场景这套zip资料提供了基于机器学习算法的实践框架适合数据科学初学者或金融分析人员参考。内容覆盖数据预处理、特征工程、模型选择、训练验证、评估调优及实际应用限制并提及线性回归、决策树、随机森林、支持向量机和LSTM/GRU等常见模型的应用思路有助于快速建立从历史行情数据到价格预测模型的整体认知。包内共2个文件含1个Python主脚本和1个文本/依赖说明文件压缩包仅45KB轻量易读当前已有441人学习下载具有一定参考热度。通过该资源可拿到一个完整的小型项目示例既能学习数据清洗、标准化、特征构造等前置处理方法也能了解模型训练、交叉验证与MSE/RMSE/R²指标评估的落地方式同时依赖说明便于快速搭建Python环境适合边读边练逐步掌握股价预测的基本流程与常见注意事项。1. 机器学习算法与股价预测为什么多数项目在验证集上翻车股票市场价格预测是机器学习算法落地金融场景里最吸引人也最容易翻车的方向。市面上大量类似“基于机器学习算法的股票市场价格分析及预测.zip”的项目包核心套路都绕不开三件事拿历史行情数据构造特征、用模型拟合未来收益、再用回测指标证明自己有效。听起来顺理成章但真按这套流程跑一遍就会撞上一个尴尬事实训练集上漂亮的准确率放到验证集里立刻缩水实盘模拟更是经常亏钱。问题往往不在模型复杂度而在数据泄露、幸存者偏差、市场风格漂移这些看不见的坑。这篇文章按一份典型股价预测项目的完整链路来拆——数据清洗、特征构造、算法选型、回测验证——把每一步能直接抄的代码和参数给出来也把那些让模型“看起来很强”的隐藏陷阱逐一挑明。适合正在复现这类项目的学生、刚入门的量化爱好者以及想快速评估“这个方向值不值得投入”的工程师——看完你能判断一份预测代码是真有逻辑还是纯粹在拟合噪声。2. 数据获取与预处理先让行情数据对齐再谈建模2.1 用 jqdata 拉取股票列表与日线行情做股票预测第一步不是建模是拿到一份干净、对齐、口径统一的数据。常见做法是用 jqdata 或 tushare 这类数据接口。以 jqdata 为例先拉取股票列表再批量取日线行情代码骨架如下from jqdatasdk import * import pandas as pd auth(your_mobile, your_password) # 拉取 A 股股票列表剔除退市和停牌风险较高的品种 stocks get_all_securities(types[stock], date2024-01-01) stocks stocks[~stocks[display_name].str.contains(ST)] stock_list stocks.index.tolist() # 获取平安银行的日线数据复权方式统一用前复权 df get_price(000001.XSHE, start_date2018-01-01, end_date2024-12-31, frequencydaily, fields[open, close, high, low, volume, money], skip_pausedTrue, fqpre) df.head()这段代码里types[stock]代表只取正股不含指数和基金skip_pausedTrue跳过停牌日因为停牌日的价格是僵硬的放进特征里只会给模型传递大量重复的无效信息fqpre把历史价格统一向前复权保证当下视角下价格连续。字段里money是成交额后面算量价特征时比单纯成交量更可靠。拉下来后第一件事是看数据量。A 股 5000 多只股票、7 年日线大概在 900 万行左右本地用 pandas 处理勉强能跑但建议先只用 300 到 500 只票做原型验证跑通再扩全量。全量时千万注意逐只get_price接口调用很慢建议循环里批量获取或者存成 parquet 落盘避免重复拉取。2.2 数据清洗与标签构造先对齐日期再算未来收益原始行情拿到后不能直接喂给模型。要做的清洗工作至少有三项去极值、填充缺失、构造标签。去极值这一步常被忽略但某些股票复权后单日涨幅能到 50%比如上市首日或除权异常这类极端值会让标准化后的特征严重偏斜。常见做法是分位数裁剪clip到 1% 和 99% 分位。标签构造是整个预测项目的灵魂。多数项目把“未来 N 日收益率”作为回归目标或者把“未来 N 日是否上涨”作为分类目标。这里必须强调一点构造标签时只能用未来数据不能混入当前时刻的信息。正确写法如下import numpy as np df[ret_future] df[close].shift(-5) / df[close] - 1 # 标签是未来 5 个交易日的收益率 df[label_up] (df[ret_future] 0.02).astype(int) # 分类版标签未来 5 日涨幅超过 2% 才算正例 # 去极值对单日收益率做分位数裁剪 df[ret_1d] df[close].pct_change().clip(-0.1, 0.1) # 填充缺失停牌导致的 NaN 前向填充再用 0 补齐头部 df[ret_1d] df[ret_1d].ffill().fillna(0)shift(-5)是灵魂操作——它把收盘价整体向上移动 5 行使当前行的ret_future对应的是未来第 5 天的收益而不是当天到第 5 天之间的滚动窗口。如果这里写错方向比如用了rolling(5).apply(...)再 shift就很容易造成前视偏差。标签阈值的选择直接影响任务难度。2% 这个阈值在 A 股市场里5 天内涨跌超过 2% 的比例大约在 40% 到 55% 之间类别算基本均衡模型比较容易收敛。如果设成 5%正例比例可能降到 15% 以下分类器会倾向全部预测“不涨”准确率虚高但毫无意义。2.3 训练集与验证集的时间切分为什么不能随机打乱股价数据是强时序数据随机打乱再切训练集和验证集的做法会直接毁掉模型的泛化能力。原因在于随机切分会让模型“看到”未来——训练集里某天的样本其特征可能包含前一天的信息而验证集里恰好有相邻日期特征分布高度相似模型实际等于开卷考试。正确做法是按时间顺序切分# 按日期切分训练集 2018-2022验证集 2023-2024 train_end 2022-12-31 valid_end 2024-12-31 train_mask df.index train_end valid_mask (df.index train_end) (df.index valid_end) train_data df[train_mask] valid_data df[valid_mask]这里还有一个细节如果同时用多只股票训练切分必须基于日期而不是基于股票代码。也就是说所有股票 2023 年之前的样本进训练集2023 年之后的进验证集而不是某几只股票全量进训练集、另外几只进验证集。因为股票之间的相关性极高按股票切分会让模型学到“某只股票的行情模式”换个股票就完全失效属于变相数据泄漏。切分完成后务必检查验证集时间范围内是否存在数据断层。如果某段行情因停牌、退市造成大量缺失模型的预测区间就不完整回测指标会失真。这块没有捷径只能靠数据检查脚本把每个样本日期上的覆盖度打印出来确认覆盖率在 95% 以上。3. 特征工程不是特征越多越好而是每一步都要对齐时序3.1 从量价数据里构造技术指标特征特征工程决定预测效果的上限模型只是逼近这个上限。在股票价格预测这类项目里最常用的是技术指标特征——均线、动量、波动率、量价配合度四大类。以均线特征为例常见做法是计算 5 日、10 日、20 日、60 日均线然后转为价格比率形式而不是直接用均线绝对值因为绝对价格跨股票不可比。def add_technical_features(df, win_list[5, 10, 20, 60]): df df.copy() close df[close] for w in win_list: # 均线比率当前价格相对均线的偏离度 df[fma_{w}_ratio] close / close.rolling(w).mean() - 1 # 动量过去 w 日累计收益率 df[fmom_{w}] close.pct_change(w) # 波动率过去 20 日收益率的年化标准差 df[vol_20] df[ret_1d].rolling(20).std() * np.sqrt(252) # 量价配合成交额变化率与价格变化率的比值 df[amount_ratio] df[money] / df[money].rolling(10).mean() - 1 # 价格位置当前收盘价在近 60 日高低区间的位置 high_60 df[high].rolling(60).max() low_60 df[low].rolling(60).min() df[pos_60] (close - low_60) / (high_60 - low_60 1e-8) return dfrolling(w).mean()是核心它默认用当前行及之前 w-1 行的数据计算均值。这里有个容易踩的细节如果直接用close.rolling(20).mean()作为特征在第 T 行这个值包含 T 日的收盘价等于提前用了当天信息这就是所谓“同期特征”。严格做法是再套一次shift(1)让特征只用截止到 T-1 日的数据。上面代码里ma_ratio和mom_w没有做这个 shift因为后续在样本构建时统一做一次全特征 shift这里保持模块纯粹。模型对特征数值尺度敏感随机森林、线性回归这些模型需要输入标准化。常见做法是 z-score 或用中位数标准化。注意必须在训练集上拟合标准化参数再转换验证集不能在全部数据上 fit否则同样是变相数据泄漏。3.2 用相关性过滤降低噪声特征技术指标构造完后特征维度容易膨胀到几十上百个。但金融数据信噪比极低多一个噪声特征往往比少一个有用特征更危险。常见做法是计算两两特征之间的 Spearman 相关系数对相关性高于 0.95 的特征对做去重保留。def drop_high_corr(features, threshold0.95): corr features.corr(methodspearman) upper corr.where(np.triu(np.ones(corr.shape), k1).astype(bool)) to_drop [col for col in upper.columns if any(upper[col] threshold)] return to_drop # 构造特征后执行过滤 feature_cols [c for c in df.columns if c.startswith((ma_, mom_, vol_, amount_, pos_))] drop_list drop_high_corr(df[feature_cols]) feature_cols [c for c in feature_cols if c not in drop_list] print(原始特征数:, len(df.columns), 过滤后:, len(feature_cols))为什么用 Spearman 而不是 Pearson股票特征大多不满足正态分布存在大量尖峰厚尾Pearson 相关性容易被少数极端值带偏Spearman 基于秩统计更稳健。阈值 0.95 是一个经验值留太紧比如 0.99同质特征大量冗余模型训练慢且容易过拟合留太松比如 0.8又会把本就不多的有效信号砍掉。这里的关键技巧是先从逻辑上保留最“干净”的特征——比如ma_20_ratio和mom_20本质上都反映 20 日趋势保留逻辑上更直接的那个即可。过滤完之后还要做一次缺失值检查。rolling(60)的特征在每只股票头部 60 行都是 NaN这在多股票拼接的数据集里会造成大量无意义样本。常见做法是直接丢掉每个股票序列头部 60 行而不是用 0 填充——因为头部填充值会让模型学到“刚上市 60 天内的股票行为”和成熟股票完全不是一个分布。3.3 样本组织把长时序切成可供模型学习的样本集清洗和特征构造完成后需要把多股票的多行时间序列组织成模型可消费的样本集。最常见的做法是“宽表堆叠”——所有股票、所有日期的特征都堆在一起每行是一个样本。这样做简单直接但明确丢失了时序结构。另一种做法是滑动窗口样本每条样本携带前 N 个交易日的特征序列适合喂给 LSTM 这类序列模型。def build_samples(df, feature_cols, seq_len20): samples_x, samples_y, timestamps [], [], [] # 按股票分组每组内部滑动窗口 for _, grp in df.groupby(code): grp grp.sort_values(date).reset_index(dropTrue) arr grp[feature_cols].values labels grp[label_up].values for i in range(seq_len, len(grp) - 5): samples_x.append(arr[i - seq_len:i, :]) samples_y.append(labels[i]) timestamps.append(grp[date].iloc[i]) return np.array(samples_x), np.array(samples_y), timestamps这里每个样本的 X 形状是(seq_len, n_features)Y 是第 i 天的分类标签。for i in range(seq_len, len(grp) - 5)中的左起始位置seq_len保证窗口不越界右结束位置减 5 是因为标签构造用了未来 5 日收益序列尾部 5 天的标签不是完整偏置的干脆丢掉。seq_len20意味着看过去一个月20 个交易日的特征来预测未来 5 日的涨跌方向。这个值不是越大越好——A 股市场风格切换周期通常在 60 到 120 个交易日超过 60 天的历史信息大概率是噪声。如果数据集是日线建议 seq_len 在 10 到 30 之间调参超过 60 基本只会增加训练时间而不会提升效果。组内排序是关键grp.sort_values(date)保证窗口按时间顺序滑动。如果不排序样本顺序错乱模型学到的是打乱后的伪序列预测能力归零。4. 算法选型与参数配置从线性回归到 LSTM 的实测对比4.1 为什么选这三个模型线性回归、随机森林、LSTM在十大机器学习算法里真正适合股票价格预测场景的其实只有三类线性模型、树模型、序列模型。它们代表了三条不同路线。线性回归可解释性最好作为性能基线随机森林或 XGBoost、LightGBM能捕捉非线性但容易过拟合LSTM 专门处理序列依赖代价是训练慢、调参复杂。以下三者对比能覆盖数据科学面试和实际项目里八成以上场景。模型类型优势劣势适合场景线性回归训练快、可解释、不易过拟合无法捕捉非线性基线对比、特征有效性验证随机森林处理非线性和交互特征、自带特征重要性噪声敏感、容易过拟合中低维度特征、样本量几万级LSTM显式建模时序依赖训练慢、调参复杂、可解释性差高频数据、窗口特征强相关的场景还有一个常被忽略的选型理由数据量。两三万条样本用随机森林最稳但如果你拉全市场 5000 只股票、5 年日线样本量轻松到百万级LightGBM 明显优于随机森林。如果只有几千条样本LSTM 几乎必然过拟合——序列模型需要大量数据来学习时序模式不像树模型可以靠剪枝控制复杂度。4.2 用线性回归建立基线跑通全流程再谈优化任何预测项目都应该先跑一个简单模型做基线。线性回归在股价预测里的价值不在效果而在帮你验证特征管线是否正确、标签是否泄漏、样本组织是否合理。如果线性回归的验证集表现接近随机那后面复杂模型大概率也是白搭。from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.metrics import accuracy_score # 把三维样本展平成二维方便喂给传统模型 X_train train_x.reshape(len(train_x), -1) X_valid valid_x.reshape(len(valid_x), -1) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_valid_scaled scaler.transform(X_valid) # C 值小 正则化强默认 1.0 在股票数据上容易过拟合 model_lr LogisticRegression(C0.01, max_iter500, random_state42) model_lr.fit(X_train_scaled, train_y) y_pred model_lr.predict(X_valid_scaled) print(f验证集准确率: {accuracy_score(valid_y, y_pred):.4f})C0.01代表很强的 L2 正则化这是有意的——金融特征噪声大宁可让模型偏保守也不能放开拟合。max_iter500防止因特征维度高导致收敛失败。如果验证集准确率落在 48% 到 55% 之间说明管线基本正确模型在猜与弱有效之间。如果准确率超过 60%大概率存在前视偏差——后面会专门排查。线性回归在股价预测里表现差是预期内的它的价值在于给出“随机水平线”。后续所有复杂模型都必须显著超过这个基线的表现否则就是无效复杂度。4.3 随机森林参数限制深度比增加树数量更重要树模型在金融数据上的核心矛盾是默认参数几乎必然过拟合。常见做法是把max_depth限制到 3 到 6 层、min_samples_leaf提高到 50 到 200牺牲训练集准确率换取验证集稳定性。from sklearn.ensemble import RandomForestClassifier model_rf RandomForestClassifier( n_estimators200, # 树的数量200 棵足够稳定 max_depth5, # 深度限制到 5防过拟合 min_samples_leaf100, # 叶节点至少 100 个样本强制模型学大规律 max_featuressqrt, # 每次分裂只随机看一部分特征 n_jobs-1, random_state42 ) model_rf.fit(X_train_scaled, train_y) y_pred_rf model_rf.predict(X_valid_scaled) print(f随机森林验证集准确率: {accuracy_score(valid_y, y_pred_rf):.4f}) # 输出特征重要性 top10判断哪些特征在起作用 importance pd.Series(model_rf.feature_importances_, indexwide_feature_cols) print(importance.sort_values(ascendingFalse).head(10))min_samples_leaf100看起来激进但在百万级样本下根本不算多——它保证每个叶子节点至少有 100 个样本投票显著降低噪声影响。对比默认值 1验证集会立刻看到从“虚高”到“真实”的回归。max_featuressqrt是随机森林的经典配置让每棵树只从特征子集里选分裂特征增强树间多样性降低相关性。特征重要性输出是一个极好的诊断工具。如果 top 特征里出现amount_ratio和短期动量说明量价逻辑有信号如果全是长期均线说明模型在捕捉慢趋势。如果重要性最高的特征是pos_60这类区间位置指标通常意味着市场处于趋势市而非震荡市后续要警惕风格切换风险。4.4 LSTM 序列建模窗口长度与正则化平衡LSTM 是标题里最可能被点名的模型。它建模方式是把 20 天特征序列作为时间步输入每一步对应一个交易日。关键参数有三个——seq_len窗口长度、units隐藏单元数、dropout丢弃率。股票数据噪声高建议从较小的网络开始import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model_lstm Sequential([ LSTM(32, input_shape(seq_len, n_features), return_sequencesFalse), Dropout(0.3), Dense(1, activationsigmoid) ]) model_lstm.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), lossbinary_crossentropy, metrics[accuracy] ) # early stopping 在验证集不再提升时自动停止防止训练到过拟合 callback tf.keras.callbacks.EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ) history model_lstm.fit( train_x, train_y, validation_data(valid_x, valid_y), epochs30, batch_size256, callbacks[callback], verbose1 )return_sequencesFalse表示只输出最后一个时间步的隐含状态这是分类任务的标准用法。units32是保守值股票预测通常不需要大网络——更深的 LSTM 会记住训练集的噪声模式在验证集上加速衰减。Dropout(0.3)放在 LSTM 输出之后随机丢弃 30% 的隐含状态输出强迫网络不依赖单一时间步的信息。EarlyStopping的patience5意思是连续 5 个 epoch 验证集 loss 不下降就停止并回滚到最优权重。这是避免 LSTM 过拟合的最后一层保障。股票数据训练时验证集 loss 通常在前 3 到 5 个 epoch 就触底之后一路反弹——如果你发现 30 个 epoch 还没触发 early stopping大概率是验证集存在数据泄漏模型在靠“抄答案”刷分。LSTM 训练时间长尤其在百万级样本上。这里提供一个实际参数如果数据集是 200 只股票、5 年日线样本量约 25 万条batch_size256时每个 epoch 大约需要 1 到 3 分钟30 个 epoch 控制在一小时内。如果单轮超过 5 分钟优先检查seq_len和units是不是设得过大不建议盲目上 GPU——多了几十秒的传输开销反而拖慢小任务。5. 常见问题与避坑排查股价预测项目最容易死在哪5.1 前视偏差验证集准确率超过 60% 时的第一嫌疑现象所有模型在验证集上的准确率都显著高于 55%甚至超过 70%但把预测结果画成曲线发现收益曲线在关键拐点处与真实行情几乎重合。原因特征或标签构造里提前混入了未来信息。最典型的三个一是标签构造时用了shift(-n)但特征没有相应做滞后处理模型间接“看到”了未来价格二是标准化在全量数据上拟合把验证集的分布信息泄露给模型三是特征里的 rolling 窗口没有排除当前行比如close.rolling(5).mean()混入了当天收盘价等于把未来信息借道当前价格送进模型。解决把特征构造和标签构造放在同一函数里统一对特征做shift(1)滞后标准化只用训练集拟合验证集和测试集都调用scaler.transform用tslearn或sklearn的TimeSeriesSplit做交叉验证每次切分后重新计算特征与标签。排查最快的方法是拿一个“未来函数”特征比如未来 5 日收益率本身喂进随机森林如果验证集准确率直接逼近 99%那说明整个管线的时间切分逻辑全部失效。5.2 幸存者偏差回测有效实盘模拟失效现象模型只对当下还存在的股票做预测回测很漂亮但按信号实盘模拟时大量买入的股票退市或长期停牌亏损远超预期。原因数据获取时只选了当前仍在交易的股票名单已经退市的股票被排除在样本之外。模型只见过“活下来的强者”学不到那些前期上涨、后期崩盘股票的完整模式。解决数据获取阶段就用历史时点快照——例如拉取 2018 年 1 月 1 日时点的全部股票列表包含之后退市的标的。jqdata 的get_all_securities(date...)支持时点查询需要按年份分段拉取再拼接。如果数据源不支持历史名单至少要过滤掉上市不足一年的新股并把退市股票在退市前的行情保留到训练集里让模型见过“完整的生命周期”。5.3 复权口径混乱不同股票不可比同一股票时间序列断裂现象特征图里某只票的价格在除权日出现断崖式跳空波动率特征随之异常高模型在这些时间点给出极端预测。原因混合使用了前复权和后复权数据。前复权价格以最新价为基准回溯调整新股上市早期价格可能被调整为负数或接近 0后复权价格以最早价为基准近期价格可能膨胀到几百元。不同股票、不同时间段混用量价特征就没有横向可比性。解决全流程统一采用前复权数据并在数据加载阶段把复权标记写入字段名防止中途混淆。对成交量特征检查是否存在因为送股导致的单日成交量翻倍现象必要时用成交额替代成交量——成交额不受股本变动影响是更稳健的量能指标。5.4 市场风格漂移旧模型预测新行情自然失效现象用 2015 到 2020 年数据训练的模型放到 2023 到 2024 年验证准确率从 53% 滑落到 48%——不是模型坏了是市场风格换了。2017 年大盘蓝筹行情下有效的趋势特征在 2021 年成长股行情里变成反向信号。原因股票市场是非平稳时间序列统计特征随时间变化。任何固定训练窗口的模型有效期都有限。解决建立滚动再训练机制比如每 3 个月用最近 18 个月的数据重新训练一次模型在验证集上确认新模型效果没衰退再切换上线。模型更新频率也是调参的一部分——常见做法是把“训练窗口长度”和“再训练周期”作为超参数一起做网格搜索而不是一次训练终身使用。5.5 类别不均衡准确率虚高的假象现象预测准确率 85%但只看预测为正类的样本发现全是“预测为负”的贡献——模型把所有样本都判为下跌因为下跌样本占比本来就有 60% 以上。原因分类阈值默认 0.5但正类占比远低于 50% 时模型会倾向输出全部为占多数的类别。解决训练时给少数类分配更高权重class_weightbalanced评估时用 F1 分数、AUC 或 RankIC 替代准确率。注意这里 RankIC 更重要——它计算预测值与未来真实收益之间的 Spearman 相关完全不受类别均衡影响更接近真实预测能力的评估。实践中只要 RankIC 的绝对值超过 0.03 就说明模型有一定预测力超过 0.05 就是很强的信号。6. 回测与验证把模型预测变成可决策的信号股价预测的最终验证方式不是更复杂的模型而是严格的时间序列回测。训练集和验证集都跑完后还要留出一段“模拟未来”——比如把 2024 年整年当作实盘模拟期让模型在“不知道未来”的情况下滚动预测。# 滚动预测模拟用过去 1 年数据训练预测未来 5 日方向 def rolling_predict(df, feature_cols, train_days252): df df.sort_values(date) result [] for start in range(train_days, len(df) - 5, 20): train df.iloc[start - train_days:start] test df.iloc[start:start 20] # 每年重新训练一次模型模拟定期更新 model RandomForestClassifier(max_depth5, min_samples_leaf100) X_train train[feature_cols].values model.fit(X_train, train[label_up].values) # 预测未来 20 天的方向概率 prob model.predict_proba(test[feature_cols].values)[:, 1] result.append(prob.mean()) return np.array(result)这段代码展示的是滚动预测的大致结构。train_days252是过去一年交易日的经验值每 20 个交易日滚动一次每次只用未来数据预测模拟真实部署时“模型需要定期重训”的节奏。这里predict_proba的输出比predict更有价值——概率值保留了排序信息可以用阈值动态调整交易信号强度。最后要落在三个问题上模型的预测有效周期是多长、换手成本吃掉多少收益、最大回撤是否在可承受范围内。第二个问题尤其实际——如果预测周期是 5 天而模型实际只在第 1 天和第 2 天有效那交易频率就要调整到 2 天一次否则频繁交易产生的冲击成本和手续费会把策略收益全部吞掉。我的一般习惯是任何预测模型的实盘验证至少跑 3 个月模拟盘记录每天的预测和真实收益对比而不是只看回测曲线——这学费交过不少次希望帮到你少走这一步弯路。本文还有配套的精品资源点击获取