第四章 因子评价体系:IC/IR分析、分层回测、多空组合收益计算

因子挖出来,不能直接扔到模型里。你得先问问它:这玩意儿到底有没有预测能力?

我见过太多人,挖了一堆因子,回测曲线漂亮得不行,一上实盘就崩。为什么?因为评价体系没搭好。今天我们就来聊聊,怎么用Python搭一套靠谱的因子评价框架。

4.1 因子评价的核心逻辑

说白了,因子评价就是回答三个问题:

  • 这个因子和未来收益有没有关系?——IC分析
  • 这个关系稳不稳定?——IR分析
  • 按这个因子选股,能不能赚钱?——分层回测 & 多空组合

我个人习惯,先跑IC,再看分层回测。IC不过关的因子,后面就不用看了,省时间。

核心公式:

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

IR = mean(IC) / std(IC)

嗯,这里要注意:IC算的是截面相关性,不是时间序列。你想想看,每个时间点,所有股票的因子值和未来收益算一个相关系数,这才叫截面IC。

4.2 用Python搭建IC/IR分析模块

先上代码。我习惯用pandas和scipy,干净利落。

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

def calc_ic(factor_df, return_df, method='spearman'):
    """
    计算每日截面IC
    factor_df: index=日期, columns=股票代码, values=因子值
    return_df: index=日期, columns=股票代码, values=未来一期收益
    """
    dates = factor_df.index
    ic_series = pd.Series(index=dates, dtype=float)
    
    for dt in dates:
        f = factor_df.loc[dt].dropna()
        r = return_df.loc[dt].dropna()
        common = f.index.intersection(r.index)
        if len(common) < 30:
            continue
        if method == 'spearman':
            ic, _ = spearmanr(f[common], r[common])
        else:
            ic = f[common].corr(r[common])
        ic_series[dt] = ic
    
    return ic_series

def calc_ir(ic_series):
    """计算IR = mean(IC) / std(IC)"""
    return ic_series.mean() / ic_series.std()

我在项目中遇到过一个问题:有些日期股票数量太少,算出来的IC噪音很大。所以我加了个阈值,少于30只股票就不算。这个经验值你可以根据自己数据调整。

小技巧: IC的绝对值在0.05以上就算不错了。如果平均IC能到0.1,那这个因子相当强。IR大于1.0,说明因子稳定性可以接受。

4.3 分层回测:看看因子怎么分组

IC告诉你相关性,但实际交易中,我们更关心:按因子排序,买前10%卖后10%,能赚多少?

分层回测就是干这个的。每个月(或每天)按因子值把股票分成N组,分别计算每组未来的收益。

def layer_backtest(factor_df, return_df, n_layers=10):
    """
    分层回测
    返回每组的累计收益序列
    """
    dates = factor_df.index
    layer_returns = {i: [] for i in range(n_layers)}
    
    for dt in dates:
        f = factor_df.loc[dt].dropna()
        r = return_df.loc[dt].dropna()
        common = f.index.intersection(r.index)
        if len(common) < n_layers * 10:
            continue
        
        # 按因子值排序,分成n_layers组
        sorted_stocks = f[common].sort_values()
        group_size = len(sorted_stocks) // n_layers
        
        for i in range(n_layers):
            start = i * group_size
            end = (i + 1) * group_size if i < n_layers - 1 else len(sorted_stocks)
            group_stocks = sorted_stocks.index[start:end]
            group_return = r[group_stocks].mean()
            layer_returns[i].append(group_return)
    
    # 转成DataFrame
    result = pd.DataFrame(layer_returns, index=dates[:len(layer_returns[0])])
    result.columns = [f'Layer_{i+1}' for i in range(n_layers)]
    return result

我曾经犯过一个错:分层时没考虑停牌股票。结果回测收益虚高,因为停牌股在复牌后往往有补涨。后来我加了过滤条件,停牌期间不纳入分组。

避坑指南: 分层回测一定要考虑交易成本。我见过一个因子,分层收益单调性很好,但加上千分之一的印花税和滑点,收益直接腰斩。所以,别只看毛收益。

4.4 多空组合收益计算

多空组合,说白了就是做多第一层,做空最后一层。这是检验因子选股能力的黄金标准。

def long_short_return(layer_returns, long_layer=0, short_layer=-1):
    """
    计算多空组合收益
    long_layer: 做多的层索引(默认第1层)
    short_layer: 做空的层索引(默认最后一层)
    """
    long_ret = layer_returns.iloc[:, long_layer]
    short_ret = layer_returns.iloc[:, short_layer]
    
    # 多空收益 = 多头收益 - 空头收益
    ls_ret = long_ret - short_ret
    
    # 累计收益
    cum_ret = (1 + ls_ret).cumprod()
    
    # 年化收益率
    annual_ret = (cum_ret.iloc[-1] ** (252 / len(cum_ret))) - 1
    
    # 夏普比率
    sharpe = ls_ret.mean() / ls_ret.std() * np.sqrt(252)
    
    return {
        'cumulative_return': cum_ret,
        'annual_return': annual_ret,
        'sharpe_ratio': sharpe,
        'max_drawdown': (cum_ret / cum_ret.cummax() - 1).min()
    }

你想想看,如果多空组合的夏普比率能到1.5以上,这个因子基本可以实盘了。当然,前提是样本外测试也过得了关。

4.5 因子评价框架图

下面这张图,是我自己总结的因子评价流程。每次开发新因子,我都按这个走一遍。

因子评价体系流程图 原始因子数据 数据清洗与对齐 IC / IR 分析 分层回测 多空组合收益 因子是否可用? 不合格,返回优化

4.6 综合评价报告

最后,我把所有指标汇总成一个评价报告。这样一眼就能看出因子好坏。

def factor_evaluation_report(factor_df, return_df, n_layers=10):
    """生成因子综合评价报告"""
    # 1. IC分析
    ic_series = calc_ic(factor_df, return_df)
    ir = calc_ir(ic_series)
    
    # 2. 分层回测
    layer_ret = layer_backtest(factor_df, return_df, n_layers)
    
    # 3. 多空组合
    ls_result = long_short_return(layer_ret)
    
    # 4. 输出报告
    report = {
        'IC_mean': ic_series.mean(),
        'IC_std': ic_series.std(),
        'IR': ir,
        'IC_positive_ratio': (ic_series > 0).mean(),
        'Layer_returns': layer_ret,
        'Long_short_annual_return': ls_result['annual_return'],
        'Long_short_sharpe': ls_result['sharpe_ratio'],
        'Long_short_max_drawdown': ls_result['max_drawdown']
    }
    
    return report

实战经验: 我一般要求因子满足以下条件才考虑实盘:

  • IC均值绝对值 > 0.03
  • IR > 0.8
  • 分层收益单调性明显(前几层收益高于后几层)
  • 多空组合年化收益 > 8%
  • 多空组合最大回撤 < 15%

记住,因子评价不是一次性工作。随着市场变化,因子的表现也会变。我建议每个月重新跑一次评价,看看因子有没有衰减。如果IC持续走低,就该考虑替换了。

好了,这套框架你拿去用。代码可以直接跑,但参数要根据你的数据调整。有什么问题,欢迎交流。


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