最大化 ICIR 的滚动训练多因子选股策略
由bq5973r5创建,最终由neoblackxt 被浏览 47 用户
1. 策略概览
本策略面向 A 股沪深300成分股,采用多因子选股 + 指数增强思路:每次调仓前都用过去一段训练窗口的历史数据,重新训练因子权重,目标是让组合信号的 ICIR(Information Coefficient Information Ratio)最大化,从而得到更稳定的预测信号,并据此构建可交易组合。
策略特点:
- 滚动训练:权重不是固定的,每个交易日都可重新训练,适应市场风格变化;
- 显式最大化ICIR:权重通过优化器直接提升 ICIR,而不是仅计算后展示;
- 可交易:股票池过滤可交易性,TopN 等权持仓,按固定周期调仓,并计入手续费。
2. 股票池与数据过滤
数据来源:cn_stock_prefactors(BigQuant预处理因子库)
股票池及过滤条件(SQL中体现):
- is_hs300 = 1:沪深300范围;
- st_status = 0:剔除 ST;
- suspended = 0:剔除停牌,保证可交易;
- pe_ttm > 0、pb > 0:估值因子有效;
- roe_avg_ttm IS NOT NULL:ROE有效。
这些过滤的目的不是“提升回测”,而是保证信号在实盘环境可落地:数据有效、标的可交易。
3. 因子体系与方向统一
策略使用 5 个因子,分别覆盖价值、质量、拥挤度、动量:
- 价值:pe_ttm、pb
- 质量:roe_avg_ttm
- 拥挤度:turn(换手率)
- 动量:momentum_5
训练阶段将因子方向统一为“越大越好”,便于权重解释:
- f1 = -1/pe_ttm(PE越低越好)
- f2 = -pb(PB越低越好)
- f3 = roe_avg_ttm(ROE越高越好)
- f4 = -turn(换手越低越不拥挤)
- f5 = momentum_5(动量越高越好)
4. 训练标签:未来 H 日收益(排序预测)
策略的预测周期为 H=5(未来5个交易日),训练标签定义为:
代码实现:
df_all['fwd_ret'] = df_all.groupby('instrument')['close'].shift(-H) / df_all['close'] - 1
说明:
- shift(-H) 把未来第5个交易日的收盘价对齐到今天这一行;
- 得到的是“从今天买入持有到第5天”的区间收益;
- 策略并不追求对收益率做点预测,而是通过因子对股票做排序,判断“谁更可能在未来5天表现更好”。
5. IC、ICIR 与训练数据结构
5.1 每日IC的计算
在训练窗口内,对每个交易日做一次横截面计算:
- 对每个因子做截面 rank(分位排序)以增强稳健性;
- 计算因子 rank 与未来收益 fwd_ret 的相关系数,得到当天该因子的 IC。
代码核心:
df_day[f'{col}_rank'] = df_day[col].rank(pct=True)
ic = df_day[f'{col}_rank'].corr(df_day['fwd_ret'])
这样,每一天会得到一个 5 维 IC 向量;过去 TRAIN_WINDOW=252 天会堆成一个 IC 矩阵:
- ic_mat 形状约为 [N_days, 5]
- 行:日期;列:5个因子
5.2 μ 与 Σ(协方差矩阵)的意义
对 ic_mat 做统计估计:
- μ:各因子 IC 的均值(有效性)
- Σ:因子 IC 的协方差矩阵(稳定性 + 重复信息结构)
代码实现:
mu = np.nanmean(ic_mat, axis=0) # shape [5]
cov = np.cov(ic_mat.T, ddof=1) # shape [5,5]
解释:
- μ 越大:该因子在过去窗口里平均更“有用”;
- Σ 对角线:因子IC波动大不大(稳不稳);
- Σ 非对角线:因子是否经常一起强/一起弱(信息重复程度)。
6. 目标函数:用 μ 与 Σ 计算 ICIR(w)
组合因子的 ICIR 通过二次型写成:
对应代码函数 icir_from_mu_cov:
- 分子 mu @ w:组合IC均值
- 分母 sqrt(w @ cov @ w):组合IC波动
该形式的好处是:不需要每次都回到全样本序列重新计算ICIR,可以直接在 μ/Σ 层面优化权重。
7. 权重约束:sum(|w|)=1(L1 归一化)
由于 ICIR 对权重整体缩放不敏感(w 乘常数,分子分母同比放大),优化过程中权重规模可能漂移,因此加入约束:
实现函数 project_l1:
w = w / np.sum(np.abs(w))
这使权重规模可控、便于解释,也更贴近实盘“总风险暴露”固定的风格。
8. 显式最大化ICIR:梯度上升 + 回溯步长
8.1 初始权重 w0:解 Σ·w = μ
为了加速收敛,先用线性方程求一个“方向很合理”的初值:
代码实现(含奇异矩阵兜底):
try:
w0 = np.linalg.solve(cov, mu)
except np.linalg.LinAlgError:
w0, *_ = np.linalg.lstsq(cov, mu, rcond=None)
w0 = project_l1(w0)
说明:这一步给的是“好起点”,最终权重仍然通过后续优化得到。
8.2 maximize_icir:从 w0 出发把 ICIR 推到最大
优化器目标:
方法:
- 计算 ICIR 的解析梯度;
- 用 w ← w + η·grad 做梯度上升;
- 采用回溯步长:若更新后ICIR不升,就把步长减半;
- 每次更新后执行 project_l1 以保持约束。
关键点:
- 初始步长 lr=0.2,若不提升则依次变为 0.1、0.05...(最多尝试20次)
- 迭代上限 max_iter=200
- 提升幅度小于 tol=1e-9 认为收敛
调用处(滚动训练里):
w, icir_mu_cov = maximize_icir(mu, cov, w0=w0, max_iter=200, lr=0.2, tol=1e-9)
9. 滚动训练主循环(权重随时间变化)
训练窗口长度 TRAIN_WINDOW=252,对回测期每个日期 current_date:
- 取过去252天作为训练集;
- 每天计算5维IC向量,形成 ic_mat;
- 估计 μ 与 Σ;
- 求初值 w0,并显式最大化ICIR得到 w*;
- 把 w* 存入 weights_history[current_date],供后续打分选股。
同时,为了便于监控与解释,代码还计算了“基于真实组合IC序列”的ICIR:
ic_combo = ic_mat @ w
icir = np.nanmean(ic_combo) / np.nanstd(ic_combo)
日志里同时输出两种ICIR:
- icir_mu_cov:用 μ/Σ 二次型公式算的(优化器口径)
- icir:用训练窗口真实序列算的(直观口径)
二者应方向一致,差异主要来自样本估计误差与协方差估计方式。
10. 生成交易信号:打分、TopN、等权、调仓
训练得到每日权重后,进入交易信号生成:
- 取回测期因子数据,并对每个因子做横截面标准化 c_normalize,保证量纲一致;
- 综合得分:
- 选择 TOPN=30 只股票,等权配置 1/TOPN;
- 用 TradingDaysRebalance(REBALANCE_DAYS=5) 只在调仓日输出持仓,降低换手、对齐预测周期。
这套策略用“未来5日收益”作为训练目标,在每次调仓前用过去一年滚动估计因子IC分布,通过显式优化器直接最大化组合因子的ICIR,从而得到更稳定的综合信号,并据此构建Top30等权、5日调仓、计入成本的可交易组合。
https://bigquant.com/codesharev3/47104882-5666-4a34-9a46-ab7a1b17eb9a
\