
简介面向金融量化与人工智能学习者的XGBoost股价预测完整项目主要帮助开发者、数据科学初学者及相关课程设计者掌握基于机器学习的时间序列预测方法解决如何对未来若干交易日股票价格进行建模与预测的实操问题。压缩包共六个文件包含三个Python脚本一个负责历史行情数据获取与清洗一个实现XGBoost特征工程与模型训练预测另一个封装绘图或数据预处理等辅助功能另有两个文本文件分别提供使用说明和依赖清单以及一个示例结果Excel文件整体约18KB结构清晰适合快速阅读与二次扩展。已有245人学习下载。通过学习这份代码读者能直观看到从原始数据到预测结果的完整流程包括数据清洗、特征构造、模型训练与结果导出掌握XGBoost在金融时间序列预测中的关键细节结合附带文档和输出样例可快速验证模型效果并在此基础上进行参数调优或预测周期调整是一份轻量但实用的入门参考。1. 基于XGBoost预测n日后股价源码的具体问题n日窗口比模型本身更危险股价预测这类任务里XGBoost早已是量化圈默认的基线模型之一。原因很直白它能吞下几百个特征而不需要太精细的标准化训练速度快调参空间大到足够让每个团队调出“自己家的独特配方”。但拿到“基于XGBoost预测n日后的股价源码.zip”这种项目包时真正值得先审视的往往不是XGBoost的树个数和深度而是那个“n日”的标签是怎么构造的。多数人能复现出一个漂亮的训练集准确率却在实盘或滚动回测里溃败问题几乎都出在标签窗口的错位、未来信息的渗入和特征序列的对齐上。本文沿着“标签—特征—模型—验证—防泄漏”的顺序把这些环节逐个拆开给出可直接复现的做法和参数边界。适合已经会跑sklearn基础流程、想自己动手做完整时序预测管线的量化开发者和机器学习工程师也适合读完源码后想确认其逻辑是否严谨的审阅者。为了避免回头踩坑n日窗口的语义会贯穿全文它在大多数实现里不是“预测n天后的某天价格”而是持有期为n日的收益率标签。2. n日后的标签构造先解决“预测什么”再谈模型2.1 用shift(-n)生成未来n日收益标签的正确姿势常见做法是取第t个交易日收盘价 close[t]计算未来第n个交易日的收益率import pandas as pd import numpy as np df pd.read_csv(daily_prices.csv, parse_dates[date]) df df.sort_values(date).reset_index(dropTrue) n 5 df[fwd_ret] df[close].shift(-n) / df[close] - 1.0 # 两分类标签未来5日涨跌幅是否超过阈值 threshold 0.0 df[label_up] (df[fwd_ret] threshold).astype(int) df[target_reg] df[fwd_ret] # 去掉末尾无法构造标签的n行 df df.iloc[:-n]这就是“n日窗口”标签的最小实现。逻辑上close.shift(-n)把future的价格移动回当前行这样每一行的target_reg描述的是“从今天收盘持有n个交易日后卖出”的收益率。标签构造完毕后要丢弃最后无法完整对齐的n行否则模型会在训练时学到“接近末尾的行没有标签”这类无意义规律。回归任务直接使用fwd_ret二分类任务则把fwd_ret与0比较得到label_up。还可以用分位数把标签切成多分类比如把未来收益按历史分位数划分到高、中、低三档让XGBoost做多分类但二分类和回归依旧是最常用的两种形态。关键点是shift(-n)发生在任何特征工程之前。只要先建特征再shift以上字段就很容易不小心把未来n日窗口内的信息放进当前行的特征里这种泄漏在验证集上几乎察觉不到换到实盘就会塌。2.2 前复权、除权除息和“未来函数”的三种真实来源股价数据不经复权直接算收益率是大坑。分红送股当天价格会跳空比如10送10次日股价理论上减半如果不做复权处理一个长期持有的组合会被算成亏损。处理方式有三条tushare、baostock、akshare等数据源直接提供前复权价qfq若只有原始价用adjust函数自己按复权因子处理倘若是美股数据还需要注意拆股split的调整。用前复权价时有一个隐藏窗口问题前复权价格是“站在今天看历史”调整得到的数值历史某一天的复权因子用到了后续分红事件信息这在小样本回测里会制造微弱的未来信息。最严格的做法是使用point-in-time数据或逐日复权因子若拿不到至少应说明数据是后复权不复权预处理后的结果。未来函数还常常来自特征。滚动均线的错位、技术指标计算时使用了后续交易日的数据、特征标准化时混入了全样本统计量这些都是最常见的三类泄漏通道。确认特征列每个时间点的取值只依赖t时刻及之前的数据是XGBoost预测n日后股价源码里最值得审查的步骤。2.3 回归还是二分类按交易动作选标签回归标签保留完整幅度信息涨5%和涨0.5%在损失函数里贡献不同梯度。二分类只关心方向适合阈值明确、手续费敏感的策略。还有一种做法是三分法剔除|fwd_ret|较小的样本让模型只学“明确上涨”和“明确下跌”。比如去掉未来5日涨幅在-1%到1%之间的样本再去训练二分类模型信号会更稳定代价是样本量减少。这个思路里阈值大小直接关系到类别平衡度xgb二分类模型训练时通常配合scale_pos_weight或样本权重调整类别不平衡。3. 特征构造把价格序列变成XGBoost能消化的横截面3.1 从原始行情抽出来的三类基础特征XGBoost的输入通常是“每行一个样本、每列一个特征”的表格结构。股价预测中这个表格就是把时序数据横向展开。基础特征最少要包含收益类、价格位置类和波动类。# 假设df已按日期排序close为复权收盘价 df[ret_1] df[close].pct_change(1) df[ret_5] df[close].pct_change(5) df[ret_20] df[close].pct_change(20) df[ma_5] df[close].rolling(5).mean() df[ma_20] df[close].rolling(20).mean() df[price_vs_ma20] df[close] / df[ma_20] - 1.0 df[vol_20] df[ret_1].rolling(20).std() # 振幅日内价格波动比例 df[amp] (df[high] - df[low]) / df[close]这些特征每一行的取值都只包含当前及过去的数据且经过了“滚动窗口右对齐”的标准处理。pct_change(5)统计的是过去5日的累计收益率rolling(20)统计的是过去20日的均值和波动。注意pct_change和rolling天然自带shift效果构造时不要画蛇添足再套一层shift。换手率特征若有成交量vol和流通股本float_share可以计算turnover vol / float_share这是A股量价模型里非常有效的特征。计算时同样不能使用未来数据float_share若发生过变化应使用对应时间的流通股本口径。3.2 xgboost对空值的保护缺失来自窗口头部不来自预测目标滚动窗口在序列开头会产生大量NaN。多数人习惯用df.dropna()把前20行直接删掉这在序列足够长时没毛病但有一类问题值得注意如果想预测的“n日后”样本本身就处在NaN区边缘dropna会改变样本在时间轴上的分布。xgboost对空值的处理是内置的训练时遇到缺失值会学习默认分支方向。很多教程会特别强调这一点实操时可以用NaN填充一个极端值比如-999但更稳妥的做法是让XGBoost自己处理缺失——因为一旦填充填充值本身就可能成为新的区分信号。使用DMatrix时可直接传入含有NaN的numpy数组xgboost在训练和预测时都会自动处理。还要区分两类空值滚动窗口头部的空值是特征本身没算出来这种情况建议直接丢弃前max_window行中间数据停牌导致的空值要用前值填充或插值不要原样留空否则会把“停牌”这个事实识别成某种信号过度拟合个别案例。3.3 非线性特征变换与XGBoost自身的非线性边界XGBoost是非线性模型能自动捕捉特征与目标之间的复杂关系那还需要手工做非线性变换吗答案是变换依然有价值。典型例子是“量价关系”成交量放大的含义与价格位置有关如果直接输入vol和close两个原始字段树模型要多次分裂才能近似得到vol/close这个比值而显式构造量比、金额、换手这类比率特征能让树模型在有限的深度内更高效地学习。另一种有效的非线性变换是取对数比如成交额log(amount)、市值log(mktcap)因为市值跨度极大XGBoost对这类跨数量级的特征做切分时对数化后的分布更均匀能让分裂点选得更有意义。XGBoost本身的非线性体现在树的分裂结构上但它不能外推出特征范围之外的值。股价预测里经常出现的“极端行情后特征值超出训练分布”树模型只能把它分配到最近学到的一个叶子上毫无泛化能力可言。所以特征变换的真正作用是压缩动态范围、提高切分效率而不是“让模型更非线性”本身。4. 训练、切分和参数设置时间序列不能用随机shuffle4.1 时序切分训练集必须严格早于验证集把随机train_test_split用在股价数据上是源码里最明显的逻辑错误。过去的样本和未来的样本不是独立同分布价格有趋势、波动率有聚集性随机拆分会让验证集嵌入在训练集时间区间中间导致验证结果虚高。正确做法是按时间顺序切分。from xgboost import XGBRegressor train_end int(len(df) * 0.7) train_df df.iloc[:train_end] val_df df.iloc[train_end:] feature_cols [ret_1, ret_5, ret_20, price_vs_ma20, vol_20, amp] X_train train_df[feature_cols].values y_train train_df[target_reg].values X_val val_df[feature_cols].values y_val val_df[target_reg].values切分比率可以根据样本量调整但务必保证val_df的时间范围完全在train_df之后。更严格的方案是“gap”切分在训练集和验证集之间留出一段不参与训练的缓冲期。原因是股票序列自相关性高紧邻的两天数据信息高度重合这种重叠会让验证集上的误差低估真实表现。常见做法是gap设为n到2n个交易日也就是5到10天。4.2 训练参数与DMatrix早停放在验证集上import xgboost as xgb dtrain xgb.DMatrix(X_train, labely_train, feature_namesfeature_cols) dval xgb.DMatrix(X_val, labely_val, feature_namesfeature_cols) params { objective: reg:squarederror, eval_metric: rmse, max_depth: 4, learning_rate: 0.02, subsample: 0.8, colsample_bytree: 0.7, min_child_weight: 5, lambda: 1.0, alpha: 0.1, seed: 42, } model xgb.train( params, dtrain, num_boost_round2000, evals[(dtrain, train), (dval, val)], early_stopping_rounds100, verbose_eval100, )参数的选择是实践出来的但每条都有清晰逻辑max_depth取4是因为股价特征间交互没有深到需要8层以上的程度更浅的树能减少对噪声的拟合learning_rate调低到0.02配合更多棵树是梯度提升的经典组合低学习率带来的方差下降远大于训练时间增加的成本subsample和colsample_bytree分别做行采样和列采样让每棵树看到不同的数据子集提升集成多样性min_child_weight偏大有助于防止树在样本量小的叶子节点上继续分裂。这组参数适用于日频数据如果切到分钟级别建议把max_depth提高到6、min_child_weight降到1。使用DMatrix而不是原生的sklearn接口主要原因是能直接控制eval集和早停逻辑。注意xgboost的XGBRegressor也支持early_stopping_rounds在sklearn接口里设置eval_set同样有效但要注意它会用最后一个迭代的模型做预测而xgb.train返回的model已经停在最佳迭代要确认源码包用的是哪种接口再判断预测时是否真的用了早停。4.3 二分类模型的scale_pos_weight与样本不均衡当标签是“未来5日是否上涨”时A股市场上上涨天数稍多但幅度不均衡。直接用默认参数训练模型容易偏向多数类。用scale_pos_weight时weight的数值一般取负样本数/正样本数。还可以给样本加权重近期的样本权重更高或者让模型的损失函数更关注涨跌幅绝对值大的样本。无论选哪种早停都要看验证集表现并且验证集不能用shuffle。5. 滚动验证与回测口径判断源码里的模型是否可落地5.1 walk-forward验证扩窗训练比滑动窗口更适合股票数据单次切分只能说明模型在某一时间段内有效覆盖不了不同市场状态。walk-forward验证是按时间顺序反复训练和验证def walk_forward(df, feature_cols, n5, train_years2, test_days63): results [] start_idx 0 total_len len(df) train_size 252 * train_years # 约两年交易日 while start_idx train_size test_days total_len: train df.iloc[start_idx:start_idx train_size] test df.iloc[start_idx train_size: start_idx train_size test_days] model xgb.train( params, xgb.DMatrix(train[feature_cols].values, labeltrain[target_reg].values), num_boost_round500, evals[(xgb.DMatrix(test[feature_cols].values, labeltest[target_reg].values), test)], early_stopping_rounds50, verbose_evalFalse, ) pred model.predict(xgb.DMatrix(test[feature_cols].values)) results.append((test[date].values, pred, test[target_reg].values)) start_idx test_days return results扩窗训练比滑窗训练更适合日频选股。每次迭代都加入新数据训练集越来越大模型能覆盖更多市场状态。滑窗训练每次只用固定区间每隔一段时间就得重新调参而且被丢弃的早期数据里可能包含相似行情。全流程跑完后把所有验证段的预测拼接起来与真实标签对齐就得到了“样本外”的完整预测序列之后的回测统计都基于这个序列不能再回到原来那份训练集里取结果。5.2 分层收益与IC比准确率更接近交易本质的指标股价预测里准确率有迷惑性特别是预测上涨这类二分类任务。当上涨天数占55%时模型只要“一直猜上涨”就有55%准确率收益却是零。更有效的评估是信息系数IC也就是预测值与未来收益的秩相关。另外可以对预测值分5层或多层统计每层在未来n日的平均收益观察是否存在单调性。没有单调性模型再好也无法直接构造投资组合。from scipy.stats import spearmanr import pandas as pd # valid_results: 将walk_forward所有验证段的预测与真实收益拼接 pred_series pd.Series(all_preds) real_series pd.Series(all_rets) ic, p_value spearmanr(pred_series, real_series) print(fIC: {ic:.4f}, p-value: {p_value:.4f}) # 分层 deciles pd.qcut(pred_series, 5, labelsFalse) layer_ret pd.DataFrame({layer: deciles, fwd_ret: real_series}).groupby(layer)[fwd_ret].mean() print(layer_ret)IC通常不会高日频预测的IC达到0.05就已经算可用的信号。不要追求0.3以上的高IC在有效市场假设下稳定超过0.1的日频信号几乎不存在。分层收益表要同时打印每层样本数如果某一层只有极少样本可以先去看该层的预测区间是否异常集中而不是急着下结论。5.3 从预测序列到“n日后持股”的回测口径预测结果是第t日收盘后对第tn日收盘价的判断。如果在第t日收盘时买入第tn日收盘卖出每n天交易一次这对应的是“持有n日、不重叠交易”的策略。另一种做法是每日滚动每天都做一次新的预测如果预测值为正就持有为负就空仓持仓逐日刷新这本质上是把多个n日信号叠加成每日仓位。后者的交易频率更高手续费影响更大但信号利用率更高。回测时要明确做的是哪种否则源码里的资金曲线会让人误解。6. 用“滞后特征对照实验”验证模型是否真的没看到未来实践中我会用一个很简单的实验来检查泄漏。把每个特征列整体向后平移n1天以后再重新切片如果模型在新数据上的表现没有明显下降说明原模型使用了未来信息。def leakage_check(df, feature_cols, n, model): df_lag df.copy() # 将特征向后平移n1天制造“不可能被看到”的滞后期 for col in feature_cols: df_lag[col] df[col].shift(n 1) df_lag df_lag.dropna() X_lag df_lag[feature_cols].values X_orig df_lag[feature_cols].values # 占位 # 用同一个测试区间重新对齐比较预测相关性 pred_orig model.predict(xgb.DMatrix(X_orig)) pred_lag model.predict(xgb.DMatrix(X_lag)) corr np.corrcoef(pred_orig, pred_lag)[0, 1] print(f特征滞后{n1}日后与原始预测相关性: {corr:.4f}) if corr 0.7: print(警告预测在特征滞后后几乎不变很可能存在未来信息泄漏。)原理不复杂如果特征中混入了t1到tn日的信息把特征整体往后平移n1天再预测标签不变、特征却大乱模型应该束手无策。可若泄漏的恰好是标签本身或标签的线性变换滞后特征仍然能预知未来相关性会维持在较高水平。这个实验胜在直接不依赖对源码逐行审查。再配合前文提到的walk-forward验证把最终模型的全部预测拼接成时间序列与真实收益对齐后人工抽看几条高预测值的样本在随后5日的走势是否符合预期一套源码的可信度就能基本定性。做完这些再写下一篇记录实验参数的笔记这个项目才算真正转化为自己的东西。本文还有配套的精品资源点击获取