ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

机器学习股价预测源码解析:特征工程、模型选型与回测防过拟合

2026/9/13 4:36:43 拓冰建站 浏览量
机器学习股价预测源码解析:特征工程、模型选型与回测防过拟合 简介一套基于机器学习算法的股票价格分析与预测源码包面向计算机、人工智能、大数据、数学、电子信息等专业正在进行课程设计、期末大作业或毕业设计的学生也适合对金融量化方向感兴趣、具备一定Python基础的学习者对照调试与二次开发。压缩包内共有2个文件其中1个为Python主脚本另1个为运行所需的依赖清单txt整体大小仅45KB结构紧凑便于快速浏览核心逻辑与安装环境。项目代码已经过严格调试下载后即可直接运行目前已有298人学习使用。通过研读这份脚本可以了解股票数据读取、特征构造、模型训练与结果预测的基本流程虽然体量小但胜在完整清晰适合作为课程入门或毕业设计的起点模板在此基础上替换数据源或改进算法策略即可拓展成更完整的量化分析实验。1. 股价预测的机器学习源码落点不在算法而在工程链路“基于机器学习算法的股票市场股票价格的分析及预测源码.zip”这类压缩包在技术社区里一直有热度下载的人一半想抄作业另一半想看看别人怎么把机器学习算法落到金融时序上。先说结论这个标题覆盖的是一条完整链路——数据采集、特征工程、算法训练、回测评估、源码工程化——模型本身只是其中一小块。真正的难点不在“用哪种机器学习算法”而在标签怎么切、特征怎么构造、回测怎么防止前视偏差。打算复现同类项目的开发者可以从下面几段内容里拿到可运行的最小代码骨架、每个环节的参数取舍以及跑通之后才会意识到的坑。2. 行情数据获取与特征工程机器学习股票分析的第一步2.1 行情数据源选择与本地化存储做A股或美股的日线级预测数据源常见有三类免费开源接口akshare、tushare、yfinance、付费终端导出的CSV、自行维护的数据库快照。对这类“源码.zip”项目来说最稳妥的做法是让代码同时支持CSV读取和在线拉取——演示和教学场景下离线CSV能保证任何机器上结果可复现在线接口适合验证时顺手拉取最新行情。提示在线拉取容易出现缺数、复权不一致等脏数据问题。下载之后先落一份本地快照后续所有实验都基于快照运行否则接口版本更新或网络异常会导致结果无法复现。一个最小可用的行情数据集至少包含六个字段date、open、high、low、close、volume。这里要特别区分复权方式如果拿到的是未复权数据除权除息日会出现价格跳空技术指标会把这些跳空误判为真实行情常见做法是先把全量历史数据统一处理为前复权再去计算指标和标签。前复权的缺点是早期价格会随最新除权不断变化所以原始未复权数据段要保留供重算使用。# data_loader.py import pandas as pd def load_price_frame(csv_path: str) - pd.DataFrame: df pd.read_csv(csv_path, parse_dates[date]) df df.sort_values(date).reset_index(dropTrue) # pct_change 依赖时间升序排序必须先于一切衍生计算 df[return_1d] df[close].pct_change() return df.dropna(subset[return_1d])pct_change()计算相邻交易日简单收益率天然跳过周末和非交易日不需要手工补零。这里先排序再算收益顺序不能换否则收益率会被错误配对。2.2 技术指标特征的构造与窗口参数技术指标是这类项目中最常见的特征来源。我一般会从三个角度组织特征价格衍生收益率、动量、移动平均偏离、成交量衍生量比、换手率、波动性衍生滚动标准差、ATR 近似值。参数选择上滚动窗口不要无脑用 20、60 这类“默认值”。先确定预测周期预测未来 5 天特征窗口至少 20 天以上预测次日5 到 10 天窗口更敏感。其次要警惕窗口重叠带来的样本依赖相邻两行的滚动均值高度相似模型看到的是重复模式评估指标会偏乐观。# feature_engineering.py import pandas as pd import numpy as np def build_features(df: pd.DataFrame) - pd.DataFrame: out df.copy() for w in [5, 10, 20]: out[fma_{w}] out[close].rolling(w).mean() out[fstd_{w}] out[close].rolling(w).std() out[fmomentum_{w}] out[close] / out[close].shift(w) - 1.0 out[volume_ratio] out[volume] / out[volume].rolling(20).mean() out out.replace([np.inf, -np.inf], np.nan).dropna() return outshift(w)是关键操作它保证第 t 天的动量只使用 t-w 到 t 的已知价格不含未来信息。volume_ratio用 20 日平均量的比值表达“今天放量还是缩量”。全部计算完成后统一dropna()会丢弃前 20 行对几千行的历史数据影响可忽略。同类型特征里最容易踩的坑是“指标计算顺序”——先做复权再做指标和先做指标再做复权结果完全不同。项目里要强制复权先行的流程否则训练集和验证集之间会混入复权口径不一致的特征值。2.3 标签构造与时序切分的硬约束标签构造有两种路线。分类路线预测未来 5 日涨跌方向上涨超过 0.5% 记为 1下跌低于 -0.5% 记为 0中间小幅波动丢弃或单独归类回归路线直接预测未来 5 日收益率再在后处理中按阈值转成买卖信号。分类任务的优点是评估直观、类别不平衡问题可以通过阈值调节回归任务的优点是能直接对接仓位管理和止盈止损计算。时间序列切分是这种项目“看起来能赚钱、一实盘就亏”的主要根源。train_test_split(random_state42)在这里不可用——相邻交易日的特征高度相关随机切分会让验证集中的样本在训练集里出现“近亲”指标虚高 10 个百分点以上。切分方式是否可用说明random_state 随机切分不可用相邻样本泄漏评估结果虚高按时间 8:2 一次切分可用但粗糙单一测试段无法覆盖牛熊切换滚动前推walk-forward推荐每段重新训练最接近实盘节奏在代码层面推荐走一个小的辅助函数# splitter.py def time_aware_split(df, train_ratio0.7, valid_ratio0.15): n len(df) train_end int(n * train_ratio) valid_end int(n * (train_ratio valid_ratio)) return (df.iloc[:train_end].copy(), df.iloc[train_end:valid_end].copy(), df.iloc[valid_end:].copy())注意这里返回三个副本避免后续dropna()或特征变换时修改到原始 DataFrame。验证集用于模型选择和早停测试集只在最终评估时使用一次。3. 机器学习算法选型与训练从线性基准到序列建模3.1 线性回归必须存在的下界基准这类源码里最常见的算法是一上来就上 LSTM这是一个方向性错误。正确步骤是先训练一个线性回归作为基线——线性模型在股价预测里通常表现平庸但它的平庸是有参照价值的如果线性模型测试集方向准确率接近随机水平说明特征和标签之间线性关系很弱此时换复杂模型必须警惕过拟合。最小实现如下。特征用上一章的 feature_df标签用未来 5 日收益率。# train_linear.py from sklearn.linear_model import Ridge from sklearn.preprocessing import StandardScaler from sklearn.metrics import mean_squared_error X_all feat_df[feature_cols].values y_all feat_df[future_5d_return].values train_end int(len(X_all) * 0.8) X_train, X_test X_all[:train_end], X_all[train_end:] y_train, y_test y_all[:train_end], y_all[train_end:] scaler StandardScaler().fit(X_train) X_train scaler.transform(X_train) X_test scaler.transform(X_test) model Ridge(alpha10.0) model.fit(X_train, y_train) print(test_rmse:, mean_squared_error(y_test, model.predict(X_test), squaredFalse))Ridge的alpha承担两个职责抑制技术指标之间的共线性压低单个特征的极端权重。金融数据的信噪比低alpha从 10 起步是常见做法比较稳的取值区间在 10 到 100。注意StandardScaler只在训练集上fit这是一个容易被忽视却至关重要的细节。3.2 树模型与集成随机森林和 XGBoost 的参数边界线性模型跑完基线下一步是随机森林或 XGBoost。随机森林的优势是几乎不用调参就能跑出一个可复现的分数适合做“复杂模型的及格线”。XGBoost 的优势在于能捕捉交互效应例如“量比超过 2 且 5 日动量为正”这类组合条件。调参时我重点关注四个参数而不是全部参数n_estimators500 到 1000 即可更多树未必更好max_depth3 到 5股价预测不需要深树深度超过 6 几乎必然过拟合learning_rate0.01 到 0.05金融数据用小学习率更稳subsample0.8 左右配合列采样共同控制随机性# train_xgb.py import xgboost as xgb model xgb.XGBRegressor( n_estimators800, max_depth4, learning_rate0.02, subsample0.8, colsample_bytree0.8, early_stopping_rounds50, verbosity0 ) model.fit( X_train, y_train, eval_set[(X_test, y_test)], verboseFalse )early_stopping_rounds50是最有效的一道过拟合闸门当测试集损失连续 50 轮不再改善训练就提前终止。这里容易犯一个错误——早停轮的评估集必须是按时间切分的验证集不能是与训练集有重叠的随机样本。最终模型训练完成后仍然要在从未参与早停判断的测试集上重新评估一次。3.3 LSTM 与序列建模窗口长度、步长和训练稳定性LSTM 在日线级别预测中属于“锦上添花”的组件。大多数日线信号已经能被特征工程表达LSTM 的边际价值在于捕捉最近 N 天的形态上下文例如“连续缩量后放量”这种短时序模式。实现 LSTM 之前要明确两个超参数。窗口长度seq_len20 到 60 之间比较合理对应一个月到季度级别的历史信息。采样步长stride每 5 天取一个样本能在保留序列特征的同时减少相邻样本的高度重叠也大幅降低训练时间。# train_lstm.py import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def build_sequences(X, y, seq_len30, stride5): xs, ys [], [] for i in range(0, len(X) - seq_len, stride): xs.append(X[i:i seq_len]) ys.append(y[i seq_len]) return np.array(xs), np.array(ys) X_seq, y_seq build_sequences(X_scaled, y, 30, 5) model Sequential([ LSTM(64, return_sequencesTrue, input_shape(30, X_scaled.shape[1])), Dropout(0.3), LSTM(32, return_sequencesFalse), Dropout(0.3), Dense(1) ]) model.compile(optimizeradam, lossmse)这里return_sequencesTrue让第一层 LSTM 输出完整的序列给第二层第二层return_sequencesFalse只输出最后一个时间步的隐状态。Dropout(0.3)是防过拟合的主力不是可选项。训练时保持numpy.random.seed和tensorflow.random.set_seed固定否则每次运行结果差异很大无法复现别人的实验。LSTM 收敛困难时先看前 10 个 epoch 的 loss 曲线如果完全不下降把学习率从1e-3降到3e-4如果在某个水平震荡不再下降说明容量够用优先调 dropout 或减小单元数而不是加层。4. 回测与评估体系让机器学习模型的预测经得起推敲4.1 评估指标要分误差和方向两个视角股价预测项目最常见的评估错误是只看 MSE 或 RMSE。这两个指标衡量预测值与真实值的绝对偏差但对方向判断正确率不敏感——模型在 60% 的时间里方向正确某一次大亏损就能把 MSE 拉得很高让人误判模型无效。正确做法是同时上报方向准确率Directional Accuracy。它的定义是预测为正且实际为正、预测为负且实际为负的样本比例。对交易决策而言方向准确率比 MSE 更贴近实际收益。# evaluate.py import numpy as np from sklearn.metrics import mean_squared_error def evaluate_model(y_true, y_pred): mse mean_squared_error(y_true, y_pred) direction float((np.sign(y_true) np.sign(y_pred)).mean()) # 简化信号预测值为正则持多头为负则空仓 strategy_returns np.where(np.sign(y_pred) 0, y_true, 0.0) cumulative np.cumprod(1 strategy_returns) return { mse: mse, direction_accuracy: direction, final_cumulative: cumulative[-1] }strategy_returns里使用了“非多即空仓”的简化规则。真实交易中还有手续费、滑点、涨跌停无法成交的限制回测至少要把手续费按单边万二到千一加进成本否则策略收益会被系统性高估。4.2 向量化回测与交易成本建模回测有循环逐日撮合和向量化两种写法。循环直观但是慢向量化快且简洁日线级别的策略用向量化完全足够。# backtest.py import numpy as np import pandas as pd def simple_backtest(df: pd.DataFrame, signal_col: str, cost_rate0.0005): df df.copy() df[position] np.where(df[signal_col] 0, 1, 0) df[position_change] df[position].diff().fillna(0) df[gross_return] df[return_1d] * df[position] df[cost] df[position_change].abs() * cost_rate df[net_return] df[gross_return] - df[cost] df[equity_curve] (1 df[net_return]).cumprod() return dfposition_change记录仓位切换点每次切换收一次手续费。return_1d表示“当天持有标的获得的收益率”position使用前一交易日的预测信号避免前视偏差。跑完回测除了看equity_curve的终值还必须看最大回撤一个回撤 20% 的策略与回撤 5% 的策略即使最终收益相同实际可交易性完全是两个等级。4.3 过拟合的三道闸门第一道是严格样本外检验训练集、验证集、测试集按时间顺序切三份测试集只使用一次。反复用测试集调参会把它变成验证集因此最后一段数据要在所有实验完成后才触碰。第二道是滚动验证把数据按年分段每次用前 3 年训练、下 1 年测试逐年向外滚动。如果某一年结果特别差通常不是模型的问题而是市场风格切换可以借此定位模型的能力边界。第三道是随机化标签测试把训练集标签随机打乱后重新训练理想的模型指标应当退化到随机水平。如果打乱标签后模型“依然优秀”说明特征和标签之间并不存在你期望的因果关系过拟合已经主导了结果。5. 从源码.zip到可复用工程目录组织、依赖管理与复现验证5.1 一个可维护的股票预测项目目录长什么样这类项目最普遍的硬伤不是模型不准而是换一台机器就无法复现。拆开一个“源码.zip”先看目录结构再找配置文件。常见的可维护结构包含四个模块一层配置data/ 存放原始行情和中间产物src/ 放数据、特征、模型、评估四个脚本根目录一个 YAML 配置文件加一个依赖清单。300 行代码全部堆在一个脚本里、没有任何函数划分的项目通常只能在作者本机跑通。requirements.txt建议锁小版本pandas 1.5.x 与 2.x 在日期解析行为上存在差异numpy 1.x 与 2.x 在部分数学函数上也有兼容性变化。先pip install -r requirements.txt而不是缺什么装什么。5.2 用 YAML 集中管理周期、特征和回测参数data: csv_path: data/raw/000001.csv target_horizon: 5 features: windows: [5, 10, 20] model: name: xgb max_depth: 4 learning_rate: 0.02 n_estimators: 800 backtest: cost_rate: 0.0005 initial_cash: 100000target_horizon同时影响标签构造和回测持有周期改这一个字段就能从“预测次日”切换成“预测周度”特征窗口和回测逻辑不用动。cost_rate按单边收费有人为了“看理想收益”把它设成 0回测曲线立刻变好看但这条路经不起实盘验证。一般建议底线设在 0.0003 到 0.0005。5.3 复现过程中最常出现的两个报错第一个报错是样本数量不一致。特征工程中shift()会引入 NaNdropna()的时机和位置一旦和标签构造错开特征矩阵和标签向量的长度就对不上model.fit()会直接抛出 “Found input variables with inconsistent numbers of samples”。排查思路很简单把特征矩阵和y放在同一个处理流程里在统一的位置执行一次dropna()。第二个报错是缩放器泄漏的静默问题。有些源码在全部数据上先fitStandardScaler 再做时间切分这样测试集统计量已经泄漏进训练流程报告出的指标必然虚高。正确写法是在训练集上完成fit再分别transform训练集和测试集。如果下载到的源码测试指标高得异常先查这一步。复现时建议只先跑通线性回归与 XGBoost 这条链路确认数据层和评估层无误后再考虑引入 LSTM。检验模型是否可信看两个数字验证集方向准确率是否稳定超过 52%以及打乱标签后的模型是否回落至 50% 附近。这两项检查通过之前再好看的回测曲线都不能作为实盘依据。本文还有配套的精品资源点击获取