第四节:单因子测试框架:IC分析、Rank IC与分组回测

好,我们进入正题。

因子构建出来之后,第一件事不是急着上策略,而是问自己一个问题:这个因子到底有没有预测能力?

我见过太多人,写了几十行代码算出个因子,直接扔进回测引擎,结果曲线漂亮得不行——一查,全是未来函数。或者更隐蔽的:因子本身跟收益根本没关系,纯粹是过拟合。

所以,我们需要一套标准化的检验流程。今天聊的三个工具:IC分析、Rank IC分析、分组回测,就是这套流程的基石。

核心逻辑一句话:IC看线性相关性,Rank IC看单调性,分组回测看实际赚钱能力。三者缺一不可。

单因子测试框架核心流程 因子数据准备 IC分析(信息系数) Rank IC分析 分组回测法(验证单调性) 因子有效性结论 Pearson相关系数 Spearman秩相关系数 多空组合收益、单调性检验

4.1 IC分析:信息系数

IC,全称 Information Coefficient,信息系数。说白了就是因子值与未来收益之间的相关系数

我习惯用 Pearson相关系数 来计算。公式很简单:

IC = corr(因子值, 未来一期收益率)

具体到代码实现,我一般这么写:

import pandas as pd
import numpy as np
from scipy.stats import pearsonr

def calc_ic(factor_series, return_series):
    """
    计算截面IC
    factor_series: 某一天的因子值(所有股票)
    return_series: 对应股票的未来收益率
    """
    # 去极值、标准化处理后的数据
    clean_factor = winsorize(factor_series, limits=[0.01, 0.99])
    clean_return = winsorize(return_series, limits=[0.01, 0.99])
    
    # 剔除缺失值
    mask = clean_factor.notna() & clean_return.notna()
    
    ic, p_value = pearsonr(clean_factor[mask], clean_return[mask])
    return ic, p_value

我的经验:IC值在 -0.1 到 0.1 之间是常态。别指望IC能到0.5以上——那基本意味着你的因子跟未来收益有某种线性关系,但现实中很少见。我见过最夸张的一次,IC跑到0.3,后来发现是数据对齐出了问题。

IC分析的核心指标有两个:

  • IC均值:看因子整体方向。正数表示因子值越大收益越高,负数则相反。
  • IC标准差:看因子稳定性。标准差越小,因子表现越稳定。

还有一个常用指标叫 IR(信息比率)

IR = IC均值 / IC标准差

IR大于0.5算不错,大于1.0就算很好了。我一般用IR来筛选因子——IR太低的因子,就算IC均值还行,实际用起来也容易翻车。

4.2 Rank IC分析:秩相关系数

Pearson IC有个问题:它对异常值特别敏感。你想想看,如果某只股票因为停牌或者数据错误,因子值突然飙到天上,Pearson相关系数立马就被带偏了。

所以,我更常用的是 Rank IC,也就是Spearman秩相关系数。

Rank IC的计算逻辑:

  1. 把因子值排序,变成秩(rank)
  2. 把未来收益率也排序,变成秩
  3. 计算这两个秩之间的Pearson相关系数

代码实现:

from scipy.stats import spearmanr

def calc_rank_ic(factor_series, return_series):
    """
    计算截面Rank IC
    """
    mask = factor_series.notna() & return_series.notna()
    
    rank_ic, p_value = spearmanr(
        factor_series[mask], 
        return_series[mask]
    )
    return rank_ic, p_value

为什么Rank IC更靠谱?

因为秩只关心相对顺序,不关心具体数值。哪怕某只股票的因子值是100倍于其他股票,它的秩也只是第1名而已。这就天然过滤掉了极端值的影响。

我个人的习惯是:同时看IC和Rank IC。如果两者方向一致,说明因子关系比较稳健。如果IC为正但Rank IC为负,那就要小心了——很可能存在非线性关系或者异常值干扰。

举个例子,我曾经遇到一个因子,IC算出来0.08,看起来不错。但Rank IC只有0.01,几乎为零。后来一查,原来是某只小市值股票因子值异常高,拉动了整个相关性。去掉这只股票后,IC直接掉到0.02。嗯,这就是典型的「伪相关」。

4.3 分组回测法

IC和Rank IC说到底都是统计量。但做量化的人都知道:统计显著不等于交易赚钱

分组回测,就是直接模拟交易,看因子在实际操作中能不能赚钱。

做法很简单:

  1. 在每个调仓日,把所有股票按因子值从大到小排序
  2. 分成N组(通常5组或10组)
  3. 每组等权或市值加权买入,持有到下一个调仓日
  4. 计算每组的累计收益

代码框架:

def group_backtest(factor_df, return_df, n_groups=5):
    """
    分组回测
    factor_df: 日期×股票的因子值矩阵
    return_df: 日期×股票的收益率矩阵
    """
    results = {}
    
    for date in factor_df.index:
        # 获取当天的因子值和未来收益
        factors = factor_df.loc[date]
        returns = return_df.loc[date]
        
        # 分组
        groups = pd.qcut(factors, n_groups, labels=False)
        
        # 计算每组收益
        for g in range(n_groups):
            mask = (groups == g)
            group_return = returns[mask].mean()
            results.setdefault(g, []).append(group_return)
    
    # 计算累计收益
    cum_returns = {}
    for g, ret_list in results.items():
        cum_returns[g] = (1 + pd.Series(ret_list)).cumprod()
    
    return cum_returns

注意:分组回测最容易踩的坑是「幸存者偏差」。我刚开始做的时候,直接用当前存续的股票做回测,结果曲线漂亮得不像话。后来才发现,那些退市的股票全被我忽略了——而它们恰恰是亏钱的主力。

正确的做法是:使用历史快照数据,也就是每个时间点实际存在的股票池。

分组回测的核心看什么?

  • 单调性:第1组收益 > 第2组 > ... > 第N组?如果是,说明因子有很好的区分度。
  • 多空收益:第1组收益 - 第N组收益。这个差值越大越好。
  • 分组稳定性:各组收益是否随时间稳定,还是忽高忽低?

我一般会画一张分组收益曲线图,把5条线叠在一起看。如果第1组始终在最上面,第5组始终在最下面,中间各组依次排列——嗯,这个因子基本稳了。

但如果曲线交叉得一塌糊涂,比如第3组突然跑到最上面去了——那说明因子存在明显的非线性关系,或者干脆就是噪声。

4.4 三者如何配合使用?

我自己的流程是这样的:

步骤 方法 目的 通过标准
1 IC分析 快速筛查,看因子是否有线性预测能力 IC均值绝对值 > 0.02,IR > 0.5
2 Rank IC分析 排除异常值干扰,验证单调性 Rank IC方向与IC一致,绝对值 > 0.02
3 分组回测 模拟真实交易,验证实际赚钱能力 分组单调性明显,多空收益显著

三步都通过了,我才会把这个因子放进候选池。但凡有一项不过,我都会标记为「待观察」——不急着用,也不急着扔,等更多数据出来再说。

一个小技巧:我习惯把IC和Rank IC画成时间序列图,按月滚动计算。这样能直观看到因子的「保质期」——有些因子前两年IC很高,后面突然失效了。这种因子我一般不会用,因为不知道它什么时候会再次失效。

好了,单因子测试框架的核心内容就这些。IC看线性关系,Rank IC看秩相关,分组回测看实战效果。三者配合,基本能筛掉90%的垃圾因子。

剩下的10%,才是真正值得深入研究的。


专注资料整理