之前探索了因子权重收缩Shrinkage算法,即权重分配问题。
https://blog.csdn.net/liliang199/article/details/164584643
这里进一步探索本期到底买哪些因子,即品种准入。
深度拆解其执行逻辑,并重点梳理背后六大核心计量理论。
1 代码示例
如下代码实现了一个基于信号强度的稀疏选股,不是简单的挑出最大的 K 个数。
通过基于信号强度的稀疏选股,只保留当期最笃定的少数派意见,
同时严守多空方向,为后续的权重收缩提供干净、高信噪比的候选因子池。
这是现代多因子模型中因子择时与因子动量策略的基石实现。
def select_topk_weight_matrix(
score_matrix: pd.DataFrame,
k: int,
) -> pd.DataFrame:
"""
Per-date top-k factor selection expressed as a weight matrix.
On each date T, the k factors with the largest |score(T, f)| are kept as
weights (sign preserved --- the score's sign orients the factor); all other
factors are set to exactly 0 and thus drop out of the composite. NaN
scores (rolling warm-up) stay NaN, so all-NaN rows still produce NaN
composites downstream. Ties are broken by column order (deterministic).
Feed the result to combine_factors_rolling_weight (optionally through
shrink_weights_to_equal for a shrunk variant) to get a fully
out-of-sample, dynamically re-selected composite.
"""
s = score_matrix.astype(float)
abs_rank = s.abs().rank(axis=1, ascending=False, method="first")
keep = abs_rank <= k
return s.mask(~keep & s.notna(), 0.0)
2 代码执行逻辑
1)取绝对值排序
对每个时点(行),按因子得分(如 ICIR)的绝对值从大到小降序排名。
2)截断保留
只保留排名前 k 名的因子(keep = True)。
3)符号保留删除
未进入前 k 名的因子,若其非空,则强制设为 0;
4)至关重要的细节
进入前 k 名的因子,其原始数值(含正负号)原封不动保留,而非取绝对值。
s = score_matrix.astype(float)
abs_rank = s.abs().rank(axis=1, ascending=False, method="first")
keep = abs_rank <= k
return s.mask(~keep & s.notna(), 0.0)
2 背后金融知识
1)基于信号强度的稀疏化
为什么按绝对值排名,而不是按原始值?
在因子投资中,预测能力的大小(强度)取决于 |IC|,而方向(正负)只代表多空倾向。
绝对值排名意味着,无论该因子当前是强烈看多(+0.8)还是强烈看空(-0.8),
只要它预测得准,就允许它进入组合。
这本质上是选取统计显著性最高的前 K 个因子,剔除那些预测能力模棱两可(接近 0)的噪音因子。
2)固定K值的自适应阈值
传统的固定阈值法(如|score|>0.2)在市场高波动期会选入过多因子,在低波动期选入过少。
固定 K 值(如 K=10)强行保证了每期组合的分散化程度恒定(始终保持 K 个因子敞口)。
这在风险管理中极其重要,防止因子总数剧烈波动导致组合换手率失控和非系统性风险忽大忽小。
3)方向不变性原则
代码保留 s 的原始符号,而非取abs(s)填充。
因子得分(如 ICIR)的正负代表了该因子当前的预期收益方向。
例如:若某估值因子的 ICIR 为 -0.6,
说明近期低估值因子表现极差,逻辑发生反转,策略应该反向做空该因子。
这种处理保留了因子择时(Factor Timing)的能力,
允许聪明的资金在因子风格切换时迅速调整多空头寸,而不是机械地认为高因子得分等于做多。
4)确定性平局与回测可重复
method=first配合列顺序(Column Order)打破平局。
核心价值是在实盘或回测中,当两个因子的
|score|完全相等(罕见但存在),如果不指定规则,排名会因浮点运算或并行计算产生随机性,导致组合非确定性漂移。
采用列顺序(通常是因子入库的固定顺序)作为次级排序标准。
确保了无论运行多少次,同一时点选出的因子子集完全一致。
这是量化回测工程化的底线要求,避免幽灵路径。
5)NaN与0的本质区别
NaN与0的本质区别 是信息缺失vs信息无效
s.mask(~keep & s.notna(), 0.0) 意味着,
未选中的因子强制归零;但NaN值被坚决保留为 NaN,不会被替换为 0。
其金融底层逻辑为
0代表"我评估过了,但我不看好/不选它",属于主动剔除。
NaN代表"滚动窗口尚未覆盖,我没法评估它",属于数据缺失。
在滚动训练(Rolling Window)的早期Warm-up阶段,许多因子还没有足够的回测期来计算ICIR。
如果将 NaN 误填为 0,模型会误以为该因子"中性无效",从而错误地分配先验权重。
保留 NaN 使得下游函数(如上一讲的 shrink_weights_to_equal)能将整行标记为NaN,
彻底屏蔽不可用时段,这是防止前视偏差和生存者偏差的防火墙。
即便某个因子在历史回测中表现极佳(原始 ICIR 很高),
如果它今天没排进前 K 名,它在收缩后的最终权重严格为 0。
这相当于引入了一个硬性门槛,杜绝了虽然信号微弱,但靠收缩硬凑一点仓位的可能性,
大幅降低了垃圾因子的污染风险。
reference
因子权重收缩Shrinkage算法的探索和分析