1. 从拍脑袋到讲逻辑多因子选股到底在解决什么问题很多人第一次接触量化选股脑子里想的都是给我一个公式告诉我明天买哪只。这种想法本身没错但如果你真拿这个思路去做基本面选股大概率会在前三个月就把信心亏光。原因很简单单因子选股本质上是在赌一个指标的有效性而市场环境一变这个指标就可能彻底失效。我刚开始做量化的时候也走过这条路。当时用了一个很朴素的策略市盈率低于20倍就买入高于40倍就卖出。回测数据看起来很漂亮但实盘跑了两个月收益曲线跟回测完全对不上。后来复盘才发现那段时间市场风格从价值切换到了成长低市盈率的票集体趴窝而高市盈率的成长股反而一路向上。这不是策略写错了而是单一因子无法覆盖市场的多面性。多因子选股的核心思路就是承认没有哪个指标能永远有效然后通过同时使用多个维度、相关性较低的因子让组合在不同市场环境下都有一定的适应能力。打个比方单因子选股像是只带了一把锤子看什么都像钉子多因子选股则是带了一个工具箱遇到不同的问题能用不同的工具。具体来说多因子模型要解决三个层面的问题第一层是选什么。基本面数据那么多市盈率、市净率、ROE、营收增速、毛利率、资产负债率、股息率……到底哪些值得纳入这不是拍脑袋决定的而是需要从经济学逻辑出发判断这个指标是否真的能解释股票的未来收益。第二层是怎么组合。选出了五个因子是等权重打分还是根据历史表现动态调整权重因子之间如果高度相关比如ROE和ROA同时放进去会不会导致某个维度被过度放大第三层是怎么用。因子得分算出来之后是选前10只等权买入还是选前50只做行业中性化处理调仓频率是月度、季度还是半年度这些执行层面的决策往往比因子本身更能影响最终收益。这篇文章不会给你一个稳赚不赔的因子组合那种东西不存在。我要做的是把多因子选股前期需要储备的知识点按照我自己的学习路径和实操经验系统地梳理一遍。适合已经会写Python基础代码、对pandas有一定了解、但还没真正做过完整多因子策略的读者。如果你连DataFrame的groupby都用不熟练建议先去补一下pandas的基础操作否则后面的内容会让你很痛苦。2. 基本面数据的获取与清洗脏数据比没数据更可怕做多因子选股第一步永远是搞数据。很多人觉得数据获取是最简单的环节随便找个接口拉下来就行了。但实际情况是数据清洗占整个策略开发时间的60%以上而且这一步做不好后面所有分析都是空中楼阁。2.1 基本面数据的几个核心来源与各自坑点目前市面上能获取A股基本面数据的渠道大致分三类数据来源类型典型获取方式优点主要坑点开源财经数据接口各类公开财经API免费、调用方便字段不全、历史数据缺失、频率限制本地金融数据库购买或学校提供的数据库字段完整、历史长需要SQL查询能力、更新滞后交易所与官方披露爬取公告或下载文件最权威格式混乱、非结构化、工作量大我个人的建议是前期学习阶段用开源接口就够了但一定要做数据校验。不要拿到数据就直接算因子先做三件事检查缺失值比例。如果某个字段在30%以上的股票上都是空值这个因子直接放弃不要试图用均值填充那会引入巨大偏差。检查时间对齐。财报数据有报告期和公告期两个时间概念。比如一季报的报告期是3月31日但公告期可能是4月25日。如果你用报告期来对齐交易数据就会引入未来函数——你在3月31日就知道了4月25日才公布的数据这在实盘中是不可能实现的。检查单位一致性。有些接口的营收单位是万元有些是元如果不统一算出来的增速会差四个数量级。注意未来函数是多因子回测中最隐蔽的杀手。除了财报公告期的问题还有停牌股票的复牌处理、成分股调整的时间点等都需要逐一确认。2.2 用pandas做数据清洗的实操框架假设你已经通过某个接口获取了一张包含股票代码、交易日期、市盈率、市净率、ROE等字段的DataFrame下面是我常用的清洗流程import pandas as pd import numpy as np # 假设 df 是原始数据包含 code, date, pe, pb, roe, revenue_yoy 等字段 # 第一步统一日期格式并排序 df[date] pd.to_datetime(df[date]) df df.sort_values([code, date]).reset_index(dropTrue) # 第二步处理缺失值——先统计再决定策略 missing_ratio df[[pe, pb, roe, revenue_yoy]].isnull().mean() print(missing_ratio) # 如果某个字段缺失超过30%考虑放弃该因子 # 第三步异常值处理——用分位数截断而非直接删除 def winsorize(series, lower0.01, upper0.99): low series.quantile(lower) high series.quantile(upper) return series.clip(low, high) for col in [pe, pb, roe, revenue_yoy]: df[col] df.groupby(date)[col].transform(winsorize) # 第四步检查财报公告期对齐 # 假设 df 中有 report_date 和 announce_date 两个字段 # 必须用 announce_date 来对齐交易日期而不是 report_date这里重点说一下为什么用分位数截断而不是直接删除异常值。直接删除会损失样本量而且A股市场上很多异常值其实是真实存在的极端情况比如某年某公司因为资产重组导致ROE暴涨到200%。如果你直接删掉就丢失了这个信息。用1%和99%分位数截断既保留了样本又限制了极端值对后续标准化和打分的影响。2.3 数据频率的选择季度、月度还是日度基本面数据天然是低频的财报一个季度才更新一次。但交易数据是日度的这就产生了一个频率匹配的问题。我的做法是因子计算用季度频率但持仓调整可以月度或季度执行。具体来说在每个财报公告日之后更新一次因子值然后在下一次调仓日使用最新的因子值。中间如果沒有新的财报因子值保持不变。这样做的好处是避免了用日度价格数据去插值基本面因子那种做法会引入大量噪音。但缺点是调仓频率不能太高否则因子值没有变化调仓就变成了纯粹的交易成本消耗。3. 因子体系搭建从经济学逻辑到可计算指标数据准备好了接下来要决定用哪些因子。这一步最忌讳的是看到什么用什么必须有清晰的经济学逻辑支撑。3.1 价值因子便宜是不是硬道理价值因子是最经典的一类核心逻辑是买入相对便宜的东西长期来看会获得超额收益。常用的指标包括市盈率PE价格除以每股收益。越低越便宜。市净率PB价格除以每股净资产。适合重资产行业。市销率PS价格除以每股营收。适合尚未盈利的成长型企业。股息率每股分红除以股价。越高说明现金回报越实在。但这里有一个常见的误区不是所有低PE的股票都值得买。有些公司PE低是因为市场预期它未来盈利会大幅下滑这种叫价值陷阱。我在实操中会做一个简单的过滤同时要求PE处于行业后30%分位且最近两个季度营收增速不为负。这样能过滤掉一部分因为基本面恶化而导致的低估值。3.2 质量因子好公司是不是好股票质量因子衡量的是公司的盈利能力和财务健康度常见指标有ROE净资产收益率巴菲特最看重的指标之一。持续高ROE说明公司有护城河。毛利率反映产品的定价能力。资产负债率衡量财务杠杆太高有风险太低可能说明公司不善用杠杆。经营性现金流/净利润判断利润是不是真金白银。质量因子的一个关键问题是行业差异极大。比如白酒行业的毛利率普遍在70%以上而零售行业可能只有20%。如果你直接拿全市场的毛利率排名那选出来的全是白酒和医药。所以质量因子必须做行业中性化处理后面会详细讲。3.3 成长因子增速的可持续性比增速本身更重要成长因子看的是公司的扩张速度常用指标包括营收同比增速净利润同比增速ROE同比变化分析师一致预期调整成长因子最大的坑是基数效应。一家公司去年净利润100万今年200万增速100%看起来很猛但可能只是因为去年太差了。我在使用成长因子时会额外加一个条件最近四个季度的净利润增速标准差不能太大以此过滤掉那些增速忽上忽下的公司。3.4 因子相关性检验别把鸡蛋放在同一个篮子里选完因子之后一定要做相关性检验。如果两个因子的相关系数超过0.7说明它们本质上在衡量同一个东西同时使用会导致某个维度被过度加权。# 计算因子之间的Spearman相关系数 factor_cols [pe, pb, roe, gross_margin, revenue_yoy, profit_yoy] corr_matrix df[factor_cols].corr(methodspearman) print(corr_matrix) # 如果发现 roe 和 gross_margin 相关性超过0.7 # 考虑只保留其中一个或者用PCA降维我一般会保留相关性低于0.5的因子组合。如果两个因子相关性很高优先保留经济学逻辑更直接的那个。比如ROE和ROA相关性很高我会保留ROE因为它是股东更关心的指标。4. 因子预处理标准化、中性化与去极值原始因子值不能直接用来打分因为不同因子的量纲完全不同。PE可能是5到100ROE可能是-20%到30%如果不做处理直接相加或加权PE会完全主导结果。4.1 标准化让不同因子站在同一起跑线最常用的标准化方法是Z-Score标准化def zscore(series): return (series - series.mean()) / series.std() # 按日期分组做标准化确保每个截面独立 for col in factor_cols: df[col _z] df.groupby(date)[col].transform(zscore)Z-Score之后每个因子的均值是0标准差是1。但Z-Score有一个问题对极端值敏感。如果某个因子有一个超级极端的值标准化之后其他值都会被压缩到很小的范围内。所以标准化之前一定要先做去极值处理也就是前面提到的分位数截断。另一种方法是排名百分位法直接把因子值转换成0到1之间的排名df[col _rank] df.groupby(date)[col].rank(pctTrue)排名法的好处是完全不受极端值影响缺点是丢失了因子值之间的相对距离信息。我一般在因子分布比较均匀的时候用Z-Score分布明显偏斜的时候用排名法。4.2 中性化剔除行业和市值的影响这是多因子选股中最容易被忽视、但影响最大的一步。如果不做中性化你的因子可能只是在选行业和选市值。举个例子银行股的PE普遍在5到8倍而科技股的PE可能在50倍以上。如果你直接用PE排名选前10%选出来的全是银行和地产。这不是因为你的因子有效而是因为行业分布导致的偏差。中性化的做法是对因子值做回归取残差import statsmodels.api as sm def neutralize(factor_series, industry_dummies, market_cap): # factor_series: 因子值 # industry_dummies: 行业哑变量矩阵 # market_cap: 对数市值 X pd.concat([industry_dummies, np.log(market_cap)], axis1) X sm.add_constant(X) model sm.OLS(factor_series, X, missingdrop).fit() return model.resid # 对每个因子在每个截面上做中性化 for col in factor_cols: df[col _neutral] df.groupby(date).apply( lambda x: neutralize(x[col], industry_dummies, x[market_cap]) ).reset_index(level0, dropTrue)中性化之后因子值代表的是在相同行业和相同市值水平下这只股票的该因子相对高低。这样选出来的股票才是真正因为因子本身而入选而不是因为行业或市值暴露。注意中性化用的行业分类标准要统一。我一般用申万一级行业因为它的分类相对稳定不会频繁调整。如果用一些细分行业分类可能会因为分类变动导致中性化结果不稳定。4.3 因子方向统一让所有因子越大越好有些因子是越大越好如ROE、营收增速有些是越小越好如PE、PB。在合成综合得分之前必须把方向统一。最简单的方法是对越小越好的因子取负值# PE和PB是越小越好取负号 df[pe_neutral] -df[pe_neutral] df[pb_neutral] -df[pb_neutral]这样处理后所有因子都是值越大代表越好后续合成时直接加权求和即可。5. 因子合成与打分等权、IC加权还是机器学习单个因子处理完之后下一步是把它们合成一个综合得分。这一步的方法选择直接决定了策略的风格。5.1 等权合成简单但不容易出错等权合成就是每个因子给相同的权重直接相加或平均df[score] df[[col _neutral for col in factor_cols]].mean(axis1)等权的好处是不需要估计任何参数避免了过拟合。缺点是假设所有因子同等重要这显然不符合实际。但在因子数量不多5个以内且经济学逻辑都比较扎实的情况下等权往往能跑赢很多复杂方法。5.2 IC加权让数据说话ICInformation Coefficient是因子值与未来收益的相关系数。IC加权就是根据每个因子最近一段时间的IC均值来分配权重# 假设已经计算了每个因子在过去12个月的月度IC ic_weights ic_mean / ic_mean.abs().sum() df[score] sum(df[col _neutral] * ic_weights[col] for col in factor_cols)IC加权的问题是IC本身不稳定。一个因子上个月IC很高下个月可能就变成负的。所以实际使用中我一般会用过去12个月IC的均值除以标准差即IC_IR来加权这样能一定程度上过滤掉不稳定的因子。5.3 机器学习合成谨慎使用现在很多人喜欢用随机森林、XGBoost之类的模型来合成因子。理论上这能捕捉因子之间的非线性关系但实操中有几个大坑过拟合风险极高。因子数量少、样本噪音大树模型很容易记住噪音。可解释性差。你很难说清楚为什么模型给了这个股票高分。训练集和测试集的划分要非常小心。必须用时间序列切分不能用随机切分。我的建议是如果你刚开始做多因子先用等权或IC加权跑通整个流程等有了稳定的回测框架和足够的数据积累再考虑机器学习方法。不要一上来就上复杂模型那样你连问题出在哪里都找不到。6. 回测框架搭建别让回测骗了你因子合成完得到一个综合得分接下来就是回测。回测是验证策略有效性的关键步骤但也是最容易自欺欺人的环节。6.1 回测的基本流程一个完整的回测至少包含以下步骤确定调仓日期。比如每季度财报公布后的第一个交易日。在每个调仓日根据综合得分排序选出前N只股票。等权或市值加权买入持有到下一个调仓日。计算每个持有期的收益累乘得到净值曲线。与基准如沪深300对比计算超额收益、夏普比率、最大回撤等指标。# 简化的回测循环框架 portfolio_returns [] for date in rebalance_dates: # 获取当期因子得分 scores df[df[date] date].sort_values(score, ascendingFalse) # 选前50只 selected scores.head(50)[code].tolist() # 计算持有期收益假设持有到下一个调仓日 next_date get_next_rebalance_date(date) period_return df[(df[date] next_date) (df[code].isin(selected))][return].mean() portfolio_returns.append(period_return) # 计算净值 nav (1 pd.Series(portfolio_returns)).cumprod()6.2 回测中最容易犯的三个错误第一个错误幸存者偏差。如果你用的股票池是当前上市的所有股票那么已经退市的股票就不在样本里。这些退市股票往往是表现最差的剔除它们会让回测收益虚高。正确的做法是使用历史成分股列表每个调仓日只使用当时真实存在的股票。第二个错误前视偏差。除了前面提到的财报公告期问题还有一个常见的是用当天的收盘价买入。如果你在调仓日收盘后计算因子得分那么最早也只能在下一个交易日开盘买入。用当天收盘价买入相当于用了未来信息。第三个错误交易成本忽略。A股的交易成本包括佣金、印花税和冲击成本。佣金一般万分之二到万分之三印花税千分之一卖出时收取冲击成本取决于资金量和股票流动性。如果策略换手率很高交易成本能吃掉大部分超额收益。注意回测中建议至少设置双边千分之三的交易成本。如果策略月度调仓、每次换手50%一年下来交易成本就是3.6%这个数字足以让很多看起来不错的策略变成亏损。6.3 评价指标不只看收益评价一个多因子策略不能只看总收益。我一般会关注以下几个指标指标含义参考标准年化超额收益相对基准的超额5%以上值得关注信息比率超额收益/跟踪误差0.5以上算不错最大回撤净值从高点的最大跌幅越小越好需结合收益看换手率每期调仓的股票比例影响交易成本因子IC均值因子与未来收益的相关性0.03以上算有效IC_IRIC均值/IC标准差0.3以上算稳定这些指标要综合看。一个策略年化超额20%但最大回撤50%另一个年化超额10%但最大回撤15%后者在实际使用中往往更受欢迎因为回撤小意味着你可以拿得住。7. 从回测到实盘那些回测不会告诉你的坑回测跑通了指标也好看是不是就可以直接实盘了远远不够。回测和实盘之间有一条巨大的鸿沟我踩过的坑至少包括以下几个。7.1 流动性问题小市值股票的买卖冲击很多多因子策略在小市值股票上表现特别好因为小市值股票波动大、错误定价多。但实盘中小市值股票的流动性很差你买进去可能就把价格推高了卖出来又把价格砸下去了。回测中假设的以收盘价成交在实盘中根本不可能实现。我的做法是在选股时加入流动性过滤剔除日均成交额低于5000万的股票。这个阈值可以根据资金量调整资金量越大阈值越高。7.2 停牌与涨跌停想买买不到想卖卖不掉A股有涨跌停制度如果调仓日某只股票涨停你根本买不进去如果跌停你也卖不出来。回测中如果不处理这种情况会高估策略收益。处理方法很简单在调仓日剔除涨停和跌停的股票不参与交易。虽然这会损失一些机会但更接近实盘情况。7.3 因子失效没有永远有效的因子这是最根本的问题。任何一个因子都有失效的时候。2021年之前小市值因子在A股非常有效但2021年之后小市值因子经历了长时间的回撤。如果你在2021年初根据历史回测重仓小市值因子后面会非常难受。应对因子失效的方法有两个一是分散同时使用多个相关性低的因子避免单一因子失效导致整体崩溃二是监控定期计算因子的IC和IC_IR如果连续多个周期IC为负就要考虑降低该因子的权重甚至剔除。7.4 心理因素回撤时你还能坚持吗最后说一个不是技术问题但最重要的问题心理。回测中看到最大回撤20%你觉得没问题我能扛住。但实盘中当你真金白银亏了20%而且连续几个月跑输基准你还能坚持执行策略吗我见过太多人回测做得漂漂亮亮实盘一遇到回撤就手动干预结果把策略搞得面目全非。多因子选股是一个概率游戏单次调仓的结果是随机的只有长期执行才能体现因子的有效性。如果你做不到长期坚持再好的策略也没用。8. 前期知识储备清单与学习路径建议最后把多因子选股前期需要储备的知识点整理成一个清单方便你对照检查自己的准备情况。8.1 必备技能清单技能类别具体内容掌握程度要求Python基础函数、循环、条件判断、文件读写熟练pandasDataFrame操作、groupby、merge、apply熟练numpy数组运算、广播机制了解统计学均值、标准差、相关系数、回归分析理解概念金融基础财务报表结构、常用估值指标理解概念数据库SQL基本查询了解8.2 推荐的学习顺序如果你是从零开始我建议按这个顺序推进先补pandas。多因子选股80%的时间在处理数据pandas不熟会非常痛苦。理解财务报表的基本结构。至少要知道利润表、资产负债表、现金流量表分别反映什么。手动计算几个因子。拿一家公司的财报数据手动算一遍PE、ROE、营收增速理解每个数字的来源。跑通一个单因子回测。用最简单的PE因子从数据获取到回测评价完整走一遍流程。扩展到多因子。加入ROE和营收增速做标准化、中性化、合成、回测。优化和迭代。尝试不同的因子组合、权重方法、调仓频率观察结果变化。这个过程我花了大约三个月每天投入两到三个小时。如果你有编程基础但金融知识薄弱可能需要更长时间补金融反之亦然。8.3 几个容易走弯路的地方不要一上来就追求复杂模型。我见过很多人连单因子回测都没跑通就开始研究LSTM和Transformer。结果数据清洗没做好回测框架有前视偏差模型再复杂也是白搭。不要迷信回测收益。回测年化50%的策略实盘能有10%就不错了。回测的作用是验证逻辑不是预测收益。不要忽略交易成本。很多策略回测看起来能赚钱加上交易成本就变成亏损。回测时一定要把成本算进去而且要用保守的估计。不要频繁更换因子。今天看这个因子好就用这个明天看那个因子好就换那个最后什么也积累不下来。选定几个逻辑扎实的因子长期跟踪定期评估比频繁切换要有效得多。多因子选股是一个需要长期积累的领域没有捷径。但只要你把前期这些基础知识打扎实后面的路会越走越顺。我在实际操作中的体会是慢就是快把数据清洗和回测框架做扎实比急着上策略要重要得多。