一、多因子选股模型:从理论到实战
多因子选股模型,说白了就是给股票打分。
你想想看,一只股票好不好,不能光看市盈率,也不能只看ROE。得综合多个维度来评估。这就是多因子的核心思想——把各种能预测收益的特征组合起来,形成一个打分系统。
我个人习惯把因子分成几大类:
- 估值因子:PE、PB、PS、PCF 这些传统指标
- 成长因子:营收增速、利润增速、ROE 变化趋势
- 质量因子:毛利率、净利率、资产负债率、现金流质量
- 动量因子:过去1个月、3个月、6个月的收益表现
- 情绪因子:换手率、波动率、分析师预期调整
我在项目中遇到过一个问题:刚开始做多因子模型时,恨不得把所有能想到的因子都塞进去。结果呢?过拟合严重,实盘一跑就崩。后来我学乖了——因子不是越多越好,关键是有效且稳定。
核心原则:因子之间要低相关,每个因子要有明确的逻辑支撑,不能光靠数据挖掘。
二、因子构建与计算:把想法变成代码
因子构建,说白了就是把你的投资逻辑量化出来。
举个例子,你想捕捉「低估值」这个逻辑。那怎么量化?可以用市盈率的倒数(E/P),也可以用市净率的倒数(B/P)。但要注意,不同行业的估值中枢不一样。银行股PE 5倍算正常,科技股PE 50倍也算正常。所以我在构建因子时,通常会做行业中性化处理。
下面是我常用的因子计算框架:
import pandas as pd
import numpy as np
from scipy import stats
def calc_ep_factor(df):
"""
计算E/P因子(市盈率倒数)
输入:df 包含 market_cap, net_profit 字段
"""
# 避免负值干扰
df['ep'] = df['net_profit'] / df['market_cap']
# 行业中性化处理
df['ep_neutral'] = df.groupby('industry')['ep'].transform(
lambda x: (x - x.mean()) / x.std()
)
# 市值中性化(可选)
df['ep_resid'] = stats.linregress(
df['log_market_cap'], df['ep_neutral']
).resid
return df['ep_resid']
嗯,这里要注意几个坑:
- 极端值处理:我一般用MAD方法(中位数绝对偏差)去极值,比3σ更稳健
- 缺失值处理:千万别直接dropna,会引入生存偏差。我习惯用行业均值填充
- 频率对齐:日频因子和月频因子不能混用,要统一到同一个调仓频率
我的小技巧:因子构建完成后,先跑一遍描述性统计。看看均值、标准差、偏度、峰度。如果某个因子的偏度绝对值大于2,那大概率有异常值没处理好。
三、因子有效性检验:IC/IR 分析
因子构建完了,怎么知道它有没有用?这就轮到IC和IR上场了。
3.1 IC(信息系数)
IC衡量的是因子值和未来收益之间的相关性。简单说:因子值高的股票,未来是不是涨得更好?
IC的计算方式有两种:
- Spearman秩相关系数:我比较推荐这个,因为它对异常值不敏感
- Pearson相关系数:要求数据正态分布,实际中很少满足
def calc_ic(factor_values, forward_returns):
"""
计算截面IC
factor_values: 某期所有股票的因子值
forward_returns: 对应股票的未来收益
"""
# 使用Spearman秩相关
ic, p_value = stats.spearmanr(factor_values, forward_returns)
return ic, p_value
# 计算时间序列IC
def calc_ic_series(factor_df, return_df):
"""
计算多期IC序列
"""
ic_list = []
for date in factor_df.index:
ic, _ = calc_ic(
factor_df.loc[date],
return_df.loc[date]
)
ic_list.append(ic)
return pd.Series(ic_list, index=factor_df.index)
3.2 IR(信息比率)
IR = IC的均值 / IC的标准差。它衡量的是因子稳定性的指标。
举个例子:
- 因子A:IC均值0.05,标准差0.02 → IR = 2.5(优秀)
- 因子B:IC均值0.08,标准差0.10 → IR = 0.8(不稳定)
因子A虽然IC均值低一些,但非常稳定。因子B偶尔表现很好,但波动太大。实盘中我更倾向于因子A。为什么?因为稳定的因子更容易做仓位管理和风险控制。
我曾经踩过的坑:只看IC均值,忽略了IR。结果选了个IC均值0.12但IR只有0.5的因子。实盘跑了一个月,前两周赚得飞起,后两周亏回去还倒贴。从那以后,我给自己定了个规矩:IR低于1的因子,坚决不上实盘。
3.3 分层回测
除了IC/IR,我还会做分层回测。把股票按因子值分成10组,看每组未来的平均收益。如果第1组(因子值最高)的收益明显高于第10组(因子值最低),而且呈现单调性,那这个因子就靠谱。
def layer_backtest(factor_df, return_df, n_layers=10):
"""
分层回测
"""
results = {}
for date in factor_df.index:
# 按因子值分组
factor_series = factor_df.loc[date]
labels = pd.qcut(factor_series, n_layers, labels=False)
# 计算每组平均收益
group_returns = return_df.loc[date].groupby(labels).mean()
results[date] = group_returns
return pd.DataFrame(results).T
四、知识体系总览
说了这么多,我把整个多因子选股的核心逻辑画了张图,方便你理解:
五、实战中的几个关键点
最后,分享几个我在实战中总结的经验:
- 因子要定期重检:市场在变,因子的有效性也会衰减。我每季度会重新跑一遍IC/IR,把失效的因子剔除。
- 警惕数据挖掘偏差:你测试了100个因子,总有几个IC显著。但这可能是巧合。我习惯用滚动窗口验证——用过去3年的数据选因子,用未来1年的数据验证。
- 因子组合要分散:别把所有仓位押在一个因子上。我一般选5-8个低相关的因子,等权或按IR加权组合。
- 别忘了交易成本:有些因子换手率很高,看起来IC不错,但扣掉手续费就亏了。我每次回测都会加上千分之二的交易成本。
一句话总结:多因子选股不是堆砌指标,而是找到那些逻辑清晰、统计显著、稳定有效的因子,然后用纪律性的方式组合起来。