单因子分析基础:因子计算、IC分析、因子收益率、分组回测

单因子分析,说白了就是回答一个问题:这个因子到底有没有用?

我刚开始做量化那会儿,总觉得因子越多越好。后来被市场狠狠教育了一顿——一堆垃圾因子堆在一起,策略照样亏钱。从那以后,我养成了一个习惯:每个因子上线前,必须过一遍单因子分析四件套

1. 因子计算:把原始数据变成信号

因子计算是第一步。你得把原始行情数据,加工成一个数值序列。这个数值就是因子值,代表你对未来收益的某种预测。

举个例子,最简单的动量因子:

import pandas as pd
import numpy as np

def calc_momentum_factor(close, window=20):
    """
    计算过去20天的收益率作为动量因子
    """
    # 因子值 = 当前收盘价 / 20天前收盘价 - 1
    factor = close / close.shift(window) - 1
    return factor

# 使用示例
close_prices = pd.Series([100, 102, 101, 105, 110, ...])
momentum = calc_momentum_factor(close_prices)
print(momentum.tail())
我的习惯:因子计算时,一定要处理缺失值和极端值。我见过有人直接用原始因子跑回测,结果因为前20天没有数据,全是NaN,回测结果惨不忍睹。

因子计算有几个关键点:

  • 对齐时间戳:因子值必须和交易时间严格对齐。别把今天的因子值用到昨天的交易上。
  • 处理停牌:停牌期间因子值怎么算?我个人建议直接填充NaN,或者用前值填充。
  • 标准化:不同股票的因子值量级可能差很多。我习惯做z-score标准化,让因子值均值为0,标准差为1。

2. 因子IC分析:因子和未来收益的相关性

因子算出来了,怎么知道它有没有预测能力?IC(Information Coefficient)就是干这个的。

IC的定义很简单:因子值和未来收益的相关系数。常用的有两种:

  • Pearson IC:线性相关系数。假设因子和收益是线性关系。
  • Rank IC:秩相关系数。只看排序关系,对极端值不敏感。我个人更常用这个。

代码实现:

def calc_ic(factor_values, forward_returns):
    """
    计算因子IC
    factor_values: 因子值序列
    forward_returns: 未来N日收益序列
    """
    # 去掉缺失值
    mask = factor_values.notna() & forward_returns.notna()
    f = factor_values[mask]
    r = forward_returns[mask]
    
    # Pearson IC
    pearson_ic = f.corr(r)
    
    # Rank IC
    rank_ic = f.rank().corr(r.rank())
    
    return pearson_ic, rank_ic

# 计算每日IC
daily_ics = []
for date in factor_df.index.unique():
    f = factor_df.loc[date]
    r = forward_returns.loc[date]
    ic = calc_ic(f, r)
    daily_ics.append({'date': date, 'pearson_ic': ic[0], 'rank_ic': ic[1]})

ic_df = pd.DataFrame(daily_ics)
print(f"平均Rank IC: {ic_df['rank_ic'].mean():.4f}")
print(f"IC标准差: {ic_df['rank_ic'].std():.4f}")
print(f"ICIR: {ic_df['rank_ic'].mean() / ic_df['rank_ic'].std():.4f}")
关键指标:
  • IC均值:>0.02算不错,>0.05算优秀
  • IC标准差:越小越好,说明因子稳定
  • ICIR(IC信息比率):IC均值/IC标准差,>0.5算合格

嗯,这里要注意:IC高不代表因子一定能赚钱。我曾经遇到一个因子,IC高达0.08,但回测一跑,亏得底裤都不剩。为什么?因为IC只衡量了相关性,没考虑交易成本、流动性这些实际问题。

3. 因子收益率分析:多空组合的表现

IC是相关性,因子收益率是真金白银的回报。做法很简单:

  1. 每天按因子值排序,分成10组(或5组)
  2. 做多因子值最高的组,做空因子值最低的组
  3. 计算这个多空组合的收益率

代码示例:

def calc_factor_return(factor_df, return_df, groups=10):
    """
    计算因子多空组合收益率
    """
    # 每天分组
    def assign_group(x):
        return pd.qcut(x, groups, labels=False, duplicates='drop')
    
    group_df = factor_df.groupby(level='date').transform(assign_group)
    
    # 计算每组平均收益
    group_returns = return_df.groupby([group_df, return_df.index.get_level_values('date')]).mean()
    
    # 多空组合:最高组 - 最低组
    long_ret = group_returns.loc[groups-1].mean()
    short_ret = group_returns.loc[0].mean()
    factor_return = long_ret - short_ret
    
    return factor_return

factor_ret = calc_factor_return(factor_df, forward_returns)
print(f"因子多空年化收益: {factor_ret * 252:.2%}")
避坑指南:我曾经犯过一个错误——直接用全市场股票做多空组合,结果发现因子收益很高,但实际交易时根本买不到小盘股。后来我加了市值和流动性过滤,因子收益直接腰斩。所以,一定要考虑现实约束

4. 因子分组回测:看透因子的全貌

分组回测是单因子分析的终极检验。它把股票分成N组,看每组的表现。一个优秀的因子,应该满足:

  • 单调性:分组收益从低到高严格递增(或递减)
  • 区分度:最高组和最低组收益差距明显
  • 稳定性:不同时间段表现一致

我习惯用一张图来展示分组回测结果:

因子分组累计收益曲线 累计收益 时间 组1(最低) 组2 组3 组4 组5(最高) 多空组合 理想的分组回测:收益从组1到组5严格递增

这张图里,组5(因子值最高)的累计收益最高,组1(因子值最低)最低,而且中间各组严格排序。这就是完美的单调性

我的经验:如果分组收益不是单调的,比如组3比组2还差,那这个因子可能有问题。我遇到过一种情况:因子在极端组(组1和组5)表现很好,但中间组乱成一团。这种因子往往是因为极端值驱动,去掉极端值后因子就失效了。

5. 综合评估:因子到底行不行?

把上面四个步骤的结果汇总,我一般会做一个因子评估表:

指标 优秀 合格 不合格
Rank IC均值 > 0.05 0.02 - 0.05 < 0.02
ICIR > 1.0 0.5 - 1.0 < 0.5
多空年化收益 > 15% 8% - 15% < 8%
分组单调性 严格单调 基本单调 乱序
最大回撤 < 10% 10% - 20% > 20%

你想想看,如果一个因子IC均值0.06,ICIR 1.2,多空年化20%,分组严格单调,最大回撤只有8%。这种因子,我二话不说直接上线。

但现实往往是:IC均值0.03,ICIR 0.6,多空年化10%,分组基本单调但偶尔乱序,最大回撤15%。这种因子,我会再观察一段时间,或者尝试做一些改进——比如换一种因子计算方法,或者加入行业中性化处理。

最后提醒一句:单因子分析只是第一步。一个因子在历史回测中表现好,不代表未来也能赚钱。我见过太多因子在样本内表现惊艳,一到样本外就崩盘。所以,永远留一手样本外数据做验证,这是对自己钱包负责。

公众号:蓝海资料掘金营,微信deep3321