简介:一套基于人工神经网络、逻辑回归、随机森林、支持向量机等多种机器学习算法的股票价格预测Python实现,配有完整实验数据与可视化文件,主要面向计算机、人工智能等专业正在准备毕业设计或课程设计的学生,也适合想提升模型搭建能力的机器学习初学者。压缩包共20个文件、仅3.66MB,含6个Python源文件、5个CSV数据集、3个Excel表格、2个批处理脚本、2个Graphviz点文件和说明文档,代码模块覆盖数据预处理、模型训练、结果对比与预测输出。资源已有328人学习下载,曾作为毕业设计获96.5分高分,代码运行稳定、导师认可,可放心参考。下载后可获得可直接运行的算法源码、规范化训练/测试数据、预测结果表格与可视化决策树图像,便于快速复现实验并深入理解不同模型在股票预测场景下的表现。项目结构清晰,便于在此基础上扩展新算法或改进特征工程,也可作为答辩演示和项目立项初期的重要参考资料。
1. 一个听起来很美的股票预测项目:先搞清楚它到底能做什么
“基于多种机器学习算法进行股票价格预测”这个标题,看起来像是一条通往财务自由的捷径,但真正把源码跑完的人通常会有两种反应:一种是发现测试集表现远不如训练集,怀疑代码写错了;另一种是拿着漂亮回测曲线杀进实盘,然后被市场狠狠教育。实际上这种项目的技术难点从来不在算法本身,而在数据切片、特征构造和评估方式。这篇笔记会用一套完整流程,把数据预处理、三种模型的落地写法、统一评估和排错串起来。适合刚入门机器学习的学生、想转量化方向但还不熟悉 sklearn 的 Python 工程师,以及所有想搞明白“机器学习预测股票到底值不值得做”的从业者。
2. 数据预处理与特征工程:把K线整理成能被模型学习的表格
股票预测项目的第一个分水岭不是算法,而是数据表格。原始K线数据一般是 date、open、high、low、close、volume 六列,直接喂给模型几乎不会有好结果。原因很简单:价格是非平稳序列,五年前的收盘价和今天的收盘价数值跨度太大,模型很难从绝对值里学到可迁移的规律。所谓机器学习中的数据处理,放在股票项目里,本质上就是三件事:把价格换成收益率、构造有业务含义的特征、为每一行数据配好不包含未来信息的标签。这三件事做扎实,后面三个模型哪怕都用默认参数,结果也不会太离谱。
2.1 数据读取与检查:先排序,再看缺失
拿到股票数据文件之后,我一般先做两件看似不起眼的事:按日期升序排序,以及检查有没有缺失值。很多下载下来的数据并不是严格按照时间顺序排列的,尤其合并多只股票或拼接历史数据时,行顺序可能被打乱。模型不在乎行的顺序,但时间序列的切分和滞后特征非常依赖顺序,所以第一步必须排序。
import pandas as pd import numpy as np df = pd.read_csv("stock_data.csv", parse_dates=["date"]) df = df.sort_values("date").reset_index(drop=True) print(df.head()) print(df.info())这段代码里,parse_dates=["date"]会把日期列解析成时间类型,方便后续按时间索引切分;sort_values("date")保证整个数据框从过去到现在排列;reset_index(drop=True)把原来的行索引重置成连续的 0 到 N-1,避免切分时索引错位。df.info()能快速看到每一列有没有空值、数据类型对不对。股票数据里最常见的坑是停牌日成交量变成 0 或直接缺失,这一步先把它暴露出来。
2.2 特征构造:为什么用收益率而不是收盘价
很多人拿到数据后第一件事是把收盘价直接当特征,这是新手最容易踩的坑。价格是绝对值,十年前股价几十块,现在几百块,模型会倾向于把“价格高”和“未来涨”挂钩,这显然是错的。正确做法是把价格序列转换成收益率序列,再在这个基础上构造技术指标。收益率是相对变化,量纲统一,而且平稳性比价格好得多。
df["return_1"] = df["close"].pct_change() df["ma_5"] = df["close"].rolling(5).mean() df["ma_20"] = df["close"].rolling(20).mean() df["volatility"] = df["return_1"].rolling(20).std() df["volume_ma_5"] = df["volume"].rolling(5).mean()这里每个特征都有明确业务含义。return_1是每日收益率,代表短期动量;ma_5和ma_20是均线,代表短中期持仓成本,两者差值隐含了金叉死叉的信息;volatility用过去 20 日收益率的标准差衡量波动风险;volume_ma_5是成交量均线,用来观察放量和缩量。特征量不需要多,这五个已经能覆盖价格、动量、波动和成交量四个维度。再多加几个相关性高的指标,反而会让逻辑回归这类模型产生多重共线性问题。
2.3 标签定义:用 shift 构造未来 5 日收益率
标签是整个项目里最需要小心的地方。预测目标不是“明天收盘价是多少”,而是“未来 5 天的收益率是多少”。用shift(-5)把未来第 5 天的收盘价挪到当前行,与当前收盘价做除法得到收益率标签。这样每一行数据的特征都只用到当天及之前的信息,而标签用的是未来信息,训练时模型就是在学“根据现在预测未来”。
horizon = 5 df["label"] = df["close"].shift(-horizon) / df["close"] - 1 df["direction"] = (df["label"] > 0).astype(int) df = df.dropna() df = df.iloc[:-horizon]shift(-horizon)是标签构造的核心,正数 shift 是取过去,负数 shift 是取未来。最后两行代码必须做:dropna()去掉前面 pct_change 和 rolling 产生的空值;iloc[:-horizon]去掉最后五行,因为最后五天的未来收盘价不存在,label 是空值。direction是把连续收益率转成二值涨跌标签,逻辑回归这种分类模型要用它。这里建议把标签也打印出来看看分布,如果涨跌比例接近 1:1,说明数据没有明显偏差。
2.4 时间顺序切分:训练集永远不能包含未来
分类问题的常规做法是train_test_split随机打乱数据,但股票预测坚决不能这么干。随机打乱会把 2020 年的数据和 2023 年的数据混在一个训练集里,测试集里的“未来信息”已经通过随机抽样混进了训练过程,评估结果会虚高。正确做法是严格按时间顺序切分,前 80% 做训练,后 20% 做测试。
split_idx = int(len(df) * 0.8) train_df = df.iloc[:split_idx] test_df = df.iloc[split_idx:] feature_cols = ["return_1", "ma_5", "ma_20", "volatility", "volume_ma_5"] X_train_raw = train_df[feature_cols].values y_train = train_df["label"].values X_test_raw = test_df[feature_cols].values y_test = test_df["label"].values这里我把数据拆成_raw和后缀,因为随机森林不需要标准化,可以直接用原始特征;而逻辑回归和神经网络需要标准化。标准化的正确顺序是:先切分,再在训练集上fit标准化器,最后用同一个标准化器transform测试集。把标准化器放在切分之前拟合,测试集的均值方差信息会被训练过程偷看,后面避坑章节会专门说这个问题。
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train = scaler.fit_transform(X_train_raw) X_test = scaler.transform(X_test_raw)到这里,一张干净的、带标签的、严格按时间切分的特征矩阵就准备好了。接下来三种模型会分别在这套数据上训练和评估。
3. 三套模型实现:逻辑回归、随机森林与人工神经网络的落地写法
三种算法放在同一个项目里,不是为了展示 API 调用,而是为了让读者直观感受不同模型对同一份金融数据的拟合方式和结果差异。逻辑回归是线性模型,假设特征与标签存在线性关系;随机森林是树模型集成,能捕捉非线性但没有外推能力;神经网络是非线性拟合器,拟合能力强但更容易过拟合。在股票这种低信噪比数据上,三者的表现往往会给出完全不同的启发。
3.1 逻辑回归:预测涨跌方向比预测价格更稳
逻辑回归严格来说是分类器,它的损失函数是 log loss(对数损失),模型输出的是样本属于某一类的概率。放在股票预测里,最常见、最合理的用法是把问题转换成“未来 5 天是涨还是跌”的二分类任务,用前面构造的direction列作为标签。价格预测这种回归任务,逻辑回归本身做不了,如果硬要回归,通常是用它的回归变体 Ridge 或者带 logistic 损失的线性模型。
from sklearn.linear_model import LogisticRegression clf = LogisticRegression(C=0.1, penalty="l2", max_iter=1000, random_state=42) clf.fit(X_train, train_df["direction"][:len(X_train)]) pred_direction = clf.predict(X_test)C是正则化强度的倒数,C 越小正则越强。金融特征之间相关性高,我一般从 C=0.1 起步,太大会让系数疯狂膨胀,太小又会把所有系数压到接近 0。penalty="l2"是默认的 L2 正则,能抑制过拟合。max_iter=1000是为了确保收敛,如果 sklearn 报收敛警告,优先加大迭代次数而不是调学习率。逻辑回归的预测结果是 0 和 1,评估时直接和测试集真实direction对比即可。
如果坚持要用逻辑回归做收益率回归,一个务实替代是换成 Ridge:
from sklearn.linear_model import Ridge ridge = Ridge(alpha=1.0) ridge.fit(X_train, y_train) pred_ridge = ridge.predict(X_test)Ridge 是带 L2 正则的线性回归,损失函数是 MSE 加正则项,在收益率预测上通常比逻辑回归硬编码成回归更靠谱。两段代码的区别在于标签:分类用direction,回归用label。这个选择取决于你最终是要做方向信号,还是要做收益率预测,两者在后续策略里的用法完全不同。
3.2 随机森林回归:树模型不需要归一化的底气
随机森林是 Bagging 集成思想,每棵树在随机抽样的子集上训练,最终结果取平均。它的优势在于能自动处理特征之间的非线性关系,而且不需要标准化,因为树模型的分裂只依赖特征值的相对大小和顺序,不受量纲影响。所以在下面这段代码里,我直接喂X_train_raw,没有用经过 StandardScaler 的数据。
from sklearn.ensemble import RandomForestRegressor rf = RandomForestRegressor( n_estimators=300, max_depth=8, min_samples_leaf=20, random_state=42 ) rf.fit(X_train_raw, y_train) pred_rf = rf.predict(X_test_raw)三个参数值得说清楚。n_estimators=300是树的数量,太少模型不稳定,太多训练时间变长但精度提升非常有限,300 是一个性价比合适的选择;max_depth=8限制单棵树深度,防止树把训练集里的噪声细节都记住;min_samples_leaf=20是我在金融数据上比较看重的参数,它强制每片叶子至少包含 20 个训练样本,相当于让树只学习大概率规律,忽略个别极端行情。金融数据信噪比极低,这三个参数往“约束模型复杂度”方向调,通常比往“提高拟合能力”方向调更有效。
随机森林训练完成后有一个额外收获:rf.feature_importances_可以直接输出每个特征的重要性。后面还会用它来判断哪些特征真正起作用。在低信噪比数据上,如果某个特征的重要性一枝独秀,先别高兴,换一段数据再验证一次,往往就露馅了。
3.3 人工神经网络:用 MLP 逼近非线性关系
这里用的人工神经网络是 sklearn 的 MLPRegressor,全称多层感知机回归器,是一种前馈神经网络。它的拟合能力比随机森林更强,但代价是更容易过拟合,尤其当输入特征少、数据噪声大的时候。MLP 对特征尺度极其敏感,所以必须使用前面标准化好的X_train和X_test。
from sklearn.neural_network import MLPRegressor mlp = MLPRegressor( hidden_layer_sizes=(64, 32), activation="relu", solver="adam", alpha=0.05, max_iter=500, early_stopping=True, random_state=42 ) mlp.fit(X_train, y_train) pred_mlp = mlp.predict(X_test)hidden_layer_sizes=(64, 32)表示两个隐藏层,第一层 64 个神经元,第二层 32 个。金融数据样本量通常只有几千条,两个小隐藏层已经足够拟合非线性关系,堆更多层反而容易过拟合。alpha=0.05是 L2 正则系数,控制权重的惩罚力度,在股票数据上我习惯从 0.01 到 0.1 之间网格搜索,默认的 0.0001 只适用于高信噪比数据。early_stopping=True会在训练过程中自动留出一部分训练数据作为验证集,一旦验证集误差不再下降就提前停止,这是对抗过拟合的有效手段。max_iter=500是最大迭代轮数,配合 early_stopping 使用。
MLP 的一个潜在问题是随机初始化带来的结果波动,所以random_state=42在神经网络上比前两个模型更重要。如果同一份数据每次跑出来结果差异很大,先检查随机种子是否固定。
4. 统一评估机制:三种算法凭什么放在同一张表上对比
模型都训练完了,接下来要回答一个核心问题:哪种算法更好?如果直接看训练集误差,神经网络大概率遥遥领先,但这是没有意义的,因为训练集拟合得好不代表预测未来表现好。统一评估机制的意义在于:用同一套指标、同一段测试数据、同一种验证流程,把三个模型放在完全公平的起跑线上比较。股票预测里最常用的评估维度有四个:回归误差、方向准确率、时间序列交叉验证结果和收益曲线的稳定性。
4.1 回归指标:MSE、MAE、R² 在收益率上的真实含义
回归任务的常规评估指标是 MSE、MAE 和 R²,但它们放在收益率序列上需要重新解读。收益率标签的数值通常在 -0.05 到 0.05 之间波动,所以 MSE 的值会非常小,看到 0.01 甚至 0.001 不要惊讶,这不代表模型有多好,而是标签本身量级小。R² 在金融领域达到 0.1 已经算有解释力,这和理工科回归里动辄 0.9 的体验完全不同。
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score def evaluate(name, y_true, y_pred): mse = mean_squared_error(y_true, y_pred) mae = mean_absolute_error(y_true, y_pred) r2 = r2_score(y_true, y_pred) acc = ((y_true > 0) == (y_pred > 0)).mean() print(f"{name}: MSE={mse:.6f} MAE={mae:.6f} R2={r2:.4f} Acc={acc:.4f}")evaluate函数把四个指标一次打完,方便三个模型逐一调用。注意这里的acc是方向准确率,计算方法是:真实收益率大于 0 且预测收益率也大于 0 的样本占比。这个指标后面章节会重点展开。MSE 和 MAE 反映预测值与真实值之间的数值差距,R² 反映模型解释了多少方差,在金融数据里这三个指标通常是“看看就好”的角色,真正决定模型能不能用的是方向准确率。
4.2 方向准确率:比 R² 更接近实战的指标
做股票预测的最终目的是辅助交易决策,而交易决策最关心的是方向:该不该买、该不该卖。方向准确率就是模型预测涨跌方向的命中率,它把连续收益率的预测误差压缩成一个二值判断,直接评估模型的实战价值。随机基线是 50%,但在实际行情里,由于手续费和滑点的存在,方向准确率必须明显高于 50% 才有可操作空间。
acc = ((y_test > 0) == (pred_mlp > 0)).mean() print(f"方向准确率: {acc:.4f}")如果方向准确率在 52% 以下,基本可以断定模型没有稳定信号;如果在 55% 以上,虽然看起来只比抛硬币好一点点,但在量化策略里已经值得进一步做回测验证。还有一个容易被忽略的反向用法:如果方向准确率显著低于 50%,说明模型稳定地预测错方向,把它反过来用,反而可能是一个有正向期望的信号。这不算玄学,是线性模型和部分树模型在特定行情下的系统性偏差。
4.3 Walk-Forward 验证:用时间窗口模拟真实预测
单次时间切分虽然比随机切分好,但只测试了一段历史区间,结论可能依赖这段区间的行情特征。Walk-Forward 验证的思路是:不断滑动时间窗口,先用过去的一段数据训练,再预测接下来的一段数据,然后窗口前移,再训练再预测。这样模拟了真实交易中“每隔一段时间用最新数据重新训练模型”的节奏,是对时间序列模型最可靠的评估方式。
def walk_forward(model_factory, X, y, train_window=400, step=100): y_trues, y_preds = [], [] for start in range(0, len(X) - train_window, step): end = start + train_window if end + step > len(X): break X_tr, y_tr = X[start:end], y[start:end] X_te, y_te = X[end:end + step], y[end:end + step] model = model_factory() model.fit(X_tr, y_tr) y_preds.extend(model.predict(X_te)) y_trues.extend(y_te) return np.array(y_trues), np.array(y_preds)model_factory是一个返回新模型实例的函数,确保每个窗口都从零开始训练,不会把上一个窗口的状态带进来。train_window=400大致相当于两年的交易日数据,step=100是每半年滚动一次。把三个模型分别传入这个函数,汇总每一段的预测结果,再调用一次evaluate,就能得出比单次切分可靠得多的结论。如果某个模型在单次切分上表现不错,但在 Walk-Forward 上大幅劣化,说明它过拟合了特定区间的行情。
调用方式如下:
y_true_wf, y_pred_wf = walk_forward( lambda: RandomForestRegressor(n_estimators=200, max_depth=6, random_state=42), X_train_raw, y_train ) evaluate("随机森林 Walk-Forward", y_true_wf, y_pred_wf)lambda在这里的作用是延迟创建模型,每次循环都调用一次工厂函数得到全新的实例。
4.4 模型对比表:同一份数据上的三种结果
把三种模型放进同一个evaluate函数后,会得到一张类似下面的对比表。以下是一份典型输出形态,不同股票、不同时间段、不同随机种子下数字都会变,关键是看三个模型之间的相对高低和数量级。
| 模型 | MSE | 方向准确率 | 备注 |
|---|---|---|---|
| 逻辑回归 | 0.0121 | 0.528 | 线性决策边界,稳定但上限低 |
| 随机森林 | 0.0115 | 0.547 | 非线性拟合,需控制深度 |
| MLP | 0.0133 | 0.514 | 拟合强但容易过拟合 |
这张表反映出金融数据里的常见格局:随机森林在多数情况下方向准确率最高,逻辑回归稳定但中庸,MLP 如果不仔细调参反而最差。原因在于 MLP 的拟合能力强,在低信噪比数据上更容易把噪声当成规律。如果换一段更长周期的数据,这个排序可能会变化,但“树模型相对稳健、神经网络需要更多调参约束”这个规律在大多数场景下成立。
5. 避坑清单:股票预测项目里最常见的五个隐蔽错误
这个章节是整篇笔记里最值得反复看的部分。下面五个错误几乎每个股票预测项目都会遇到,有些错误会让评估结果虚高到离谱,有些会让模型在实盘中完全失效。每条都按“现象 → 原因 → 解决”来拆,可以直接拿着对照自己的代码排查。
5.1 未来函数泄漏:回测 R² 接近 1,实盘直接翻车
现象:训练集 R² 高达 0.93,测试集也有 0.91,但拿着模型去做实盘预测,结果跟随机猜差不多。这种“回测惊艳、实盘翻车”的情况,第一嫌疑就是未来函数泄漏。
原因:特征或标签里混入了未来信息。最常见的有三种:一是rolling用了center=True,导致均值窗口里包含了当天之后的数据;二是用全体数据的均值、标准差去填充缺失值;三是对价格序列做全局归一化时把未来区间的统计量混了进来。模型在训练时偷偷看到了未来,测试时自然表现得像开卷考试。
解决:把特征构造里所有rolling和shift检查一遍,确保某一行特征只使用当天及之前的信息。以均线为例,最稳妥的写法是:
# 错误示范:rolling 带 center=True,窗口包含未来 df["ma_center"] = df["close"].rolling(5, center=True).mean() # 正确示范:先 shift 再 rolling,锁定历史数据 df["ma_5"] = df["close"].shift(1).rolling(5).mean()shift(1).rolling(5)的含义是:用昨天及之前 5 天的收盘价算均值,完全排除今天收盘价的干扰。这在实盘中更贴近现实,因为当天收盘价往往在收盘后才有准确值,而信号需要在盘中甚至盘前生成。凡是特征构造里有rollng、shift、fillna、StandardScaler的地方,都值得停下来想一个问题:这一行数据有没有偷看未来。
5.2 归一化泄漏:StandardScaler 放早了一个位置
现象:代码逻辑看起来没问题,用三种模型在同样数据上跑,逻辑回归的结果明显好于预期,但方向准确率只在 50% 左右摇摆。有人会怀疑是标准化出了问题。
原因:在切分训练集和测试集之前就对整个特征矩阵做了StandardScaler的fit。fit会计算全量数据的均值和标准差,测试集的分布信息在训练前就被模型“看见”了。这不是模型偷看未来答案,而是偷看了未来特征分布的统计量,同样属于数据泄漏。
解决:标准化器只能fit训练集,然后用它transform测试集。对比下面两段代码:
# 错误做法:先标准化再切分,scaler 见过测试集 scaler = StandardScaler().fit(X_all_scaled) X_all_scaled = scaler.transform(X_all_scaled) # 正确做法:先切分再标准化 scaler = StandardScaler().fit(X_train_raw) X_train = scaler.transform(X_train_raw) X_test = scaler.transform(X_test_raw)记住一个原则:任何需要从数据里学习参数的预处理步骤,都必须在训练集上完成拟合。常见的预处理还包括 MinMaxScaler、PCA、填充缺失值的均值 imputer,它们的fit对象都只能放在训练集上。
5.3 随机种子不固定:同一份代码跑出两套结果
现象:代码没改,数据没改,只是重启了一次运行环境,输出结果变了,有时方向准确率差了 2 到 3 个百分点。如果你正在做参数调优,这种随机波动会直接淹没真实的参数差异。
原因:随机森林有随机的样本抽样和特征选择,MLP 有权重随机初始化,逻辑回归虽然本身是确定性的,但依赖的优化过程也可能由于数据顺序影响迭代结果。没有固定随机种子,模型内部的随机性没法抑制。
解决:在所有引入随机性的地方明确指定 seed,并在记录实验结果时把 seed 一并写进日志。
import numpy as np np.random.seed(42) model = RandomForestRegressor(n_estimators=200, random_state=42) model = MLPRegressor(random_state=42) model = LogisticRegression(random_state=42)这里有个细节:np.random.seed(42)控制的是全局随机状态,模型参数里的random_state=42控制的是模型内部的随机状态,两者都设置才最稳妥。如果使用神经网络的early_stopping,sklearn 内部也会从训练数据里划分验证集,这个划分同样受随机状态影响。
5.4 直接预测价格序列:模型学到的是均值而不是规律
现象:模型输出的预测曲线非常平滑,几乎贴着历史价格曲线的走势,MSE 很小,但方向准确率只有 50% 出头。看起来预测很准,实际毫无交易价值。
原因:价格是强非平稳序列,今天的价格和半年前的价格相差很大,模型只要学会“预测值约等于最近几天的平均价格”,就能把 MSE 压得很低。这本质上是在拟合价格的惯性,而不是在预测未来的变化。
解决:把预测目标从“未来价格”改成“未来收益率”。收益率是相对变化,消除了价格绝对水平的影响,模型被迫去学习涨跌的驱动因素,而不是偷懒做均值外推。
# 不要这样构造标签:直接预测未来价格 df["label_price"] = df["close"].shift(-5) # 应该这样:预测未来 5 日收益率 df["label_ret"] = df["close"].shift(-5) / df["close"] - 1做回归评估时也只看收益率相关指标。如果一定要预测价格,就在收益率的基础上乘当前收盘价还原回去,但训练和评估始终用收益率。
5.5 数据文件里的停牌与缺失:用 0 填充会让你白忙一场
现象:训练过程中模型的波动率特征突然出现大量异常值,loss 不收敛,或者随机森林的预测结果在某段时间出现极端数值。检查数据后发现,数据文件里有一段时间的成交量是 0。
原因:停牌日没有交易,成交量是 0,收盘价可能保持前一天的值或者直接缺失。如果把 0 当成真实成交量,volume_ma_5会被拉低,volatility会异常跳动,模型被迫学习这些假的规律。
解决:对停牌日做针对性处理。比较稳的做法是删除停牌行,因为停牌期间没有交易,样本对预测未来的贡献有限;如果不想删行,就用前向填充把缺失的成交量填成上一交易日的值。
df = df.dropna(subset=["close", "volume"]) df["volume"] = df["volume"].replace(0, np.nan).ffill()replace(0, np.nan)先把 0 成交量转成缺失值,ffill()用最近一个有交易的日期的值填充。这套组合能有效避免停牌导致的垃圾特征。注意,这个处理顺序必须在构造volume_ma_5之前完成,否则均线还是会吃到 0 值。
6. 进阶玩法:滚动重训练与特征重要性验证
算法跑通、评估做完,只算完成了第一轮。想让这套项目真正可用,至少还要做两件事:滚动重训练和特征重要性稳定性验证。
滚动重训练解决的是模型“过时”的问题。市场特征是随宏观环境和交易结构变化的,用 2020 年数据训练出来的模型去预测 2025 年行情,效果自然会衰减。常见做法是每隔一段时间用最近的数据重新训练一遍,周期可以是每季度、每月或每 50 个交易日。下面这段代码实现了一个最小的滚动训练循环:
def rolling_predict(model_factory, X, y, train_window=400, retrain_every=50): preds = [] for i in range(train_window, len(X) - 5, retrain_every): model = model_factory() model.fit(X[i - train_window:i], y[i - train_window:i]) preds.append(model.predict(X[i:i + 1])[0]) return np.array(preds)逻辑很清晰:每次只取最近train_window行训练,预测当前时刻的下一个样本,然后窗口滑动retrain_every行再重新训练。把滚动预测的预测值和真实值放在一起对比方向准确率,比单次切分测试更能反映真实使用场景下的模型水平。
第二个验证手段是特征重要性的稳定性分析。随机森林训练后输出的feature_importances_只能说明在训练集上哪些特征更重要,但如果换一段数据这个排序就面目全非,说明模型依赖的特征只是特定行情下的巧合。我一般会把数据按年份切块,分别训练并输出重要性,观察排序是否稳定:
importances = pd.Series(rf.feature_importances_, index=feature_cols).sort_values(ascending=False) print(importances)如果volatility在第一段数据里重要性第一,在第二段数据里跌到倒数第一,这个特征就不值得信任。反向的启发是:如果在多数时间段内重要性都稳定的特征,即使模型整体表现一般,也值得继续深挖。
我自己最早做这个项目时也干过蠢事,看到一个特征重要性极高就兴奋地加仓调参,换一段数据就全露馅了。后来养成的习惯是任何结论都至少在两段不重叠的历史数据上验证一次,这个代价很小,但能过滤掉大量虚假的“规律”。股票预测不是造永动机,这套源码和数据真正的价值,是让你在低信噪比的数据里练出一套严谨的评估习惯,这个习惯比任何一个模型都值钱。希望帮到你。
本文还有配套的精品资源,点击获取