3. 单因子分析:IC分析、IR分析、分组回测、因子收益率计算

单因子分析,说白了就是给每个因子做「体检」。

我刚开始做量化那会儿,总觉得因子越多越好,恨不得把几百个因子全塞进模型里。结果呢?过拟合得一塌糊涂,实盘跑起来跟过山车似的。后来我才明白——因子不在多,在于精。而精不精,就得靠单因子分析来把关。

3.1 因子分析的核心逻辑

先看一张图,帮你快速建立整体认知:

单因子分析核心流程 原始因子数据 数据预处理(去极值、中性化) 四大分析维度 IC分析 预测能力评估 IR分析 稳定性评估 分组回测 收益区分度 因子收益率 多空收益 综合判断:因子是否有效?是否可纳入模型?

这张图把单因子分析的四个维度串起来了。接下来我们一个一个拆开讲。

3.2 IC分析:因子与收益的相关性

IC(Information Coefficient),就是因子值与未来收益的相关系数。它回答一个核心问题:这个因子到底能不能预测涨跌?

我个人习惯用两种IC:

  • Spearman秩相关系数:只看排序关系,对极端值不敏感。我一般用这个做主力。
  • Pearson相关系数:看线性关系,但容易被极端值带偏。偶尔用来做交叉验证。

IC的行业标准

  • |IC| > 0.05:有预测能力
  • |IC| > 0.10:预测能力较强
  • |IC| > 0.15:非常优秀(但少见)

来看代码实现:

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

def calc_ic(factor_series, return_series, method='spearman'):
    """
    计算IC值
    factor_series: 因子值(某期截面)
    return_series: 未来N期收益
    """
    # 去空值
    mask = factor_series.notna() & return_series.notna()
    f = factor_series[mask]
    r = return_series[mask]
    
    if method == 'spearman':
        ic, p_value = spearmanr(f, r)
    else:
        ic = f.corr(r, method='pearson')
        p_value = None
    
    return ic, p_value

# 滚动计算IC序列
def rolling_ic(factor_df, return_df, window=12):
    """
    计算滚动IC序列
    factor_df: 因子值矩阵(日期×股票)
    return_df: 收益矩阵(日期×股票)
    """
    dates = factor_df.index
    ic_series = []
    
    for t in range(len(dates)):
        ic, _ = calc_ic(factor_df.iloc[t], return_df.iloc[t])
        ic_series.append(ic)
    
    return pd.Series(ic_series, index=dates)

我的经验:IC序列比单期IC重要得多。我曾经遇到一个因子,单期IC高达0.12,但IC序列正负交替,实盘直接翻车。后来我养成了习惯——先看IC序列的稳定性,再看IC的绝对值

3.3 IR分析:IC的稳定性

IR(Information Ratio),就是IC的均值除以IC的标准差。它衡量的是因子预测能力的稳定性。

公式很简单:

IR = mean(IC) / std(IC)

IR越高,说明因子的预测能力越稳定。我个人认为:

  • IR > 0.5:合格
  • IR > 1.0:优秀
  • IR > 1.5:非常罕见

注意:IR对IC序列的长度很敏感。我建议至少用12个月以上的IC序列来计算IR,太短的话统计意义不大。

代码实现:

def calc_ir(ic_series):
    """
    计算IR
    ic_series: IC时间序列
    """
    mean_ic = ic_series.mean()
    std_ic = ic_series.std()
    
    if std_ic == 0:
        return 0
    
    ir = mean_ic / std_ic
    return ir

# 实战用法
ic_series = rolling_ic(factor_df, return_df)
ir_value = calc_ir(ic_series)
print(f"IR = {ir_value:.3f}")

嗯,这里要注意——IR高不代表因子一定好。如果一个因子IC均值0.02,标准差0.01,IR=2.0,但IC绝对值太小,实际预测能力有限。所以IC和IR要结合起来看

3.4 分组回测:最直观的验证

分组回测,就是把股票按因子值分成N组(通常是5组或10组),然后看每组未来的收益表现。

为什么做分组回测?因为IC只是一个相关系数,它不能告诉你:因子值最高的股票到底赚了多少?最低的又亏了多少?

分组回测的步骤:

  1. 每个月末,按因子值排序,分成5组(Q1~Q5)
  2. 每组等权或市值加权构建组合
  3. 持有到下个月末,计算组合收益
  4. 滚动重复,得到各组收益序列
def group_backtest(factor_df, return_df, n_groups=5):
    """
    分组回测
    factor_df: 因子值矩阵
    return_df: 收益矩阵
    """
    dates = factor_df.index
    group_returns = {i: [] for i in range(n_groups)}
    
    for t in range(len(dates)):
        # 获取当期因子和收益
        f = factor_df.iloc[t].dropna()
        r = return_df.iloc[t]
        
        # 分组
        sorted_f = f.sort_values()
        group_size = len(sorted_f) // n_groups
        
        for g in range(n_groups):
            start = g * group_size
            end = (g + 1) * group_size if g < n_groups - 1 else len(sorted_f)
            stocks = sorted_f.index[start:end]
            
            # 等权收益
            group_return = r[stocks].mean()
            group_returns[g].append(group_return)
    
    # 转换为DataFrame
    result = pd.DataFrame(group_returns, index=dates)
    result.columns = [f'Q{i+1}' for i in range(n_groups)]
    
    # 计算多空收益(Q1 - Q5)
    result['Long-Short'] = result['Q1'] - result['Q5']
    
    return result

分组回测的评判标准

  • 单调性:Q1到Q5的收益应该单调递减(或递增)
  • 区分度:Q1和Q5的收益差距要显著
  • 稳定性:多空组合的收益序列要稳定,不能大起大落

我在项目中遇到过这样的情况:一个因子IC很高,但分组回测发现Q2和Q3的收益几乎一样,没有单调性。这说明因子只在极端值上有区分度,中间区域完全失效。这种因子我一般会直接淘汰。

3.5 因子收益率计算

因子收益率,就是做多因子值最高的股票、做空因子值最低的股票所获得的收益。它本质上就是分组回测中「多空组合」的收益。

计算方式有两种:

方法 说明 适用场景
简单多空 做多Q1,做空Q5,等权 快速验证因子有效性
市值加权多空 按市值加权构建多空组合 更贴近实盘
def factor_return(group_returns):
    """
    计算因子收益率(多空收益)
    group_returns: 分组回测结果
    """
    # 简单多空
    long_short = group_returns['Q1'] - group_returns['Q5']
    
    # 累计收益
    cumulative = (1 + long_short).cumprod()
    
    # 年化收益率
    annual_return = (cumulative.iloc[-1] ** (252 / len(cumulative))) - 1
    
    # 年化波动率
    annual_vol = long_short.std() * np.sqrt(252)
    
    # 夏普比率
    sharpe = annual_return / annual_vol if annual_vol > 0 else 0
    
    return {
        'annual_return': annual_return,
        'annual_vol': annual_vol,
        'sharpe': sharpe,
        'cumulative': cumulative
    }

避坑指南:我曾经犯过一个错误——直接用因子收益率代替分组回测。结果发现因子收益率很高,但分组回测的单调性很差。后来我明白了:因子收益率只看两端,分组回测看全局。两者缺一不可。

3.6 综合评判:如何判断一个因子是否有效?

把四个维度串起来,我一般这样判断:

  1. IC均值 > 0.05:有预测能力
  2. IR > 0.5:预测能力稳定
  3. 分组单调性好:Q1到Q5收益严格递减
  4. 多空收益显著:年化收益 > 5%,夏普 > 0.5

四个条件都满足,这个因子才算「合格」。如果只满足两三个,我会再观察一段时间,或者做更深入的分析。

你想想看,如果连单因子分析这关都过不了,那放进多因子模型里只会添乱。所以,单因子分析不是可选项,而是必选项


专注资料整理