跳到正文

IC 分析:如何衡量你的因子是否真正有预测力

IC 分析是评估因子质量的主要工具。以下是如何计算、解读和避免常见错误。

第 9 篇 · 共 10 篇 约 2 分钟

你构建了一个 alpha 因子。你回测了它,多空组合年化收益 18%,Sharpe 1.4。在你分配资本之前,你需要回答一个问题:这个因子真的在预测未来收益,还是你只是在回测期幸运?

IC 分析是你严谨回答这个问题的方法。

什么是 IC?

IC(信息系数)测量你的因子在时间 t 的值与资产从 t 到 t+1 的实现收益之间的相关性。

ICt=corr(ft(X(i)),rt+1(i))IC_t = \text{corr}(f_t(X^{(i)}), r_{t+1}^{(i)})

对于有用的因子,你需要:

  • IC 在时间内持续为正
  • IC 统计显著(t-stat > 2.0)
  • IC 在不同市场结构下稳定

Pearson IC 测量线性相关性。Spearman(rank)IC 测量单调相关性。对异常值更稳健——使用它作为主要指标。

在 Python 中计算 IC

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

def compute_ic(factor_values, returns, method='spearman'):
    """
    计算因子值与下期收益之间的 IC。

    Parameters
    ----------
    factor_values : DataFrame
        因子值 (date x asset)
    returns : DataFrame
        收益 (date x asset)
    method : str
        'spearman' 或 'pearson'

    Returns
    -------
    ic_df : DataFrame
        每日 IC 时间序列
    mean_ic : float
        平均 IC
    t_stat : float
        IC 均值的 t 统计量
    """
    ic_list = []
    dates = factor_values.index

    for date in dates[:-1]:
        if date not in returns.index:
            continue
        next_date = returns.index[returns.index.get_loc(date) + 1]

        fv = factor_values.loc[date].dropna()
        ret = returns.loc[next_date].dropna()

        common = fv.index.intersection(ret.index)
        if len(common) < 10:
            continue

        if method == 'spearman':
            ic, _ = stats.spearmanr(fv[common], ret[common])
        else:
            ic, _ = stats.pearsonr(fv[common], ret[common])

        ic_list.append({'date': date, 'ic': ic})

    ic_df = pd.DataFrame(ic_list).set_index('date')
    ic_series = ic_df['ic']

    mean_ic = ic_series.mean()
    t_stat = mean_ic / (ic_series.std() / np.sqrt(len(ic_series)))

    return ic_df, mean_ic, t_stat


def compute_rolling_ic(factor_values, returns, window=60, method='spearman'):
    """
    计算滚动窗口 IC。
    """
    ic_list = []
    dates = factor_values.index

    for i in range(window, len(dates) - 1):
        window_factors = []
        window_returns = []

        for date in dates[i-window:i]:
            if date not in returns.index:
                continue
            next_date_idx = returns.index.get_loc(date) + 1
            if next_date_idx >= len(returns.index):
                continue
            next_date = returns.index[next_date_idx]

            fv = factor_values.loc[date].dropna()
            ret = returns.loc[next_date].dropna()
            common = fv.index.intersection(ret.index)

            if len(common) > 10:
                window_factors.extend(fv[common].values)
                window_returns.extend(ret[common].values)

        if len(window_factors) > 30:
            if method == 'spearman':
                ic, _ = stats.spearmanr(window_factors, window_returns)
            else:
                ic, _ = stats.pearsonr(window_factors, window_returns)
            ic_list.append({'date': dates[i], 'ic': ic})

    return pd.DataFrame(ic_list).set_index('date')

如何解读 IC

IC 范围解读
IC < 0.01弱——可能经济上不显著
IC 0.01–0.03中等——配合足够换手率和多元化可能有效
IC 0.03–0.05强——真正的预测信号
IC > 0.05非常强——要么是好因子要么是前视偏差

持续的 IC 才是重要的。 在一年内 IC = 0.05 但下一年 IC = 0.00 的因子不可靠。你需要跨多年持续一致的 IC。

t 统计量比 IC 本身更重要

IC = 0.03 听起来不错。但如果 t-statistic 是 1.2,统计上不显著——很可能只是噪声。

有用的规则:要求 mean IC 的 t-stat > 2.0。这意味着在原假设(真实 IC = 0)下,偶然观察到这种 IC 的概率低于 5%。

def test_ic_significance(ic_series, min_t_stat=2.0):
    """
    测试 IC 是否统计显著。
    """
    n = len(ic_series)
    mean_ic = ic_series.mean()
    std_ic = ic_series.std()
    t_stat = mean_ic / (std_ic / np.sqrt(n))

    return {
        'mean_ic': mean_ic,
        'std_ic': std_ic,
        't_stat': t_stat,
        'significant': abs(t_stat) > min_t_stat,
        'positive_pct': (ic_series > 0).mean()
    }

IC 随时间衰减

即使好因子也会衰减。2010 年 IC = 0.05 的因子在 2020 年可能只有 IC = 0.02,因为更多量化基金交易相同信号。因子衰减是正常的——问题是因子在经济上是否仍然可行。

绘制 6 个月或 12 个月滚动窗口的 IC。如果你看到 3 年以上单调衰减,因子正被套利。如果 IC 在较低水平企稳,因子已达到均衡。

IC 在组合构建中的作用

IC 是因子权重的关键输入:

def ic_weighted_portfolio(factor_dict, returns, lookback=252):
    """
    按 IC 加权的因子组合。

    每个时期的因子权重与该时期的滚动 IC 成正比。
    """
    dates = list(factor_dict[list(factor_dict.keys())[0]].index)
    factor_names = list(factor_dict.keys())

    weights_history = []

    for i in range(lookback, len(dates) - 1):
        ic_weights = {}
        for name in factor_names:
            fv = factor_dict[name].iloc[i-lookback:i].dropna()
            ret = returns.iloc[i-lookback:i].dropna()
            common = fv.index.intersection(ret.index)

            if len(common) > 30:
                ic, _ = stats.spearmanr(fv[common], ret[common])
                ic_weights[name] = max(ic, 0)  # 丢弃负 IC
            else:
                ic_weights[name] = 0

        total = sum(ic_weights.values())
        if total > 0:
            weights = {k: v / total for k, v in ic_weights.items()}
        else:
            weights = {k: 1/len(factor_names) for k in factor_names}

        weights_history.append(weights)

    return pd.DataFrame(weights_history, index=dates[lookback:])

常见错误

只看平均 IC。 IC = 0.04 但 2018-2020 年 IC = 0.08、2021-2024 年 IC = 0.00 的因子是结构性依赖的。绘制滚动 IC 来发现这一点。

忽略 IC 波动性。 IC = 0.05 但 std(IC) = 0.08 的因子不如 IC = 0.04 但 std(IC) = 0.03 的因子可靠。信噪比很重要。

用样本内 IC 来论证样本外分配。 如果你在 2015-2020 数据上优化了因子并报告同一时期的 IC,你在展示样本内 IC。在 2021-2024 数据上计算的样本外 IC才对真实分配重要。

只测试单个市场。 在美国股票上测试的因子在日本或欧洲股票上可能完全失效。跨市场 IC 测试是必要的稳健性检查。

在知识体系中的位置

IC 分析是因子评估的基础。在将任何因子加入组合之前,你需要建立它具有统计显著且稳定的 IC。因子 decay 研究 IC 如何随不同时间范围变化。横截面稳定性检查 IC 在不同市场结构下是否一致。

参考文献

  • López de Prado, Advances in Financial Machine Learning, 第 3 章. Wiley, 2018. ISBN: 978-1-119-48208-6.
  • Ang, Asset Management, 第 9 章. Oxford University Press, 2014. ISBN: 978-0-19-995932-3.