把多个因子合成一个选股分数,有很多种加权方式。最常用、也最容易被问到的三种:
- 等权:每个因子各占 50%,简单直接。
- IC 加权:因子过去预测能力越强,权重越高。
- 最大化 IR:在 IC 基础上再考虑稳定性,IC 均值高且波动小的因子权重更高。
本文用 30 只大盘股截面 + 60 日动量 + 20 日低波因子,把三种方案跑一遍,看它们对最终选股的差异。
一、为什么加权方式会影响结果
假设你已经有了两个因子:
mom_60d:60 日动量(不含最近 5 天,避开短期反转)。vol_20d:20 日年化波动率,低波通常更稳,所以取-vol_20d作为得分方向。
如果不加权,两个因子量纲不同(一个是收益率,一个是波动率),直接相加没意义。通常先做 截面 z-score 标准化,再按权重求和:
text
score = w1 * z(mom) + w2 * z(-vol)
不同的 w1 / w2 就是不同的加权哲学。
二、本文你将得到什么
- 三种加权方案的权重计算公式。
- 一段可运行的 Python 代码,直接替换证书就能跑。
- 30 只股票截面的分组收益对比(Top 5 vs Bottom 5)。
- 一个常见坑:IC 加权不一定总是赢。
三、三种加权方案
| 方案 | 权重逻辑 | 公式 |
|--------|---------------------|---------------------------|------|---------------------------|
| 等权 | 因子一视同仁 | w1 = w2 = 0.5 |
| IC 加权 | 按 | IC | 占比分配 | wi = |IC_i| / Σ|IC_j| |
| 最大化 IR | 按 IR = IC均值 / IC标准差 | wi = |IR_i| / Σ|IR_j| |
IC(Information Coefficient)用 Spearman 秩相关 计算:因子值与未来收益的相关性。|IC| 越大,预测能力越强。
四、可运行代码
python
import pandas as pd
import numpy as np
from scipy.stats import spearmanr
from mairui import Client
# 公开演示证书;实际项目请换成自己的正式证书
client = Client("LICENCE-66D8-9F96-0C7F0FBCD073")
UNIVERSE = ["600519", "000001", "000002", "600036", "601318",
"601398", "600276", "000333", "600030", "601166"]
def fetch_panel():
rows = []
for code in UNIVERSE:
raw = client.stock_history(code, lt=120, dividend="n")
df = pd.DataFrame(raw)
df["code"] = code
rows.append(df[["code", "t", "c"]])
return pd.concat(rows)
def momentum(s):
return s.iloc[-6] / s.iloc[-66] - 1 # 60 日,跳过最近 5 日
def volatility(s):
return s.pct_change().tail(20).std() * np.sqrt(252)
def zscore(x):
return (x - x.mean()) / x.std()
panel = fetch_panel()
snap = []
for code, df in panel.groupby("code"):
df = df.sort_values("t")
snap.append({
"code": code,
"mom": momentum(df["c"]),
"vol_score": -volatility(df["c"]),
"fwd": df["c"].iloc[-1] / df["c"].iloc[-21] - 1,
})
snap = pd.DataFrame(snap).dropna()
ic_mom, _ = spearmanr(snap["mom"], snap["fwd"])
ic_vol, _ = spearmanr(snap["vol_score"], snap["fwd"])
# 三种权重
w_equal = {"mom": 0.5, "vol": 0.5}
abs_sum = abs(ic_mom) + abs(ic_vol)
w_ic = {"mom": abs(ic_mom) / abs_sum, "vol": abs(ic_vol) / abs_sum}
ir_mom, ir_vol = ic_mom / 0.05, ic_vol / 0.05 # 示例 std,仅演示公式
ir_sum = abs(ir_mom) + abs(ir_vol)
w_ir = {"mom": abs(ir_mom) / ir_sum, "vol": abs(ir_vol) / ir_sum}
for name, w in [("等权", w_equal), ("IC加权", w_ic), ("Max-IR", w_ir)]:
snap["score"] = w["mom"] * zscore(snap["mom"]) + w["vol"] * zscore(snap["vol_score"])
top5 = snap.nlargest(5, "score")["fwd"].mean() * 100
bot5 = snap.nsmallest(5, "score")["fwd"].mean() * 100
print(f"{name}: top5={top5:+.2f}% bot5={bot5:+.2f}% spread={top5-bot5:+.2f}%")
注意:
stock_history的返回字段以官方文档为准;演示证书可能返回有限数据,替换为正式证书后即可获得完整截面。
五、运行结果示例
text
等权 : top5=+0.22% bot5=-1.58% spread=+1.80%
IC加权 : top5=-2.77% bot5=-1.23% spread=-1.54%
Max-IR : top5=-2.77% bot5=-1.23% spread=-1.54%
本例中 等权反而更稳。原因是演示数据下低波因子 IC 接近 0,IC/Max-IR 加权把权重过度集中在动量上,导致组合失衡。实际交易中,因子权重需要结合样本外滚动 IC 与稳健性一起调。
六、常见坑
- IC 只是样本内指标:历史 IC 高不等于未来 IC 高,建议用滚动窗口跟踪。
- 权重不要过度集中:单一因子权重超过 70% 时,组合风险会逼近该单因子。
- 方向要先定对:波动率、换手率这类因子通常是"低者更好",合成前要先取负号,否则权重越高的方向越错。
七、小结
- 等权是最鲁棒的起点,适合因子同质化或 IC 差异不大时。
- IC 加权 / Max-IR 加权更适合"你已经信任历史 IC 统计"的场景。
- 真正上线前,应该用滚动 IC、样本外回测、相关性去重再校验一次。
下一篇:因子去重 ------ 相关性高的因子留哪个?
免责声明
本文仅供技术学习与交流,不构成投资建议。文中示例数据不代表实时行情,投资有风险,入市需谨慎。
代码与文档
源码与接口文档见: github.com/MaiRuiApi