Python 多因子策略入门

前面所有策略都在时序维度打转:盯着一只股票的历史行情找信号。多因子策略切换到"截面"维度:同一时刻给全市场股票打分,买得分高的、卖得分低的。

这是量化机构的主流框架,本篇用 20 只 A 股的小股票池,完整走一遍因子构建 - IC 检验 - 分层回测的标准流程。结果同样照实呈现——包括一个失效的动量因子。


什么是因子

因子是我们相信能预测未来收益的股票特征。业内常用的因子大类:

因子大类 代表因子 逻辑
动量 过去 6-12 个月涨幅 强者恒强(海外成熟市场)
价值 市盈率、市净率倒数 便宜的股票被低估
质量 ROE、毛利率、负债率 好公司值得溢价
规模 总市值(小市值因子) 小公司弹性大

本篇以最直观的动量因子为教学案例:用过去 120 日(约 6 个月)的涨幅给股票打分。

实例

import yfinance as yf
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 组,等权持有一个月,看各组收益是否有序排列。

实例

layers = {i: [] for i in range(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%

把五组的累计净值画出来:

实例

import matplotlib.pyplot as plt

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 等专业工具,但流程与本篇完全一致。