ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

sklearn股票预测实战:从特征工程到walk-forward验证

sklearn股票预测实战:从特征工程到walk-forward验证 简介这份资源是一套基于sklearn实现股票预测的机器学习项目源码面向计算机、人工智能、金融商贸等方向的在校学生与自学者可用于毕业设计、课程设计、作业提交或项目初期立项演示也适合作为入门机器学习实战的练手案例。压缩包共15个文件约24.25MB以4个Python脚本为核心配合6个xml配置文件、1个csv数据集、1个txt依赖清单及md说明文档覆盖数据获取、随机森林回归建模与结果输出等环节目录结构清晰便于按模块阅读与二次修改。目前已有287人学习下载代码经过完整测试运行成功后才上传答辩评审平均分达到96分。读者可从中获得一套可复现的股票预测流程、数据读取与特征处理思路、模型训练与评估脚本以及依赖环境配置参考适合在此基础上调整算法或扩展功能快速搭建自己的预测实验。1. 从一份 sklearn 股票预测源码包说起它到底能不能跑出可用信号很多人第一次搜「机器学习股票预测」心里想的其实是「有没有一份能直接跑的代码把历史行情喂进去第二天告诉我买还是卖」。这份基于 sklearn 的机器学习股票预测资源就是冲着这个诉求来的它用 Python 生态里最稳的 scikit-learn 做建模主力把股票历史数据整理成特征矩阵再套上分类或回归模型输出对未来涨跌的预测。它不碰深度学习那套重框架也不需要显卡一台普通笔记本就能把整条链路走通。适合谁一是刚学完吴恩达机器学习、想找个真实场景练手的人二是做金融商贸数据分析、需要快速验证因子有效性的人三是被「机器学习预测」这个词吸引、但被各种深度学习教程劝退的从业者。它解决的不是「精准预测明天涨停」这种玄学问题而是让你把数据清洗、特征构造、模型训练、回测评估这条完整流程亲手跑一遍知道每个环节的边界在哪。下面我按自己拆包复现的顺序把这份资源讲透。2. sklearn 股票预测的建模链路从原始行情到特征矩阵2.1 为什么选 sklearn 而不是上深度学习股票数据有个很现实的特点信噪比极低样本量看着大但去掉重叠窗口后有效独立样本并不多。深度学习模型参数动辄百万级在这种数据上极易过拟合训练半天还不如一个逻辑回归。sklearn 的优势在于模型轻、接口统一、调参直观线性模型、树模型、SVM 几行代码就能切换对比。常见做法是先用LogisticRegression或RandomForestClassifier建立基线确认特征里确实有信号再考虑要不要上更复杂的结构。这份资源正是按这个思路组织的先把基线跑通再谈优化。另一个理由是工程可控。sklearn 的Pipeline能把标准化、特征选择、模型串成一条流水线配合TimeSeriesSplit做时序交叉验证整个实验可复现。金融数据对「未来函数」极其敏感用错一次标准化顺序回测收益能虚高到离谱而 sklearn 的流水线机制恰好能帮你把这类错误挡在门外。2.2 数据获取与特征工程的落地步骤资源里的数据一般来自公开行情接口或本地 CSV。我一般先把数据整理成「日期 OHLCV」的标准格式再构造特征。核心特征分三类价格衍生类收益率、对数收益率、移动平均线偏离度、波动类滚动标准差、ATR、量能类成交量变化率、量价背离。下面这段代码是我复现时用的特征构造骨架import pandas as pd import numpy as np # 假设 df 含 date, open, high, low, close, volume 列已按日期升序 df df.sort_values(date).reset_index(dropTrue) # 1. 未来一期收益率作为预测目标注意 shift 方向别搞反 df[ret_next] df[close].shift(-1) / df[close] - 1 # 2. 当期及历史特征 df[ret_1] df[close].pct_change(1) df[ret_5] df[close].pct_change(5) df[ma5_bias] df[close] / df[close].rolling(5).mean() - 1 df[ma20_bias] df[close] / df[close].rolling(20).mean() - 1 df[vol_5] df[ret_1].rolling(5).std() df[vol_ratio] df[volume] / df[volume].rolling(5).mean() # 3. 二分类标签下一期涨为 1跌为 0 df[label] (df[ret_next] 0).astype(int) # 4. 去掉滚动窗口产生的空值和最后一行没有未来收益 df df.dropna().reset_index(dropTrue)逻辑说明shift(-1)是把明天的收益对齐到今天这样模型在 t 时刻只能看到 t 及之前的信息标签才是「未来」。参数上pct_change(5)的 5 代表 5 日动量rolling(20)对应约一个月的交易日这些窗口没有绝对标准需要按持仓周期调整。vol_ratio用 5 日均量做基准能捕捉放量异动。最后一行必须删掉因为它的标签是 NaN留着会污染训练集。2.3 时序切分与模型训练的关键参数股票数据绝不能随机打乱做交叉验证否则就是用未来数据预测过去。资源里用的是TimeSeriesSplit我复现时也保持这个做法。下面是把流水线和时序验证串起来的代码from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import TimeSeriesSplit, cross_val_score feature_cols [ret_1, ret_5, ma5_bias, ma20_bias, vol_5, vol_ratio] X df[feature_cols].values y df[label].values pipe Pipeline([ (scaler, StandardScaler()), # 树模型其实不强制但流水线统一处理更省心 (clf, RandomForestClassifier( n_estimators300, # 树的数量太少不稳太多变慢 max_depth5, # 关键正则股票数据深度超过 6 基本过拟合 min_samples_leaf50, # 叶子最小样本防止记住噪声 random_state42, n_jobs-1 )) ]) tscv TimeSeriesSplit(n_splits5) scores cross_val_score(pipe, X, y, cvtscv, scoringaccuracy) print(各折准确率:, scores) print(均值:, scores.mean())逻辑说明StandardScaler放在流水线里保证每折只用训练段统计量做标准化避免信息泄漏。max_depth和min_samples_leaf是控制过拟合的两个核心旋钮股票数据上我一般把深度压在 4 到 6 之间。TimeSeriesSplit的n_splits5表示把时间轴切成 5 段滚动前进每段训练集都在测试集之前。注意准确率别太当真涨跌样本不平衡时模型全预测「涨」也能有 50% 出头后面评估章节会讲怎么破。3. 避坑与排查股票预测模型最容易翻车的五个地方3.1 现象回测准确率 70% 以上实盘一塌糊涂原因最常见的是未来函数。比如先对全量数据做了StandardScaler().fit_transform再切训练测试或者特征里混入了当日收盘后才公布的信息。另一个隐蔽来源是标签用了close对close的当期比较而不是shift(-1)。解决所有预处理必须放进Pipeline切分用TimeSeriesSplit。写完特征后强制自查一遍每个特征在 t 时刻是否真的可得。我习惯在特征表里加一列date人工抽查几行确认没有用到 t 之后的数据。3.2 现象模型只预测一个类别准确率卡在 50% 附近原因涨跌标签本身接近五五开但经过dropna和特定时间段筛选后可能严重偏斜或者特征区分度太低树模型直接摆烂输出多数类。解决先打印y.value_counts()看分布。如果偏斜用class_weightbalanced或对少数类过采样。更根本的是检查特征把feature_importances_打出来如果所有特征重要性都差不多且很低说明这批因子没信号换因子比调参有用。3.3 现象换一段历史数据结果完全对不上原因股票市场风格切换频繁某段行情里动量因子有效另一段里反转因子有效。模型在单一时间段训练天然只适应那段分布。解决用滚动窗口训练比如每次用过去 500 个交易日训练、预测接下来 20 个交易日然后窗口前移。资源里如果只做了单次切分建议自己补上滚动回测这样得到的收益曲线才接近真实体验。3.4 现象cross_val_score报错或结果异常原因TimeSeriesSplit在样本量不足时前面几折训练集太小模型学不到东西或者X、y长度不一致通常是dropna后忘了同步重置索引。解决确认X.shape[0] y.shape[0]并在dropna后统一reset_index(dropTrue)。样本太少时把n_splits降到 3或改用TimeSeriesSplit(max_train_size...)控制训练窗口。3.5 现象训练很快但预测新数据时报特征数量不匹配原因训练时用了 6 个特征预测时传入的 DataFrame 少了一列或者列顺序变了。sklearn 的Pipeline不认列名只认数组顺序。解决把feature_cols定义成常量训练和预测都走同一个列表取数。更稳的做法是把特征工程也封装成函数输入原始行情、输出固定列顺序的矩阵从源头保证一致。4. 把预测结果变成可评估的信号指标、回测与阈值调优4.1 别只看准确率金融场景该盯的几个指标准确率在涨跌预测里参考价值有限因为样本不平衡和交易成本会吃掉大部分优势。我一般同时看这几个AUC 衡量排序能力召回率看抓到多少真实上涨精确率看预测上涨里有多少是真的。更贴近实战的是把预测概率转成信号后算累计收益和最大回撤。下面这段代码把模型输出转成简单策略并评估from sklearn.metrics import roc_auc_score, classification_report from sklearn.model_selection import train_test_split # 按时间切分前 80% 训练后 20% 测试 split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] pipe.fit(X_train, y_train) proba pipe.predict_proba(X_test)[:, 1] print(AUC:, roc_auc_score(y_test, proba)) print(classification_report(y_test, (proba 0.5).astype(int))) # 简单策略概率大于阈值就持有否则空仓 threshold 0.55 signal (proba threshold).astype(int) strategy_ret signal * df[ret_next].values[split:] cum_ret (1 strategy_ret).cumprod() print(累计收益:, cum_ret[-1] - 1) print(最大回撤:, (cum_ret / cum_ret.cummax() - 1).min())逻辑说明predict_proba取正类概率比硬分类多了调阈值空间。threshold0.55是我常用的起点意味着只在模型比较确信时才出手能过滤掉一部分噪声交易。strategy_ret用信号乘未来收益模拟持有期收益。注意这里没扣手续费和滑点真实回测要按双边千分之一到千分之三扣减否则收益虚高。4.2 阈值与持仓周期的联动调优阈值不是拍脑袋定的。持仓周期短比如 1 日噪声大阈值要调高到 0.6 以上才有点意思持仓周期拉到 5 日或 10 日信号更稳阈值可以降到 0.52 附近。我一般做个网格阈值从 0.5 到 0.7 步长 0.02持仓周期取 1、3、5、10看哪组在测试集上累计收益和回撤的比值最好。但要注意这种调优本身也会过拟合测试集所以最终还得留一段完全没碰过的数据做最终验证。4.3 特征重要性与模型可解释性树模型可以直接输出feature_importances_线性模型看系数。如果发现某个特征重要性异常高先别高兴回去查它是不是泄漏了未来信息。我踩过一次坑把「当日振幅」当特征结果它和标签高度相关后来发现振幅是用当日最高最低价算的而标签是次日收益看似没问题但振幅本身包含了当日收盘后的信息量在某些数据源里更新不及时就会引入偏差。稳妥做法是每个特征都问一句这个数在收盘前能不能确定。5. 进阶技巧用 walk-forward 验证把模型逼到接近真实单次切分的评估只能算「看一眼」真正能让你对模型有信心的是 walk-forward 滚动验证。做法是固定训练窗口长度每次向前滚动一步训练、预测、记录最后把所有预测段拼起来算整体表现。这样每个预测点都只用它之前的数据训练和实盘节奏一致。def walk_forward(X, y, ret, train_size500, step20): preds, truths, rets [], [], [] for start in range(0, len(X) - train_size - step, step): tr_end start train_size te_end tr_end step pipe.fit(X[start:tr_end], y[start:tr_end]) p pipe.predict_proba(X[tr_end:te_end])[:, 1] preds.extend(p) truths.extend(y[tr_end:te_end]) rets.extend(ret[tr_end:te_end]) return np.array(preds), np.array(truths), np.array(rets) preds, truths, rets walk_forward(X, y, df[ret_next].values) signal (preds 0.55).astype(int) cum (1 signal * rets).cumprod() print(walk-forward 累计收益:, cum[-1] - 1) print(walk-forward 最大回撤:, (cum / np.maximum.accumulate(cum) - 1).min())参数上train_size500约两年交易日step20约一个月调仓一次。这两个值决定了模型适应新行情的速度窗口越短适应越快但越不稳步长越小交易越频繁但成本越高。我一般会跑几组对比选累计收益和回撤都还能接受的那组。跑完 walk-forward 后如果累计收益还是正的、回撤可控才说明这套特征和模型有继续打磨的价值如果一滚动就亏那问题多半在因子本身不在模型。从那以后我每次拿到一份股票预测代码都强制先跑一遍 walk-forward再看单次切分的漂亮数字——顺序反了很容易被过拟合的曲线骗进去。希望这份拆解帮到你少走一段我当年踩过的弯路。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进