4. 单因子分析:因子计算与实现、因子IC分析、因子分组回测、因子收益与夏普比率

好,我们进入单因子分析这个核心环节。说实话,很多做量化的人,策略亏钱往往不是因为模型不够复杂,而是连最基础的因子分析都没做扎实。我个人习惯是,拿到一个新因子,先把它拆成四个步骤来审视:因子怎么算、IC怎么样、分组回测表现如何、最终收益和风险比划不划得来。

4.1 因子计算与实现

因子计算,说白了就是把你的投资逻辑变成一串数字。比如你相信“低市盈率”的股票能涨,那你就得把市盈率算出来,然后对齐到每个股票、每个时间点。

我见过不少新手,因子算出来之后直接拿去跑回测,结果发现信号全是未来函数。嗯,这里要注意:因子计算最怕的就是“未来信息泄露”。

举个简单的例子,假设我们要计算一个“过去5日动量因子”:

import pandas as pd
import numpy as np

def calc_momentum_factor(price_df, window=5):
    """
    计算过去N日的动量因子
    price_df: DataFrame, index为日期, columns为股票代码
    """
    # 计算收益率
    ret = price_df.pct_change()
    # 滚动求和,注意shift(1)防止未来数据
    momentum = ret.rolling(window=window).sum().shift(1)
    return momentum

# 使用示例
# prices = pd.read_csv('daily_prices.csv', index_col=0, parse_dates=True)
# factor = calc_momentum_factor(prices, window=5)

看到那个 .shift(1) 了吗?这就是避坑的关键。我曾经有一次忘记加这个shift,结果回测年化收益直接翻倍,当时还高兴了半天,后来发现是用了当天的数据去预测当天的涨跌——这不就是作弊嘛。

⚠️ 重要提醒: 因子计算完成后,一定要做去极值和标准化处理。我一般用MAD(中位数绝对偏差)去极值,然后用z-score标准化。不做这一步,极端值会把你的IC分析搞得一塌糊涂。

4.2 因子IC分析(信息系数)

因子算好了,怎么知道它有没有预测能力?IC(Information Coefficient)就是干这个的。IC衡量的是因子值与未来收益之间的相关性。

常用的IC有两种:

  • Pearson IC: 线性相关系数,假设因子和收益是线性关系
  • Spearman Rank IC: 秩相关系数,更稳健,对异常值不敏感

我个人更偏爱Rank IC。为什么?因为因子值本身的大小可能受极端值影响,但排序关系相对稳定。你想想看,一个因子如果能把股票从好到坏排个序,那它的价值就已经很大了。

def calc_rank_ic(factor_series, forward_ret_series):
    """
    计算截面Rank IC
    factor_series: 某一天的因子值(所有股票)
    forward_ret_series: 对应的未来N日收益
    """
    # 计算秩相关系数
    ic = factor_series.rank().corr(forward_ret_series.rank())
    return ic

# 计算时间序列IC
def calc_ic_ts(factor_df, ret_df):
    """
    计算每日IC,返回IC时间序列
    """
    dates = factor_df.index
    ic_list = []
    for date in dates:
        ic = calc_rank_ic(factor_df.loc[date], ret_df.loc[date])
        ic_list.append(ic)
    return pd.Series(ic_list, index=dates)

IC算出来之后,我通常会看几个指标:

  • IC均值: 大于0.02就算不错了,大于0.05就是好因子
  • IC标准差: 越小越好,说明因子稳定
  • IR(信息比率): IC均值 / IC标准差,大于0.5算合格
💡 实战经验: 我曾经遇到一个因子,IC均值高达0.08,但标准差也大得吓人。后来发现它只在特定市场环境下有效,大部分时间就是个噪音。所以别只看均值,IR才是王道。

4.3 因子分组回测

IC分析告诉你因子有没有预测能力,但分组回测告诉你的是——这个能力能不能转化成真金白银。

分组回测的逻辑很简单:每个月(或每天)按因子值把股票分成N组,然后看每组未来的收益表现。如果第1组(因子值最大)的收益显著高于第N组(因子值最小),那这个因子就有区分度。

def group_backtest(factor_df, ret_df, n_groups=5):
    """
    因子分组回测
    返回每组的时间序列收益
    """
    dates = factor_df.index
    group_returns = {i: [] for i in range(1, n_groups+1)}
    
    for date in dates:
        # 获取当天的因子值和未来收益
        factors = factor_df.loc[date].dropna()
        returns = ret_df.loc[date]
        
        # 按因子值分组
        grouped = pd.qcut(factors, n_groups, labels=False) + 1
        
        # 计算每组平均收益
        for group in range(1, n_groups+1):
            stocks_in_group = grouped[grouped == group].index
            group_ret = returns[stocks_in_group].mean()
            group_returns[group].append(group_ret)
    
    return pd.DataFrame(group_returns, index=dates)

分组回测的结果,我一般会画成折线图。理想的情况是:各组收益曲线从高到低整齐排列,而且第1组和第N组之间有明显差距。

📌 小技巧: 分组数量不要太多,5组或10组就够了。我曾经试过分20组,结果每组股票太少,统计意义不大,回测结果忽上忽下,根本看不出规律。

4.4 因子收益与夏普比率

最后一步,也是最实际的一步:这个因子到底能赚多少钱?风险有多大?

我们通常用“多空组合”来评估因子收益。具体做法是:做多第1组(因子值最大的组),做空第N组(因子值最小的组),然后看这个组合的收益表现。

def calc_factor_return(group_returns_df):
    """
    计算多空组合收益
    假设第1组是多头,第N组是空头
    """
    long_ret = group_returns_df.iloc[:, 0]  # 第1组
    short_ret = group_returns_df.iloc[:, -1]  # 最后一组
    factor_ret = long_ret - short_ret
    return factor_ret

def calc_sharpe(return_series, rf=0.0):
    """
    计算夏普比率
    """
    excess_ret = return_series.mean() - rf
    std = return_series.std()
    sharpe = excess_ret / std * np.sqrt(252)  # 年化
    return sharpe

夏普比率大于1,说明这个因子不错;大于2,那就是相当优秀了。但我要提醒你,回测中的夏普比率往往偏高,实盘能打个六折就不错了。

我记得有一次,我开发了一个因子,回测夏普高达3.5,当时兴奋得不行。结果实盘跑了三个月,夏普直接掉到0.8。后来复盘发现,是因为回测时没考虑交易成本和冲击成本。所以,因子收益分析一定要加上交易成本假设,不然就是自欺欺人。

🎯 核心要点总结:
  • 因子计算:注意未来函数,做好去极值和标准化
  • IC分析:用Rank IC更稳健,关注IR而非单纯IC均值
  • 分组回测:看各组收益是否单调排列,这是因子有效性的直观证据
  • 收益与夏普:多空组合收益是核心,夏普比率要打折扣看

好了,单因子分析这一套流程走下来,你基本就能判断一个因子是“宝贝”还是“垃圾”了。别嫌麻烦,这一步做扎实了,后面的多因子组合才能站得住脚。

单因子分析核心流程 因子计算与实现 因子IC分析 因子分组回测 因子收益与夏普 去极值 · 标准化 Rank IC · IR 多空组合 · 单调性 年化收益 · 夏普比率 虚线表示可选反馈路径,实线表示标准流程
专注资料整理