Python 多因子策略入门
前面所有策略都在时序维度打转:盯着一只股票的历史行情找信号。多因子策略切换到"截面"维度:同一时刻给全市场股票打分,买得分高的、卖得分低的。
这是量化机构的主流框架,本篇用 20 只 A 股的小股票池,完整走一遍因子构建 - IC 检验 - 分层回测的标准流程。结果同样照实呈现——包括一个失效的动量因子。
什么是因子
因子是我们相信能预测未来收益的股票特征。业内常用的因子大类:
| 因子大类 | 代表因子 | 逻辑 |
|---|---|---|
| 动量 | 过去 6-12 个月涨幅 | 强者恒强(海外成熟市场) |
| 价值 | 市盈率、市净率倒数 | 便宜的股票被低估 |
| 质量 | ROE、毛利率、负债率 | 好公司值得溢价 |
| 规模 | 总市值(小市值因子) | 小公司弹性大 |
本篇以最直观的动量因子为教学案例:用过去 120 日(约 6 个月)的涨幅给股票打分。
实例
import pandas as pd
# 20 只跨行业 A 股股票池(消费/金融/医药/新能源/科技等)
pool = ["600519.SS","000858.SZ","000568.SZ","601398.SS","600036.SS",
"601318.SS","600276.SS","300760.SZ","300750.SZ","002594.SZ",
"300274.SZ","002415.SZ","000063.SZ","000651.SZ","000333.SZ",
"601668.SS","601899.SS","600309.SS","600900.SS","688981.SS"]
close = yf.download(pool, start="2022-01-01", end="2024-12-31",
progress=False, multi_level_index=False)["Close"]
# 动量因子:过去 120 日涨幅(每天一行、每只股票一列的因子值矩阵)
momentum = close / close.shift(120) - 1
print("因子矩阵形状:", momentum.shape)
IC:因子预测力的体检报告
因子好不好,不能靠信仰,要算 IC(Information Coefficient):每月末,计算"因子值"与"下月收益"的截面相关系数。
IC 为正且稳定,说明因子值高的股票下月确实涨得多;IC 接近 0,因子就是玄学。
实例
me = close.resample("ME").last()
mom_m = momentum.resample("ME").last()
fwd = me.shift(-1) / me - 1 # 下月收益
ics = []
for d in mom_m.index[7:-1]: # 前 6 个月留作动量预热
f = mom_m.loc[d].dropna()
fr = fwd.loc[d].dropna()
common = f.index.intersection(fr.index)
if len(common) >= 15:
# Spearman 秩相关:对极端值更稳健
ics.append(f.loc[common].corr(fr.loc[common], method="spearman"))
ics = pd.Series(ics)
print(f"月度 IC 样本数 {len(ics)} | IC 均值 {ics.mean():.4f} | IC 标准差 {ics.std():.4f}")
print(f"ICIR = {ics.mean()/ics.std():.4f} | IC>0 占比 {(ics>0).mean():.2%}")
输出结果如下所示:
月度 IC 样本数 28 | IC 均值 -0.0639 | IC 标准差 0.3561 ICIR = -0.1795 | IC>0 占比 46.43%
结果出人意料又在情理之中:IC 为负。在这 20 只 A 股、2022-2024 的样本里,过去涨得多的股票下月反而更容易跌。
| 指标 | 本例 | 通常认为 |
|---|---|---|
| IC 绝对值 | 0.064 | 大于 0.03 即有信息量 |
| IC 方向 | 负(反转效应) | 动量因子在美股通常为正 |
| ICIR | -0.18 | 绝对值大于 0.3 较稳定 |
这正是 A 股的著名特征:短期动量弱、反转效应强,大量学术与实务研究得到过同样的结论。"动量因子在美股有效"的知识照搬到 A 股,就会被数据打脸。
IC 为负不等于白干——负 IC 因子反向使用就是正 IC。用分层回测验证这个发现。
分层回测:把因子变成组合
分层回测是因子检验的"实弹演习":每月末按因子值把股票分成 5 组,等权持有一个月,看各组收益是否有序排列。
实例
for d in mom_m.index[7:-1]:
f = mom_m.loc[d].dropna()
fr = fwd.loc[d].dropna()
common = f.index.intersection(fr.index)
if len(common) < 15:
continue
ranked = f.loc[common].rank(pct=True)
for i in range(5):
# 第 i 组:因子排名处于 (i/5, (i+1)/5] 区间的股票
grp = ranked[(ranked > i * 0.2) & (ranked <= (i + 1) * 0.2)].index
layers[i].append(fr.loc[grp].mean())
layer_ret = pd.DataFrame(layers, index=mom_m.index[7:-1])
print("各组月均收益:")
for i in range(5):
print(f"第 {i+1} 组: {layer_ret[i].mean():.4%}/月")
q1 = (1 + layer_ret[0].mean()) ** 12 - 1
q5 = (1 + layer_ret[4].mean()) ** 12 - 1
print(f"第1组年化 {q1:.2%} | 第5组年化 {q5:.2%} | 多空年化差 {q5-q1:.2%}")
输出结果如下所示:
各组月均收益: 第 1 组(动量最低): 1.5296%/月 第 2 组: 1.4587%/月 第 3 组: 1.5851%/月 第 4 组: 0.8594%/月 第 5 组(动量最高): 0.3194%/月 第1组年化 19.98% | 第5组年化 3.90% | 多空年化差 -16.08%
把五组的累计净值画出来:
实例
cum_layers = (1 + layer_ret.fillna(0)).cumprod()
fig, ax = plt.subplots(figsize=(12, 6))
colors = ["#c0563f", "#e0a458", "#8a95a1", "#3a9d8f", "#14805e"]
for i in range(5):
label = f"Group {i+1}" + (" (Low Momentum)" if i == 0
else " (High Momentum)" if i == 4 else "")
ax.plot(cum_layers.index, cum_layers[i],
color=colors[i], linewidth=1.5, label=label)
ax.set_title("Momentum Factor: Layered Portfolio Cumulative Return (2022-2024)")
ax.set_xlabel("Date")
ax.set_ylabel("Cumulative Return")
ax.legend()
ax.grid(alpha=0.25)
plt.show()
执行以上代码,输出的图表如下:

五组曲线总体呈现"动量越低表现越好"的分层——与负 IC 完全一致,两条独立证据链互相印证。
本篇的方法论价值
找到一个失效的因子,比看到一个赚钱的回测更有价值:
| 步骤 | 本篇实践 | 意义 |
|---|---|---|
| 构建因子 | 120 日动量 | 把想法变成可计算的数字 |
| IC 检验 | 均值 -0.064,ICIR -0.18 | 用数据而非信仰判断有效性 |
| 分层回测 | 五组单调性清晰 | 验证因子与收益的截面关系 |
| 结论 | A 股 2022-2024 动量反转 | 因子有市场与时期依赖,必须本地化检验 |
提醒:本篇样本只有 20 只股票、3 年数据,结论不能直接外推到全市场。工程实践中会扩展到全 A 股、加入行业市值中性化、使用 alphalens 等专业工具,但流程与本篇完全一致。
点我分享笔记