跳到正文

横截面稳定性:测试你的因子是否在不同市场都有效

只在一个市场或行业有效的因子不是真正的 alpha。横截面稳定性测试你的因子的预测力是否在资产、行业和时间段之间都保持一致。

第 3 篇 · 共 10 篇 约 2 分钟

你的因子在美国股票上有很好的 IC = 0.05。但它是真的 alpha——还是只是一个行业赌注、市值赌注,或特定回测期间的伪影?

横截面稳定性测试回答这个问题:因子的预测力是在所有资产的完整横截面上保持,还是集中在某个子集?

为什么稳定性很重要

只在某个行业有效的因子不是通用 alpha。它可能是:

  • 伪装成 alpha 的行业轮动策略
  • 市值效应(大盘股 vs. 小盘股)
  • 地区效应(仅美国等)
  • 时间段伪影(2018-2022 年有效但之前无效)

真正的 alpha 是 在横截面上稳健的。 如果你的因子对所有股票都有效,而不只是科技股,你的因子更可能是真的。

测试横截面稳定性的方法

方法 1:按行业计算 IC

按行业分别计算 IC:

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

def ic_by_sector(factor_values, returns, sector_mapping):
    """
    按行业分别计算 IC。
    """
    results = {}
    for sector in sector_mapping.unique():
        assets = sector_mapping[sector_mapping == sector].index

        fv = factor_values[assets]
        ret = returns[assets]

        ic_list = []
        dates = fv.index

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

            fv_row = fv.loc[date].dropna()
            ret_row = ret.loc[next_date].dropna()

            common = fv_row.index.intersection(ret_row.index)
            if len(common) > 10:
                ic, _ = stats.spearmanr(fv_row[common], ret_row[common])
                ic_list.append(ic)

        results[sector] = {
            'mean_ic': np.mean(ic_list),
            'positive_pct': np.mean([ic > 0 for ic in ic_list]),
            'n_obs': len(ic_list)
        }

    return pd.DataFrame(results).T

稳定的因子在所有行业有相似的 IC(within sampling variance)。集中在科技的因子在科技有高 IC,其他都接近零。

方法 2:按市值五分位计算 IC

按市值将资产分为五分位,分别计算每个五分位的 IC:

def ic_by_size(factor_values, returns, market_caps, n_quintiles=5):
    """
    测试因子 IC 是否随市值变化。
    """
    results = {}
    dates = factor_values.index

    for q in range(1, n_quintiles + 1):
        threshold_low = market_caps.quantile((q-1) / n_quintiles)
        threshold_high = market_caps.quantile(q / n_quintiles)

        assets = market_caps[(market_caps >= threshold_low) &
                            (market_caps < threshold_high)].index

        if len(assets) > 10:
            ic_list = []
            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, assets].dropna()
                ret = returns.loc[next_date, assets].dropna()

                common = fv.index.intersection(ret.index)
                if len(common) > 10:
                    ic, _ = stats.spearmanr(fv[common], ret[common])
                    ic_list.append(ic)

            results[f'Q{q}'] = {
                'mean_ic': np.mean(ic_list),
                'positive_pct': np.mean([ic > 0 for ic in ic_list]),
                'n_assets': len(assets)
            }

    return pd.DataFrame(results).T

如果你的因子只在小盘股有效,它是以市值效应的伪装。

方法 3:按地区计算 IC

对于全球策略,测试 IC 在各地区是否一致:

def ic_by_region(factor_values, returns, region_mapping):
    """
    测试因子 IC 在各地理区域的一致性。
    """
    results = {}
    dates = factor_values.index

    for region in region_mapping.unique():
        assets = region_mapping[region_mapping == region].index

        if len(assets) > 20:
            ic_list = []
            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, assets].mean(axis=1)
                ret = returns.loc[next_date, assets].mean(axis=1)

                common = fv.dropna().index.intersection(ret.dropna().index)
                if len(common) > 10:
                    ic, _ = stats.spearmanr(fv[common], ret[common])
                    ic_list.append(ic)

            results[region] = {
                'mean_ic': np.mean(ic_list),
                'positive_pct': np.mean([ic > 0 for ic in ic_list])
            }

    return pd.DataFrame(results).T

方法 4:滚动 IC 稳定性

测试 IC 是否随时间稳定,还是集中在特定时期:

def rolling_ic_stability(factor_values, returns, window=252):
    """
    计算滚动 IC 并测试稳定性。
    """
    ic_series = compute_rolling_ic(factor_values, returns, window=window)

    rolling_ic = ic_series.rolling(window).mean()
    rolling_ic_std = ic_series.rolling(window).std()

    positive_pct = (rolling_ic > 0).mean()
    ic_decay = (rolling_ic.iloc[-window:] < rolling_ic.iloc[:window].mean()).mean()

    return {
        'rolling_ic': rolling_ic,
        'rolling_ic_std': rolling_ic_std,
        'positive_pct': positive_pct,
        'ic_decay': ic_decay,
        'mean_ic': ic_series.mean(),
        'std_ic': ic_series.std()
    }

关键阈值

指标好可疑
行业 IC 标准差< 0.02> 0.05
市值 IC 标准差< 0.03> 0.05
IC 正比例> 75%< 60%
各五分位最小 IC> 0.01< 0 或接近 0

常见错误

行业集中伪装成 alpha。 如果你的因子在科技 IC = 0.08,其他都接近零,你没有 alpha——你有一个科技行业观点。要么接受它,要么明确管理行业敞口。

时间段稳定性。 在 2015-2019 年有效但 2020-2024 年无效的因子可能是结构性变化(竞争、市场结构)或只是运气不好。延长样本外测试是确认的唯一方法。

忽略 IC 抽样方差。 即使是真 alpha,不同行业之间的 IC 差异也可能很大,仅仅由于抽样误差。如果行业 A 的 IC 比行业 B 高 0.01,这可能只是噪声。用 t-stat 判断差异是否显著。

在知识体系中的位置

横截面稳定性是任何因子部署前的关键稳健性检查。具有不稳定横截面表现的因子应被怀疑对待——它可能是数据挖掘伪影。因子组合应选择跨横截面稳定的因子。

参考文献

  • Ang, Asset Management, 第 9 章. Oxford University Press, 2014. ISBN: 978-0-19-995932-3.
  • López de Prado, Advances in Financial Machine Learning, 第 3 章. Wiley, 2018. ISBN: 978-1-119-48208-6.
  • Green, Hand, Zhang, “The Characteristics That Provide Independent Information,” Review of Financial Studies, 2013.