机器学习多因子选股:从因子筛选到回撤控制的源码实战

发布时间:2026/10/12 14:41:11
机器学习多因子选股:从因子筛选到回撤控制的源码实战
简介面向量化金融与人工智能交叉领域的读者完整演示了基于机器学习构建多因子选股模型的实验流程。内容覆盖单因子测试、因子共线性分析、特征与标签构建以及支持向量回归、长短时记忆网络、极端梯度提升、随机森林、自适应提升等多种模型的回测对比最优随机森林模型累计收益约百分之六十经择时策略控制后最大回撤约百分之九夏普率零点九左右。压缩包共三十八个文件以Python源码为主包含单因子测试、因子分析及多种模型训练脚本另附多份PDF研究报告、可视化结果图、说明文档等整体体积十四点七一兆字节。项目代码均调试运行通过适合计算机、人工智能等专业毕业设计或量化入门者参照复现。目前已有四百六十四人学习下载既能快速搭起多因子研究基线也可为后续扩展策略与风险管理提供结构清晰的样板。1. 机器学习构建多因子选股模型这套源码能把回撤压到 9%做量化选股的人应该都有过这种体验单因子测了一大堆相关性没筛直接丢给模型结果训练集收益漂亮得像骗局一到样本外就原形毕露。这套基于机器学习方法构建的多因子选股模型源码包走的是一条更扎实的路线——先用单因子测试把有效因子筛出来再做共线性分析然后才轮到 SVR、LSTM、XGBoost、随机森林、Adaboost 这些模型轮番上阵最后一层择时策略把回撤死死摁住。我拆完之后最大的感受是这个项目不是给你一个跑通的脚本就完事而是把因子筛选→模型对比→风险控制整条链路都留了可改的代码和报告适合正在做量化课程设计、毕业设计或者想把自己那套因子库接进机器学习框架的从业者。它解决的核心问题很具体在 A 股这种 alpha 越来越难找的市场里如何系统地找到有效因子并让模型在样本外不过拟合同时把回撤控制在可接受范围。2. 单因子测试与因子筛选先筛数据再谈模型别把噪声喂给算法2.1 单因子测试框架从因子列表到回测报告的一次完整闭环打开压缩包最先要看的不是模型文件而是single_factor_test.py。这个文件定义了一套完整的单因子测试流程核心逻辑是传入因子列表逐个执行 run_test把回测报告的关键字段落进 CSV再做可视化与筛选。整个流程设计得比较规范适合直接跑通后替换成自己的因子。# 单因子测试入口示例基于源码逻辑整理 import pandas as pd from factor_test import FactorTester tester FactorTester(data_path./data/stock_daily.csv) factor_list [momentum_20, turnover_5, roe_ttm, pe_ratio, alpha_101] report_path ./output/single_factor_report.csv for factor in factor_list: # 执行单因子回测获取IC、IR、分组收益等关键字段 df_report tester.run_test(factor, group_num10, holding_days5) df_report[factor_name] factor df_report.to_csv(report_path, modea, headernot pd.io.common.file_exists(report_path), indexFalse) # 结果可视化 tester.visualize_group_return(momentum_20, save_path./figs/momentum_20_group.png) tester.visualize_ic_trend(roe_ttm, save_path./figs/roe_ttm_ic.png)逻辑上分四步走第一步定义待测因子列表第二步执行多次单因子 run_test第三步保留回测报告并保存 CSV第四步结果可视化。group_num10表示把股票按因子值从小到大分成 10 组观察第 10 组与第 1 组的累计收益差这是判断因子单调性的常用做法。holding_days5是调仓周期A 股短周期调仓容易踩到交易成本5 天算是一个折中的默认值。跑完看两个指标IC 均值是否显著不为零ICIR 是否大于 0.5。如果 IC 均值为 0.02 但 ICIR 不到 0.3这个因子稳定性存疑后面模型再厉害也救不回来。2.2 因子共线性分析为什么价值因子和每股指标因子经常打架源码包里的factor_analysis.py专门处理因子共线性。很多人在这一步会偷懒觉得相关性高点无所谓反正树模型能自动挑特征。但实际上线性模型如multi_factor_lr.py会直接被共线性干翻树模型虽然不至于崩但特征重要性会被均分可解释性瞬间变差。# 因子共线性筛查逻辑基于源码思路归纳 import pandas as pd import numpy as np def analyze_collinearity(factor_df, corr_threshold0.6): corr_matrix factor_df.corr() upper_tri corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k1).astype(bool)) # 找出相关性超过阈值的因子对 drop_candidates [] for col in upper_tri.columns: high_corr_pairs upper_tri.index[upper_tri[col].abs() corr_threshold] for idx in high_corr_pairs: drop_candidates.append((col, idx, round(upper_tri.loc[idx, col], 3))) return drop_candidates # 示例价值类因子与每股指标类因子容易高度相关 drop_list analyze_collinearity(factor_df, corr_threshold0.6) for pair in drop_list: print(f因子 {pair[0]} 与 {pair[1]} 相关系数 {pair[2]}建议保留其一)这里有血泪教训源码包里有价值类.png、每股指标类.png、行业分析师类.png这些因子分类图说明作者先对因子做了分类再测相关性。我实践下来市盈率、市净率、股息率这几个价值因子与每股收益、每股净资产之间的皮尔逊相关系数经常能到 0.7 以上。处理方式一般是保留 IC 更高、逻辑更独立的那一个或者用因子正交化处理。corr_threshold0.6是一个参考值行业内常用 0.5 到 0.7 作为阈值区间这取决于你后续用什么模型——如果后面是 LSTM 这种对特征冗余不敏感的模型阈值可以放松如果前面是线性回归0.5 就得动手处理。2.3 单因子测试的边界什么时候可以弱化这一步官方提示里说得比较明白单因子问题可以手动实现或者弱化处理通过间接方法解决并不一定要完成一个完整的回测框架。这一点我深有体会。如果你的目标是做多因子模型本身的研究重点在模型对比和择时策略那单因子测试可以简化成IC 计算 分组收益两层不需要把换手率、交易成本、冲击成本全部建模。压缩包里有两份参考文档直接相关单因子测试.PDF和华泰证券的多因子系列报告。前者是学术视角的因子检验流程后者是业界视角的多因子体系构建两个配合着看能帮你判断自己该做到哪一层。我的建议是课程设计做到 IC 均值 ICIR 分组单调性三层就够用毕设可以往上加换手率分析和分行业中性化。3. 特征工程与等权 baseline先跑通一条最简单的路再上复杂模型3.1 特征和标签怎么构建预测哪一段收益才是正确的姿势源码包里的data_exploration.ipynb承担了特征工程和标签定义的核心工作。特征这块前面单因子筛选出的最终因子直接作为模型输入。重点是标签构建——预测未来 N 日的收益率而不是当日或次日。常见做法是# 标签构建预测未来5日收益率基于源码思路 import pandas as pd import numpy as np df[future_return_5d] df.groupby(stock_code)[close].transform( lambda x: x.shift(-5) / x - 1 ) df df.dropna(subset[future_return_5d]) # 特征标准化每个截面做z-score避免引入未来函数 for col in feature_cols: df[col _zscore] df.groupby(trade_date)[col].transform( lambda x: (x - x.mean()) / x.std() )这里有一个新手最容易翻车的点shift(-5)取的是未来第 5 天的收盘价相对今天的收益但如果最后 5 天没有数据dropna会把它们删掉这没问题。真正的坑在于标准化方式——如果在全样本上做标准化等于把未来信息混进了训练集回测收益会虚高。正确做法是每个交易日截面内做 z-score也就是上面代码里groupby(trade_date)的写法。源码包的same_weight_model.py能跑出相对合理的结果就是因为它在特征处理上守住了这条线。3.2 等权线性模型为什么先跑 baseline 而不是直接上 LSTM项目里先建立的是等权重线性模型这个顺序是对的。一个等权线性模型的含义是所有入选因子在打分时权重相同最终按综合得分排序选股。它的作用有两个第一是作为 baseline后续所有复杂模型的超额收益都要跟它比如果 XGBoost 连等权线性都跑不过那问题多半出在特征而不是模型第二是验证因子组合的逻辑一致性等权模型里因子的正负方向是明确的如果某个因子在单因子测试里是正向的等权打分层却贡献了负收益那一定是数据处理出问题了。# 等权线性打分选股逻辑基于same_weight_model.py思路 def score_stocks(factor_df, factor_direction): score 0 for factor in factor_df.columns: # direction为1表示因子值越大越好-1表示越小越好 score factor_df[factor] * factor_direction[factor] return score # 选股取综合得分最高的前10% top_stocks df.groupby(trade_date).apply( lambda x: x.nlargest(int(len(x) * 0.1), total_score) )等权模型跑出来的收益曲线就是一条参考线。如果这条参考线年化收益在 15% 到 20%最大回撤 15% 左右那说明因子本身是有效的后面的机器学习模型是在此基础上做增强。如果等权模型本身都是亏的直接调包调参没有任何意义回头重新筛因子才是正事。这个判断逻辑能帮你省下大量无谓的调参时间。4. 机器学习模型对比与参数选型SVR、XGBoost、随机森林、LSTM 谁更靠谱4.1 模型全家桶的定位每个模型适合什么样的因子结构源码包里模型文件很全svm.py、gbdt.py、xgb_model.py、random_forest_reg.py、mlp.py、lstm.py、adaboost_model.py、multi_factor_lr.py。把它们分成三组理解比较清晰第一组是线性模型multi_factor_lr.py适合因子维度少、共线性处理干净的场景。它的优势是可解释性强回归系数直接就是因子权重但缺点是处理不了非线性关系。第二组是树模型家族包括random_forest_reg.py、gbdt.py、xgb_model.py、adaboost_model.py。这类模型对特征尺度不敏感能捕捉因子与收益之间的非线性交互训练速度快是做多因子选股的性价比之选。第三组是神经网络mlp.py和lstm.py理论上拟合能力最强但在 A 股这种信噪比低的数据上非常容易过拟合。我的习惯是先用树模型找到特征重要性的排序确认哪些因子真的在起作用再决定要不要上神经网络。4.2 随机森林为什么在这个项目里表现最好参数与结果的对照摘要里明确提到最优的随机森林模型累计收益 60% 左右经择时策略风险控制后最大回撤 9% 左右夏普率 0.9 左右。这个结果在行业内属于中等偏上水平。随机森林胜出的原因不复杂它对噪声的容忍度高bagging 机制降低了单棵树的方差而且因子数据本身噪声就大XGBoost 的 boosting 机制会不断拟合残差反而把噪声也学进去了。# 随机森林参数示例基于random_forest_reg.py from sklearn.ensemble import RandomForestRegressor rf_model RandomForestRegressor( n_estimators300, # 树的数量300足够收敛 max_depth6, # 限制深度防止过拟合 min_samples_leaf20, # 叶子节点最少样本数A股截面数据建议15-30 max_features0.6, # 每次分裂随机选取60%特征 random_state42 ) # 训练与交叉验证 from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for train_idx, test_idx in tscv.split(X): rf_model.fit(X[train_idx], y[train_idx]) pred rf_model.predict(X[test_idx])参数上有几个注意点。n_estimators300是收益和时间的平衡再往上加树对精度提升有限但耗时翻倍。max_depth6是我常用的值A 股因子数据信噪比低深度超过 8 基本就开始记忆噪声。min_samples_leaf20对应的是收益预测稳定性值太小会导致预测结果跳动剧烈回测曲线毛刺特别多。时间序列切分务必用TimeSeriesSplit而不是随机 KFold否则未来数据泄露直接让回测结果失真。max_features0.6也就是 60% 的特征采样比例能在保证多样性的同时不让单棵树太偏。4.3 SVR 与 LSTM 的典型翻车为什么这两个模型很难调好svm.py和lstm.py在量化选股里属于看起来很美的模型。SVR 的问题在于它对特征缩放极其敏感虽然用了 z-score 标准化但 RBF 核的 gamma 参数一旦没调好模型会退化成预测值全是均值附近的平庸结果。LSTM 的问题更麻烦——它需要的是长序列的时间依赖关系但 A 股因子数据是横截面切片时间维度上每个样本独立强行构造时间窗口只会让模型学到市场风格切换的假规律。我见过不少人在这两个模型上耗掉大半时间最后收益还不如等权线性模型。所以我的建议很直接SVR 和 LSTM 在这个场景里定位为对比实验证明自己试过即可不要把主要精力押在上面。run_test.bat这个批处理文件的设计思路就是一次把所有模型跑完统一记录结果你可以在自己的机器上把 XGBoost 和随机森林单独拎出来仔细调参其他的跑个默认参数做个对照就行。5. 实战避坑回测细节、数据对齐和因子处理那些翻车现场5.1 未来函数泄露回测收益虚高的头号元凶现象训练集和测试集上收益都很高模拟盘一跑就变脸回测曲线在调仓日附近总有诡异的跳涨。原因特征计算时用了未来数据最常见的是用全样本算均值、方差做标准化或者计算因子值时shift方向写反。解决严格按时间顺序切分训练集与测试集特征标准化必须在截面内完成每个调仓日只使用当日及之前的数据。我自己的习惯是写一个数据时间戳校验函数每次跑回测前检查所有特征列的时间索引是否严格小于等于预测目标时间戳宁可慢一点也不想再被未来函数坑一次。5.2 股票停牌与涨跌停边界样本把回测曲线拉出异常尖峰现象回测收益曲线在某几个时间点出现极端的上下跳检查持仓发现持有了一字板或停牌复牌的股票。原因停牌股票在调仓日无法成交一字涨停板买不进、一字跌停板卖不出但回测程序默认按收盘价成交导致收益被虚增或虚减。解决在买入条件里过滤当日停牌和一字涨跌停的股票卖出时如果跌停则推迟到次日处理。压缩包里的因子分类 PNG 图里有特色技术指标类这类因子在高波动股票上特别容易触发这种边界情况处理不好会直接扭曲回测结果。A 股的涨跌停制度是量化模型上线前必须尊重的现实约束。5.3 因子值与收益率的匹配错位T 日因子对 T1 日收益现象单因子测试 IC 很高但放进多因子模型后贡献为负甚至出现因子方向反转。原因因子值计算日期与收益匹配日期没有对齐。常用做法是 T 日收盘计算因子值T1 日开盘买入T1 日收盘计算收益。如果错配成 T 日因子对 T 日收益就包含了当日价格变动对因子值的反向影响。解决检查数据对齐逻辑确保factor_value的日期索引与future_return的起始日期差一天。这个问题说实话光看代码很难发现两个不同的日期列放在两个 DataFrame 里左连接一错位输出结果照样完整但因子方向就是不对。5.4 训练集与测试集的时间切分不能随机打乱必须按年滚动现象模型在样本内 IC 高得吓人样本外直接归零或变负。原因直接用了train_test_split默认的随机切分方式导致训练集包含未来数据。解决使用时间序列切分比如训练集用 2016 到 2019 年验证集用 2020 年测试集用 2021 年逐年滚动前移。time_roll_model.py这个文件建议认真读一下它实现的就是滚动时间窗口训练每滚动一次模型重新拟合一次这种做法工程上叫 walk-forward 验证比一次性切分更能模拟真实交易场景。5.5 因子标准化与行业中性化为什么银行股总是被选出来现象选股结果集中在银行、地产等低估值板块组合分散度不够。原因因子没有做行业中性化处理价值因子天然偏好低估值行业如果没有按行业做标准化模型会把行业风格当成 alpha。解决在每个行业内分别对因子做标准化让因子反映行业内相对强弱而不是跨行业的绝对比较。基础类因子和价值类因子尤其需要做这一步否则你会发现模型的收益曲线跟银行指数走势高度重合那就不叫多因子选了那叫行业择时。源码的get_factor_report.py里可以看到因子报告的输出结构建议在报告里加入行业分布统计如果某个行业权重超过 30%大概率是中性化没做到位。6. 择时与回撤控制把夏普 0.9 的模型推上线前还要做什么6.1 策略叠加择时收益曲线变向的代价与收益整个项目最值得借鉴的设计是在模型预测之上叠加了一层择时策略这才把最大回撤控制在了 9% 左右。说直白点模型负责选哪些股票选股 alpha择时负责什么时候该空仓仓位控制。回测代码里有明显的仓位标记逻辑市场状态差的时候持仓比例可以降到零相当于模型说这时候我不玩了。这种做法牺牲了一部分上涨收益换来了回撤的大幅压缩。夏普率 0.9 配上最大回撤 9%这个组合比年化 50% 但回撤 30%的模型有实用价值得多——真金白银下场的时候回撤 30% 你根本扛不住心态一崩操作就走形。6.2 后验归因自己给自己做一次体检模型跑完不要只看收益曲线把get_factor_report.py生成的报告翻开对照前面在华泰证券那份多因子系列报告里的归因框架去做后验检查收益贡献来自哪些因子、是否集中在某几个行业、风格因子暴露有多大。这一步相当于给模型做体检。我在自己的项目里见过最典型的案例某次回测收益特别漂亮一查归因发现全是小市值因子在贡献行情风格一转模型立刻失效。这种伪 alpha躲过了单因子测试的 IC 检查却躲不过风格归因。A股Alpha策略及产品回顾与展望这份报告里就有专门的章节讲风格暴露的监控方式实操价值很高。6.3 最后一步参数敏感性测试模型参数调完不是终点要做一次敏感性测试把随机森林的max_depth从 4 到 10 逐个跑一遍把min_samples_leaf从 10 到 40 逐个跑一遍观察收益和回撤的波动范围。如果参数稍微一动结果就剧烈波动说明模型过拟合比较严重需要退回更保守的参数区间。如果参数在合理范围内变化时结果相对稳定说明模型对参数的依赖是平滑的这个模型上线的置信度才算真正建立起来。从那以后我每次做完一个模型都会强制走一遍敏感性测试把不同参数组合的结果矩阵存下来不再是拿一组最好看的参数就兴冲冲往外跑。做量化不是比谁的 npm 调参技术好而是比谁更早发现自己的模型在什么时候会失效。希望这套流程和源码包里的代码能帮你在自己的多因子模型上少踩几个坑把该避的问题提前避掉。本文还有配套的精品资源点击获取