ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

sklearn机器学习股票预测:从特征工程到时间序列验证的量化入门实战

sklearn机器学习股票预测:从特征工程到时间序列验证的量化入门实战 简介基于sklearn的机器学习股票预测项目专注金融股票价格回归预测面向金融数据分析爱好者、计算机相关专业学生以及需要完成毕设或课设的开发者。项目涵盖数据获取、清洗、特征构建、模型训练、结果评估等完整环节代码均调试通过可直接运行验证。压缩包共15个文件以4个Python脚本为核心分别对应不同处理流程与预测策略辅以csv样本数据、README操作说明、依赖包清单及项目管理配置整体约24.25MB结构清晰便于按需查阅。该项目已有287人学习下载适合作为机器学习入门和金融量化分析的实战模板。读者可通过源码理解sklearn中随机森林等模型的调用与参数调整学习如何设计股票预测实验并在此基础上扩展特征或更换模型用于课程设计、作业或初期项目演示。1. 基于 sklearn 的机器学习股票预测这可能是你最快上手量化入门的方式用 sklearn 做股票预测最常被问的一句话是「这个能赚钱吗」。我的回答是把它当成机器学习落地的练手项目你能在两天内走完数据清洗、特征工程、模型训练、评估这条完整链路这比它能不能赚钱更有价值。这个项目基于 sklearn 全家桶核心是特征构造和模型对比适合已经会 Python 基础、想接触量化分析但不知道怎么下手的开发者。很多人第一次跑完代码看到 0.6 的准确率就以为找到了圣杯实际上一轮牛市里你拿「昨天涨了今天就买」当策略准确率可能都超过 0.55。本文不教你玄学预测只讲清楚 sklearn 在这套流程里每一步怎么用、参数怎么调、坑在哪里。2. 数据准备与特征工程预测的上限在这里不在模型2.1 数据获取与清洗先解决停牌、缺失值和复权问题A 股数据最常见的坑是前复权、后复权不统一。如果用不复权数据做技术指标除权除息那天会出现一根假阴线直接把特征搞坏。常见做法是用tushare或akshare拉取前复权日线数据前复权保证历史价格连续指标计算不会出现跳变。另一个坑是停牌。停牌期间数据是缺失的pandas 默认填充会把停牌日变成上一交易日的重复值相当于给模型灌了几天一模一样的特征。我一般的做法是直接删除停牌日不填充保持时间轴的稀疏性。缺失值处理分两种指标计算产生的 NaN比如RSI刚起步时算不出来用bfill回填极少数剩余空值用中位数填充。清洗完的数据格式大概是date, open, high, low, close, volume, amount七列索引是date按升序排列。import pandas as pd df pd.read_csv(stock_daily.csv, parse_dates[date]) df df.set_index(date).sort_index() # 剔除停牌导致的缺失交易日 df df[df[volume] 0].copy() # 前复权价格重算tushare 的 qfq 字段直接给复权因子 # 这里演示手动复权逻辑用 adj_factor 修正 close df[close_adj] df[close] * df[adj_factor] / df[adj_factor].iloc[-1] # 计算收益率删除第一个 NaN df[return] df[close_adj].pct_change() df[return].fillna(0, inplaceTrue)adj_factor是复权因子把历史价格统一到当前价格口径这样 2020 年 6 月的价格和 2024 年 6 月的价格才有可比性。pct_change()计算的是相邻两日的百分比变化这是后续所有特征的基础。注意这里我用bfill不如直接fillna(0)干净——因为第一行本来就没有收益率硬填一个0代表「当天没涨没跌」对模型来说是一个中性信号不算错误信息。2.2 技术指标特征与标签设计先把「预测什么」定义清楚标签设计决定了整个项目的性质。你要预测的是「明天涨还是跌」还是「未来五天涨跌幅超过 3%」这两个问题的难度和模型输出完全不同。常见做法是预测「未来 N 日收益率是否为正」N 取 1、3、5 三档分别对应短线、中线、波段。我用默认参数跑下来N5 的预测稳定性明显好于 N1原因是单日涨跌受噪声干扰太大模型几乎学不到有效信号。特征方面不要一上来堆几十个技术指标。先用经典的五组动量MOM、RSI、MACD、布林带位置、成交量比率。每组指标用「当前值 过去 3 日均值 过去 5 日标准差」三个维度展开这样模型不仅能看见指标数值还能看见指标的波动趋势。import numpy as np # 标签未来 5 日收益率是否为正 df[label] (df[return].shift(-5) 0).astype(int) # 动量因子过去 5 日累计涨幅 df[mom_5] df[close_adj].pct_change(5) # RSI(14)用 ewm 近似避免引入额外依赖 delta df[close_adj].diff() gain delta.clip(lower0).ewm(alpha1/14, adjustFalse).mean() loss (-delta.clip(upper0)).ewm(alpha1/14, adjustFalse).mean() df[rsi] 100 - 100 / (1 gain / loss) # 布林带位置价格在带宽中的相对位置 ma20 df[close_adj].rolling(20).mean() std20 df[close_adj].rolling(20).std() df[bb_pos] (df[close_adj] - ma20) / (2 * std20) # 量比当日成交量 / 过去 5 日平均成交量 df[vol_ratio] df[volume] / df[volume].rolling(5).mean()shift(-5)是核心操作它把未来 5 天的收益率搬到今天这一行构造监督信号。千万注意构造标签时不能把未来数据当作特征喂给模型否则就是数据泄露。ewm(alpha1/14)是 RSI 的标准平滑方式adjustFalse表示从序列起点开始递归计算跟 TA-Lib 的算法一致。bb_pos计算的是价格在布林带中的相对位置大于 1 说明突破上轨小于 -1 说明跌破下轨。特征做完后统一做标准化。StandardScaler对树模型无所谓但对逻辑回归和 SVM 影响很大。我习惯把所有特征mom_5、rsi、bb_pos、vol_ratio等全部标准化到均值 0、方差 1留着scaler对象后面预测新数据时用。2.3 训练集与测试集划分时间序列不能用随机切分这是新手翻车率最高的环节。用train_test_split默认的随机切分会把 2020 年的数据和 2024 年的数据混在一起训练和测试模型相当于偷看了未来。正确做法是按时间顺序切分训练集在前、测试集在后。常见比例是 8:2但如果数据跨度超过 5 年可以按「前 80% 时间训练、后 20% 时间测试」来切。from sklearn.model_selection import train_test_split # 手动按时间顺序切分禁止 shuffle split_idx int(len(df) * 0.8) train, test df.iloc[:split_idx].copy(), df.iloc[split_idx:].copy() feat_cols [mom_5, rsi, bb_pos, vol_ratio] X_train, y_train train[feat_cols], train[label] X_test, y_test test[feat_cols], test[label] # 标准化只用训练集拟合 scaler测试集只做 transform from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)这里fit_transform和transform的区分是数据泄露的重灾区。fit会计算训练集的均值和标准差如果对测试集也做fit_transform相当于测试集的分布信息提前暴露给了模型测试分数会虚高。另外切分时注意索引边界iloc[:split_idx]是前 80% 的行时间上是连续的不会出现未来数据混入训练集的问题。3. sklearn 模型构建与参数调优三个模型的实战对比3.1 逻辑回归、随机森林、梯度提升的选型逻辑sklearn 里适合表格数据的模型很多但股票预测场景下我只推荐三个逻辑回归、随机森林、梯度提升GradientBoostingClassifier。逻辑回归是线性基线速度快、可解释性强适合判断特征方向和量级随机森林能捕捉非线性关系对异常值不敏感梯度提升在中小数据集上通常精度最高但调参复杂、容易过拟合。模型选型的第一原则是先跑逻辑回归建立基线再往上加复杂度。逻辑回归的准确率如果只有 0.52随机森林跑到 0.58别急着高兴——先看是不是数据泄露或者标签构造有问题。很多时候复杂模型的高分是「记忆」了训练集的噪声不是学到了规律。from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier models { lr: LogisticRegression(max_iter1000, C0.1), rf: RandomForestClassifier(n_estimators200, max_depth6, min_samples_leaf20, random_state42), gbdt: GradientBoostingClassifier(n_estimators200, max_depth3, learning_rate0.05, random_state42), } for name, model in models.items(): model.fit(X_train_scaled, y_train) train_acc model.score(X_train_scaled, y_train) test_acc model.score(X_test_scaled, y_test) print(f{name}: train_acc{train_acc:.3f}, test_acc{test_acc:.3f})C0.1是逻辑回归的正则化强度越小正则越强防止过拟合。随机森林的max_depth6、min_samples_leaf20是刻意收紧默认max_depthNone会让树长到纯叶节点在噪声数据上极易过拟合。GradientBoostingClassifier的learning_rate0.05配合n_estimators200相当于用更多更小的步子逼近目标比默认的0.1 100组合更稳。random_state42是固定随机种子保证每次跑出来的结果一致——这条对股票预测特别重要后面避坑章节会细说。3.2 评估指标准确率是骗人的要关注 AUC 和混淆矩阵股票涨跌预测里准确率是最没价值的指标。假设模型永远预测「明天不涨」在熊市里准确率可能超过 60%但这个模型毫无意义。我评估模型时固定看三个指标AUC、精确率、召回率。AUC 衡量模型排序能力0.5 等于瞎猜0.55 到 0.6 是常见水平0.65 以上已经算优秀。精确率衡量「预测上涨时到底对了几次」召回率衡量「真实上涨里抓到了几成」。from sklearn.metrics import roc_auc_score, classification_report y_prob models[gbdt].predict_proba(X_test_scaled)[:, 1] y_pred models[gbdt].predict(X_test_scaled) print(fAUC: {roc_auc_score(y_test, y_prob):.3f}) print(classification_report(y_test, y_pred, target_names[跌/平, 涨]))predict_proba返回的是每个类别的概率取第二列就是「上涨概率」。roc_auc_score需要的是概率而不是类别标签这点容易搞混——拿y_pred去算 AUC 会得到一个接近准确率的值含义完全不同。classification_report会同时输出精确率、召回率和 F1 值我习惯把target_names写成「跌/平」和「涨」方便直接看哪类错误更多。从实际经验看模型预测「涨」的精确率如果超过 0.55在 A 股已经算有参考价值但离实盘还差得很远。3.3 特征重要性分析哪些特征真正在起作用训练完树模型后特征重要性不是可选项而是必查项。sklearn 的树模型自带feature_importances_属性能告诉你模型到底在依赖哪些特征。多次运行项目后我总结出的规律是mom_5和vol_ratio的重要性通常排前二rsi次之bb_pos最不稳定——布林带位置在震荡市里几乎是噪声。import matplotlib.pyplot as plt importance models[rf].feature_importances_ feat_names feat_cols for name, imp in sorted(zip(feat_names, importance), keylambda x: x[1], reverseTrue): print(f{name}: {imp:.4f})特征重要性排序的作用有两个一是做特征筛选重要性低于 0.05 的特征可以考虑删除降低模型复杂度二是反推市场逻辑——如果vol_ratio重要性极高说明量能变化比价格位置更能预测短期走势这跟技术分析里「量在价先」的朴素认知一致。注意feature_importances_是训练集上的统计结果如果训练集不同重要性排序会变化不要把它当成固定结论。4. 避坑指南sklearn 做股票预测的五个典型翻车现场4.1 每次运行结果不一样模型分数忽高忽低现象同一个模型、同一份数据跑两次测试准确率差 3 个百分点有时 AUC 0.55有时 0.59。原因sklearn 里大部分模型有随机性。随机森林的bootstrap抽样是随机的梯度提升的初始值也是随机的train_test_split默认还会 shuffle。不固定随机种子每次训练都是不同的模型。解决统一设置random_state并在所有涉及随机的地方显式传入。常见做法是在逻辑回归、随机森林、梯度提升里都写死random_state42train_test_split里也传random_state42。如果项目里用到 NumPy 的随机操作再加一行np.random.seed(42)兜底。4.2 安装 sklearn 报错pip 装错包了现象pip install sklearn能装上但运行import sklearn时提示 deprecation warning或者某些 API 不可用。原因PyPI 上sklearn这个包名已经弃用真正的包名是scikit-learn。早期有些教程写pip install sklearn装到的可能是旧版本或兼容壳导致版本不对。解决从 0.24 版本后统一用pip install scikit-learn代码里import sklearn不受影响。如果已经装错先pip uninstall sklearn再装scikit-learn。版本低于 1.0 的旧项目建议升级新版 API 变化不大但GradientBoostingClassifier等模型的默认参数有调整。4.3 模型 AUC 高达 0.7实盘却稳定亏损现象回测曲线漂亮AUC 0.7但对接实盘模拟交易后连续亏损。原因百分之九十是数据泄露。最常见的泄露是「用未来数据构造特征」另一个是「标准化时用了全量数据的均值和标准差」。解决数据泄露的排查手段是把特征构造代码逐行检查看有没有使用shift(-n)的残留。标准化泄露的排查是确认scaler只在训练集上fit。更彻底的验证方法是做「滚动前推测试」每个时间点只用过去的数据重新训练然后预测下一个时间点这个流程下一章会写。4.4 特征出现 NaN模型直接报错现象ValueError: Input contains NaN, infinity or a value too large for dtype(float64)。原因技术指标计算初期会产生 NaN比如rolling(20).std()前 19 行都是 NaNpct_change(5)前 5 行也是 NaN。sklearn 不接受任何 NaN。解决特征构造完成后统一做一次dropna()或者用fillna(methodbfill)回填。注意删除行时要把标签列的对应位置一起删掉否则会出现 X 和 y 长度不匹配。推荐的做法是用df.dropna(subsetfeat_cols [label])一次性过滤。4.5 类别不平衡预测结果全是「跌」现象classification_report里「跌」类的召回率 0.9「涨」类的召回率 0.2模型基本在喊跌。原因股票数据里上涨和下跌的天数占比不是 50:50。震荡市里「跌/平」占多数模型学到的是「全预测跌」损失最小。解决先别急着做样本均衡真实场景里类别比例本身就是有效信号。如果一定要均衡用class_weightbalanced参数逻辑回归和随机森林都支持。梯度提升没有class_weight需要手动构造样本权重这是另一套流程建议先从前两个模型起步。另外也可以把标签改成「未来 5 日涨幅超过 2%」这种更稀疏的目标类别比例会自然改善。5. 验证模型是否真的有效时间序列交叉验证与模拟交易检验前面用固定时间切分验证模型只能说明「历史数据有规律」。要确认模型不是过拟合我一般会做两步验证第一步用TimeSeriesSplit做交叉验证观察各折的 AUC 稳定性第二步做一个最简单的模拟交易回测看模型信号能不能跑赢「买入持有」基线。TimeSeriesSplit和普通KFold的区别在于KFold是随机划分TimeSeriesSplit是前一段训练、后一段测试逐步后移。这样才能模拟「今天用过去预测未来」的真实场景。from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import roc_auc_score tscv TimeSeriesSplit(n_splits4) auc_list [] for train_idx, test_idx in tscv.split(X_train_scaled): X_t, X_v X_train_scaled[train_idx], X_train_scaled[test_idx] y_t, y_v y_train.iloc[train_idx], y_train.iloc[test_idx] model GradientBoostingClassifier(n_estimators100, max_depth3, random_state42) model.fit(X_t, y_t) auc_list.append(roc_auc_score(y_v, model.predict_proba(X_v)[:, 1])) print(fTimeSeriesSplit AUC scores: {[round(a, 3) for a in auc_list]}) print(fMean AUC: {np.mean(auc_list):.3f})n_splits4的意思是分 4 折每折用前 75% 训练、后 25% 验证。AUC 列表的方差能反映模型稳定性——如果四折的 AUC 分别是 0.58、0.52、0.49、0.55说明模型在不同市场环境下都不稳定不具备实盘条件。如果波动在 0.03 以内才考虑进入下一步。模拟交易回测我习惯写得尽量简单模型预测上涨概率超过 0.6 就买入低于 0.4 就卖出其余时间空仓。计算累计收益率时要注意交易成本A 股双边成本按 0.1% 算。这里的关键不是追求高收益而是对比「模型策略」和「买入持有」的曲线差异——如果模型策略连简单基线都跑不赢说明特征或标签设计有根本问题。# 用测试集做模拟交易信号 prob model.predict_proba(X_test_scaled)[:, 1] position (prob 0.6).astype(int) # 1持仓0空仓 # 按收盘价收益率计算策略收益 daily_ret test[return].values strategy_ret position[:-1] * daily_ret[1:] # 当日信号下个交易日生效 cum_return np.cumprod(1 strategy_ret) buy_hold_return np.cumprod(1 daily_ret[1:]) # 输出对比策略累计收益 vs 买入持有累计收益 print(f策略累计收益: {cum_return[-1]:.3f}) print(f买入持有累计收益: {buy_hold_return[-1]:.3f})position[:-1] * daily_ret[1:]这行是关键今天的信号只能作用于明天的收益直接让今天持仓吃今天收益就是未来函数。这是模拟交易里最容易犯的隐蔽错误。从那以后我每次写回测代码都强制走一遍「信号是否用了当日收盘后的信息」的检查——信号在收盘后产生最早只能下个交易日执行。做完整套验证流程你会发现 sklearn 股票预测项目的核心价值不是那个 0.6 的 AUC而是你完整走通了「数据 → 特征 → 模型 → 验证 → 回测」的闭环。这个能力迁移到其他分类任务里价值远大于在股市里的那点准确率。希望帮到你。本文还有配套的精品资源点击获取
返回列表