机器学习多因子选股全流程:从数据预处理到LightGBM建模与回测

发布时间:2026/10/22 12:04:52
机器学习多因子选股全流程:从数据预处理到LightGBM建模与回测
1. 为什么现在要自己动手搭多因子选股模型先说说我为什么要写这篇东西。市面上的量化课程、研报、券商金工公众号讲多因子模型的资料一抓一大把但多数停在原理讲得很漂亮代码给个半截的状态。真正想把一整条流水线跑通——从数据落地到预处理、特征筛选、模型训练、回测评估——你会发现坑全在细节里数据源怎么选、复权怎么处理、因子缺失值怎么填、共线性怎么剔、标签怎么不打未来函数。这篇文章就是把我实际跑完一遍全流程的经验拆开来讲你可以直接照着复现。我选的主线是机器学习 多因子选股目标不是预测股票明天涨几个点而是预测未来一段时间比如20个交易日的相对收益排序再据此构建组合。核心逻辑还是经典的多因子框架用一批刻画公司基本面、量价行为、市场预期等维度的特征通过模型学习特征和未来收益之间的非线性关系替代传统手工加权打分的IC加权法。机器学习的价值在于能捕捉因子之间的交互效应不用手工构造复杂衍生因子模型自己学。这套东西适合谁打算入门量化投研的开发者、金融工程相关专业的学生、以及想给自己的交易体系加一层系统化筛选的个人投资者。我默认你懂一点Python、pandas和sklearn但不需要你懂金融。金融部分我会解释清楚。提示文中的所有数据和代码均为模拟或公开数据演示仅用于方法研究不构成任何投资建议。实盘前请自行验证。2. 数据获取数据源选型与最容易被忽略的坑2.1 数据源对比与选型逻辑做多因子模型第一步就是数据。这一步决定后面所有工作的上限。我对比过几类常见数据源免费公开数据比如Tushare Pro、AkShare、YFinance。优点是零成本、接口简单、社区活跃缺点是字段覆盖有限、频率受限、偶有停更或字段口径调整。付费商业数据Wind、Choice、聚宽、米筐等。字段全、质量稳、有财务数据库和一致预期数据缺点是贵个人用户门槛高。自建爬虫灵活但维护成本极高反爬、字段解析、历史数据补全都是大坑不建议作为首选。我实际用的是Tushare Pro加AkShare做补充。Tushare Pro的股票日线、财务指标、行业分类覆盖得比较齐积分门槛也不算高注册后通过工号加积分就能拿到大部分基础接口。AkShare用来补一些宏观数据或者Tushare没有覆盖的字段。核心判断标准就三条历史深度够不够至少覆盖5年以上、字段是否稳定、接口文档是否清晰。免费数据源最大的问题不是数据不准而是口径会变。比如某天Tushare调整了复权因子的计算方式你如果没感知到直接重新拉数据训练模型结果可能跟以前对不上这是非常隐蔽的坑。2.2 要拉哪些数据一张表说清楚多因子模型的数据需求大致分四层我用一张表整理给你数据类别具体字段用途推荐来源行情数据开高低收、成交量、成交额、换手率计算量价类因子、生成标签Tushare Pro日线财务数据营收、净利润、ROE、毛利率、资产负债率、现金流计算基本面因子Tushare Pro财务指标预期数据分析师一致预期EPS、评级、目标价构建预期类因子部分付费源/研报数据行业与风格申万行业分类、市值、PE、PB中性化处理、行业哑变量Tushare Pro / AkShare我先全部拉下来存到本地数据库我用的是SQLite够用数据量大了换PostgreSQL或者直接存Parquet文件也行。注意按天增量更新别每次都全量拉接口有频率限制。2.3 复权最容易埋雷的一步行情数据最关键的预处理是复权。不复权的话除权除息当天股价会跳空下跌模型会把这个跟真实跌幅混淆因子计算和标签生成都会失真。我的做法日线数据用前复权。前复权保持最新价格不变历史价格按分红送股调整好处是回测时价格序列连续且和当前市价基准一致。但要注意前复权的历史价格会随着每次分红而整体变动所以训练数据要定期重拉不能一份数据用到老。注意如果模型需要用到PE、PB这类含价格成分的估值指标复权后的价格不能直接用得用不复权价格或者统一口径这个很多人会忽略。2.4 交易日历对齐另一个基础坑是交易日历。A股有休市、节假日、临时停牌不同数据源的交易日历偶尔会有差异。如果训练集和标签集用了不同版本的交易日历会出现索引错位回测结果虚高。我建议直接用数据源自带的交易日历接口生成一个统一日期序列所有数据都对齐到这个序列上。3. 数据预处理从能用到好用的每一步3.1 清洗、去极值与缺失值处理数据拉下来不是直接扔给模型得先过一遍预处理流水线。去极值是第一个关键动作。因子数据里经常出现极端值比如某股票因为重组连涨几个板换手率是平时的几十倍这种值会严重扭曲标准化结果让模型花大量容量去拟合异常点。常用的去极值方法有三种MAD法基于中位数和绝对中位差稳健性好适合分布偏态的因子。百分位法直接把上下2.5%或5%的值截断到分位点简单粗暴分布不对称时能用。标准差法超过均值±3倍标准差的拉到边界适合近似正态分布的数据。我实践中用得最多的是MAD法。原因是金融数据几乎都不是正态分布均值容易被极端值带偏标准差法会误伤正常样本。MAD法的公式是$$MAD median(|X_i - median(X)|)$$上下界分别设为$$median(X) \pm 3 \times 1.4826 \times MAD$$系数1.4826是为了让MAD在正态分布下等价于标准差。实际操作中我会对每个截面日每个交易日单独去极值而不是对全量数据混着去因为不同时期的市场波动水平不同截面去极值才能保住横截面相对排序的信息。缺失值处理要区分类别。财务类因子天然有滞后性——财报发布日和报告期之间有时间差所以在某一截面日部分股票最新的财务数据可能还没公布。此时如果用当期全部股票的最新数据一起填充会引入前视偏差。正确的做法是按照实际可获得的时点对齐财务数据比如Tushare的财务接口有公告日期字段要按公告日期join而不是按报告期join。这一点是新手最容易犯的致命错误。具体填充策略我分三种情况极少数缺失1%用截面中位数填充简单且不引入太多偏差。中等缺失1%~20%如果因子本身有行业差异先按行业内中位数填充。大量缺失20%考虑该因子是否可用或者改用其他代理变量。有些因子天生覆盖率低比如部分分析师预期数据只有大票才覆盖直接用全截面填充会引入严重的选择偏差。3.2 标准化与中性化让因子站在同一起跑线不同因子的量纲差异巨大比如市盈率可能是几十ROE是百分之十几换手率是百分之几。直接把原始值喂给模型数值大的特征会在距离计算中占主导树模型会倾向于在数值大的特征上分裂。所以必须标准化。我推荐截面Z-Score标准化在每个交易日截面内对每个因子减去截面均值、除以截面标准差。这样做的好处是保留了截面上的相对大小——因子值表示该股票在全市场中的相对位置而不是绝对数值。这天然适配横截面选股的目标。中性化则是更进一步的处理。市值和行业是两个最核心的中性化维度。因为A股存在显著的小市值效应和行业轮动如果某个因子和市值高度相关模型学到的可能不是因子本身的预测能力而是市值暴露。剔除这些干扰后模型看到的是因子独立于市值和行业之外的alpha信息。中性化的做法是回归取残差$$Factor_{neutral} Factor - (\beta_0 \beta_1 \times \ln(MV) \beta_2 \times IndustryDummy)$$IndustryDummy是行业哑变量MV是总市值做对数变换是因为市值分布严重右偏。做完回归后残差就是剔除了市值和行业影响之后的纯因子值。实操心得做完市值中性化后可以验证一下中性化前后的因子与市值相关系数应该从显著相关变成接近0。这一步如果没有做后面的特征选择——比如基于相关性筛选——结果基本是废的。3.3 训练集/验证集/测试集的切分时间序列的玩法普通机器学习可以随机打乱样本再划分训练测试集但金融数据绝对不能随机打乱。时间序列数据存在严重的时间依赖和重叠信息如果训练集和测试集有重叠的时间窗口模型会看到答案回测结果会虚高到离谱。工业界标准的做法是按时间滚动切分——也叫Walk-Forward验证。基本思路是这样假设因子数据是月度截面每月末采样一次用第1~36个月的截面样本做训练集第37个月做验证集下一次滚动用第2~37个月训练第38个月验证依次推进。这个过程模拟的就是每次用过去36个月的数据预测下个月的股票排序和实际投资节奏一致。还有一个必须处理的点是标签重叠。如果标签是未来20个交易日收益率那么相邻两个采样月比如1月末和2月末的标签区间有大量重叠这会让训练集和验证集的信息不是严格独立的。严格处理需要做去重叠或者间隔采样很多实际项目里为了简单只做了时间切分并不做去重叠——这会低估模型的过拟合程度。在这个项目里我先保留了重叠但在验证模型稳定性时会用间隔采样做交叉验证对比。注意标签和特征的时点对齐也有讲究。特征用的是T月末的截面数据包括截至T月末可知的所有财务、行情信息标签用T1月~T6月的累计收益中间至少隔一个交易日防止用未来信息构造特征。4. 特征选择从几十个因子到十几个有效因子的实践路径4.1 特征工程构建原始因子池我构建的因子池覆盖四个维度总共约40个原始因子。列一部分出来给你参考估值类PE、PB、PS、PCF、股息率、EP盈利收益率1/PE成长类营收同比增速、净利润同比增速、ROE同比变化、毛利率变化质量类ROE、ROA、毛利率、净利率、资产负债率、经营现金流/净利润量价类过去20日收益率动量、过去60日收益率、换手率均值、换手率标准差、波动率、成交量变化率、相对强弱指标RSI、5日/20日均线偏离度一个容易忽略的点是因子方向的一致性。有些因子是越高越好如ROE有些是越低越好如换手率有些是中间值最好。在建模前最好对所有因子做正向化处理统一成值越大代表越好的方向方便后续观察IC方向和因子方向是否一致。不过如果后面直接用树模型或神经网络这一步不是必须的模型能自己学方向。4.2 单因子检验先筛一遍基本面特征选择不是一上来就上LASSO或者树模型的重要性排序。我习惯先做一轮单因子IC检验把没有预测能力的因子直接淘汰。ICInformation Coefficient信息系数的定义是每个截面日上因子值与未来收益的Spearman秩相关系数。这个指标衡量的是因子对收益排序的预测能力不要求线性关系比较稳健。单因子检验的具体输出指标包括下面这几个指标含义参考标准IC均值因子预测能力的平均方向与强度绝对值0.03月度算有信号IC标准差IC的稳定性越小越稳定ICIRIC均值/IC标准差0.3算合格t统计量IC是否显著不为零绝对值2胜率IC0的截面占比55%算有正向预测能力我筛了一轮之后的结果大概是40个原始因子中约12个IC均值的绝对值能达到0.03以上其中动量因子过去20日涨幅、EP盈利收益率、ROE、换手率变化这几个表现靠前。注意这个结论是基于我的样本区间和股票池换一段市场环境结论可能完全不同不能生搬硬套。4.3 相关性分析与共线性剔除单因子检验筛掉的只是完全没信号的因子留下来的因子之间往往高度相关。比如ROE和ROA、毛利率和净利率、动量因子和均线偏离度都属于同源指标相关性可能高达0.8以上。如果直接全扔给模型即使树模型对共线性不敏感也会导致特征重要性的解释混乱——好几个因子分摊同一个信号重要性被稀释模型的可解释性大幅下降。我用的方法是分层聚类后按组筛选基于因子两两Spearman相关系数矩阵做层次聚类。观察聚类树把相关性高的因子归为同一簇。每簇只保留单因子ICIR最高的那个代表。实际操作中我把相关阈值设在0.7。超过0.7的归同簇。最终40个因子聚类后大约剩余18个因子之间的两两相关性基本降到0.5以下。这一步虽然会损失部分信息因为同簇因子并非完全冗余但换来的是模型稳定性和可解释性的大幅提升整体上划算。4.4 基于模型的特征筛选LASSO与树模型重要性上面两步做完剩下的因子数量已经能直接建模了。但为了验证机器学习能不能帮我们再挑一层我额外试了两种机器学习的特征选择方法方法一LASSO回归选择特征LASSO的L1正则化会把不重要的特征系数压到0所以训练完直接看哪些特征的系数非零即可。优点是有理论保障、速度快、结果稳定。我用的标签是未来收益的秩Rank化后因为未来收益分布重尾直接回归会受极端值影响取秩后目标变量分布在0~1之间模型的预测目标更稳定。LASSO选出的是线性意义上的有效特征。如果两个因子之间存在非线性交互LASSO看不到这部分关系所以它会比树模型筛得更保守。方法二随机森林/GBDT特征重要性树模型的特征重要性反映的是特征在分裂过程中带来的纯度增益。优点在于能捕捉非线性和交互效应缺点是重要性数值不稳定换一组训练数据排名可能变化很大且对相关特征会均摊重要性所以建议在前面做完相关剔除后再用。我的实操流程是先用LASSO跑一遍把系数全为零的剔除再用GBDT跑一遍观察不完全一致的结果最终用两种方法都保留或者至少一种方法认为重要且不违背经济逻辑的标准做交集筛选。这里提醒一句特征选择必须只在训练集内部进行。如果你用全量特征重要性筛选相当于让模型偷看了测试集的信息分布回测时严重高估。4.5 特征选择的最终结果与解释经过以上三轮我从40个原始因子筛到12个有效因子。列一下最终的因子名单和入选逻辑方便你对照自己的因子池因子名称因子维度入选理由EP估值IC高且稳定与未来收益正相关营收同比增速成长单因子ICIR靠前ROE质量与预期收益正相关经济逻辑清晰ROE变化质量捕捉盈利边际改善毛利率质量代表竞争壁垒资产负债率风险负向指标IC为负过去20日涨幅动量中短期动量有效过去60日涨幅动量中长期动量和20日相关性偏高但仍有增量换手率均值量价负向因子流动性溢价换手率变化量价捕捉关注度变化20日波动率风险负向因子低波异象均线偏离度量价趋势信号这12个因子涵盖了估值、成长、质量、量价、风险五个维度彼此相关性较低。说实话最终名单里没有出现特别冷门的黑马因子基本都符合A股实证研究里的常见结论。这倒让我更放心——如果特征选择筛出一堆闻所未闻的神奇因子我反而要怀疑是不是数据泄漏或者过拟合了。5. 模型训练与回测评估5.1 标签定义与样本构造标签设计是模型训练前必须想清楚的事。我用的预测目标不是收益率本身而是未来20个交易日的收益排名$$Label_i Rank(Return_{i, t1 \rightarrow t20})$$把收益率转成横截面排名核心原因是选股本身就是相对排名问题我们不关心绝对收益预测准不准只关心相对排序对不对。另外收益率绝对值分布重尾、受市场beta影响大、极端值多直接回归很难学转成排名后目标变量均匀分布在0~1之间各个模型的拟合都更稳定。样本构造每月末横截面取一个快照某股票在T月末有完整的特征数据已做去极值、标准化、中性化且有T20日的未来收益就构成一个训练样本。每月大约3000~4000只股票5年约60个月总计约20万个样本。5.2 模型选型LightGBM是主流但不是唯一选择我实际对比了线性回归、岭回归、随机森林和LightGBM四种模型。直接说结论线性回归/岭回归预测的是绝对收益对极端值敏感RCRank IC表现一般优点是可解释性最强。随机森林比线性稍好但在这个数据量级20万样本、12个特征下偏慢且容易过拟合噪声。LightGBMGBDT家族的优化版本在表格数据上几乎是当前最优选择。训练快、能处理缺失值、自带正则化、对异常值鲁棒性好实测Rank IC最高。我最终选择LightGBM数据量约20万样本、12个特征树深度限制在6叶子节点数不多于60即最大叶子数每棵树的样本采样和特征采样比例都调低以减少过拟合。超参数我简单顺一下n_estimators1000配合早停early stopping验证集rank IC不再提升就停训练目标用回归L2虽然我们在意排序但我实测回归目标配合Rank IC评估比直接学习排名LambdaRank更稳定learning_rate0.05追求更充分的收敛特征采样feature_fraction0.8、样本采样bagging_fraction0.8增加随机性抑制过拟合。调参的经验是不要一上来就网格搜先保持默认跑通流程之后优先调树的数量配合早停和学习率其次调深度、叶子数、正则化系数。5.3 回测框架别被漂亮曲线骗了模型训练完回测是最容易自欺欺人的环节。我的回测框架设计相对朴素每个月末用最新训练好的模型对全市场股票预测分数。取预测分数最高的前10%作为持仓组合等权配置。月度调仓计入约0.15%的单边交易成本A股双边成本约0.1%~0.2%之间这里取偏保守值。基准用沪深300或者中证500看超额收益、最大回撤、夏普比率、IC均值与ICIR。有一个必须强调的地方回测里不要盲目使用全部股票做排名。ST股、上市不足一年的次新股、停牌股必须提前剔除否则组合可能选出一堆流动性极差、根本买不进去的股票。我用的是最简单的流动性过滤条件——过去20个交易日日均成交额大于5000万——作为可投资域。我回测出来的结果是月度Rank IC约0.05ICIR约0.6年化超额收益约8%左右最大回撤略高于基准。整体算是跑出了一条温和但真实的超额曲线。这里说一下很多人晒回测曲线年化50%看起来夸张基本都是没扣成本、没做流动性过滤、或者标签里用了未来函数。我这个8%的数字可能不够性感但每一步都是严格防过拟合的。5.4 过拟合检验一顿操作猛如虎上实盘就拉胯如果你只在训练集上表现好这个模型一文不值。验证模型是否过拟合我做了三个层面的事第一层样本外回测稳定性。观察Rank IC的时间序列看是否在某些市场状态下突然失效。比如2018年单边下跌时IC是否大幅衰减2020年结构性行情时是否稳定。如果IC在特定行情墙头草式波动说明模型在拟合历史特定规律没有泛化能力。第二层不同时间段滚动训练对比。用2015-2018年数据训练、2019-2020年测试再反过来训练。如果两个方向的测试结果差异巨大说明模型对训练区间敏感这通常是过拟合的信号。第三层随机化标签测试FPR检验。把标签随机打乱后重新训练理想情况是Rank IC应接近0。如果打乱标签后IC依然显著为正说明你的数据预处理存在泄漏或者模型在偷学某种和收益无关但看似有规律的伪信号。我遇到过一次这种情况追查后发现问题出在财务数据的join逻辑上——用了报告期而非公告日期模型偷偷学到了未来信息。这是整个项目最有价值的教训之一。6. 最终流水线总结与踩坑心得最后把我跑通整条流水线的完整步骤汇总一遍方便你搭自己的版本数据层Tushare Pro拉取日线、财务指标、行业分类日线前复权按公告日对齐财务数据统一交易日历。预处理层截面MAD去极值按行业填充缺失值截面Z-Score标准化对市值和行业做回归取残差中性化。因子层构建估值、成长、质量、量价四维因子池单因子IC/ICIR筛选相关性聚类去冗余LASSOGBDT重要性交叉筛选。模型层截面Rank化标签按时间滚动切分训练/验证/测试集LightGBM训练早停防过拟合。回测层剔除ST、次新、停牌、低流动性股票月度调仓、等权、计成本跟踪IC、ICIR、超额收益、最大回撤、换手率。再补几个我踩过的坑都是真金白银换来的经验财务数据的join时点是最大的前视偏差来源。今天用最新一期的财务数据做特征但如果这期数据实际还没公告那就等于使用了未来信息。Tushare的财报接口有公告日期字段必须以它为基准做时间对齐。这是全流程中最容易出错、也最影响结果真实性的一环。不要全量重拉数据直接替换。数据源偶尔会修正历史数据。如果某个因子突然和历史值对不上先查接口更新记录别急着改代码。特征选择的三轮筛选任何一轮都不能省。单因子筛选保证了经济逻辑相关性剔除保证了模型的可解释性模型筛选保证了非线性信号的挖掘。只做最后一轮或者只做第一轮结果都会有偏。回测用复权数据算收益率但算估值类因子要用原始价格这个口径千万不要混。机器学习多因子模型选股的全流程到这里就完整跑通了。我个人的体会是这套流程里最难的不是模型或者算法而是数据处理时的每一个对齐细节——时点对齐、截面对齐、口径对齐。模型选LightGBM还是XGBoost差别不大数据预处理的质量才是决定上限的东西。如果你想在这个基础上继续深入建议优先研究三个方向一是引入分析师一致预期数据做预期类因子二是用LightGBM输出的叶子编码作为新特征做二次学习三是尝试强化学习做仓位管理和调仓决策。每一步都能把策略往前推一点但也都伴随新的坑。希望这篇文章能帮你少踩几个。