🚀 AI 一键生成 qmt 策略代码

如何在 QMT 中计算因子的 IC 值与 Rank IC 以评估有效性?

qmt | 发布时间: 2025/12/23
以下内容由 EasyQuant 生成。

问题描述

如何计算选股因子的 IC 值和 Rank IC?

解决方案

在量化交易中,IC (Information Coefficient)Rank IC 是评估选股因子有效性的两个核心指标。

  • IC (Normal IC):是指某期因子值与下期收益率之间的 Pearson 相关系数。它衡量因子值与未来收益的线性相关程度。
  • Rank IC:是指某期因子值的排名与下期收益率的排名之间的 Spearman 相关系数(秩相关系数)。它消除了异常值的影响,衡量因子排序对未来收益排序的预测能力,通常比 Normal IC 更稳健。

在 QMT 中计算这两个指标,通常需要结合 pandasscipy.stats 库。

实现思路

  1. 获取数据:在 handlebar 周期内,获取股票池中所有股票的历史行情。
  2. 计算因子值:基于历史数据计算当期因子值(例如 $T$ 日的因子)。
  3. 计算未来收益:获取下一期的收益率(例如 $T$ 日到 $T+1$ 日的收益)。
    • 注意:在回测 handlebar 中,我们通常是在 $T$ 日收盘后计算。为了验证因子的有效性,我们通常计算 上一期因子值 ($T-1$)当期收益率 ($T-1 \to T$) 的相关性。
  4. 数据对齐与清洗:将因子值和收益率按股票代码对齐,去除空值。
  5. 计算相关系数:使用 scipy.stats.pearsonr 计算 IC,使用 scipy.stats.spearmanr 计算 Rank IC。
  6. 绘图或输出:使用 ContextInfo.paint 将结果绘制在副图上。

QMT 策略代码示例

以下代码展示了如何计算一个简单因子(20日动量因子)的 IC 和 Rank IC。

# -*- coding: gbk -*-
import pandas as pd
import numpy as np
from scipy import stats

def init(ContextInfo):
    # 1. 设置股票池,这里以沪深300为例
    ContextInfo.set_universe(['000300.SH'])
    
    # 2. 设置策略运行周期为日线
    ContextInfo.period = '1d'
    
    # 3. 设置回测时间范围(如果在回测模式下)
    ContextInfo.start = '20220101'
    ContextInfo.end = '20230101'
    
    # 4. 必要的初始化变量
    ContextInfo.ic_list = []
    ContextInfo.rank_ic_list = []

def handlebar(ContextInfo):
    # 跳过历史K线不足的情况,确保有足够数据计算因子
    if ContextInfo.barpos < 25:
        return

    # 获取当前股票池
    stock_list = ContextInfo.get_stock_list_in_sector('沪深300')
    
    # =============================================
    # 步骤 1: 获取历史行情数据
    # 我们需要过去的数据来计算因子(T-1)和收益(T)
    # count=22: 获取过去22根K线,足以计算昨天的20日动量和今天的收益
    # =============================================
    data_dict = ContextInfo.get_market_data_ex(
        ['close'], 
        stock_list, 
        period=ContextInfo.period, 
        count=22, 
        dividend_type='front'
    )
    
    # 将数据转换为 DataFrame 格式,方便处理
    # 这里的逻辑是将 dict 转为 DataFrame,行是日期,列是股票代码
    close_data = pd.DataFrame({k: v['close'] for k, v in data_dict.items()})
    
    # 如果数据为空,直接返回
    if close_data.empty:
        return

    # =============================================
    # 步骤 2: 准备因子值 (Factor) 和 收益率 (Return)
    # 逻辑:我们要看 "昨天的因子" 是否预测了 "今天的收益"
    # =============================================
    
    # 获取 昨天(T-1) 和 今天(T) 的收盘价
    # iloc[-1] 是今天,iloc[-2] 是昨天,iloc[-22] 是20天前(相对于昨天)
    
    # 1. 计算收益率 (Return): 今天收盘价 / 昨天收盘价 - 1
    # Series, index为股票代码
    returns_t = close_data.iloc[-1] / close_data.iloc[-2] - 1
    
    # 2. 计算因子值 (Factor): 20日动量
    # 定义:昨天的收盘价 / (昨天往前推20天的收盘价) - 1
    # 注意:close_data.iloc[-2] 是昨天
    # close_data.iloc[-22] 是昨天往前推20个交易日
    factor_t_minus_1 = close_data.iloc[-2] / close_data.iloc[-22] - 1
    
    # =============================================
    # 步骤 3: 数据清洗与对齐
    # =============================================
    
    # 创建一个临时的 DataFrame 来对齐数据
    df_calc = pd.DataFrame({
        'factor': factor_t_minus_1,
        'ret': returns_t
    })
    
    # 去除包含 NaN 或 Inf 的行
    df_calc = df_calc.replace([np.inf, -np.inf], np.nan).dropna()
    
    # 确保有足够的数据点进行相关性计算
    if len(df_calc) < 10:
        return

    # =============================================
    # 步骤 4: 计算 IC 和 Rank IC
    # =============================================
    
    # 1. 计算 Normal IC (Pearson 相关系数)
    # pearsonr 返回 (correlation, p-value),我们取第一个值
    ic_val, _ = stats.pearsonr(df_calc['factor'], df_calc['ret'])
    
    # 2. 计算 Rank IC (Spearman 秩相关系数)
    # spearmanr 返回 (correlation, p-value)
    rank_ic_val, _ = stats.spearmanr(df_calc['factor'], df_calc['ret'])
    
    # =============================================
    # 步骤 5: 输出与绘图
    # =============================================
    
    # 打印日志 (可选)
    # print(f"Time: {timetag_to_datetime(ContextInfo.get_bar_timetag(ContextInfo.barpos), '%Y-%m-%d')} | IC: {ic_val:.4f} | RankIC: {rank_ic_val:.4f}")
    
    # 在副图绘制曲线
    # 注意:paint 只能画数值,不能画字符串
    ContextInfo.paint('IC', ic_val, -1, 0, 'red')       # 红色线表示 IC
    ContextInfo.paint('RankIC', rank_ic_val, -1, 0, 'blue') # 蓝色线表示 Rank IC
    
    # 绘制 0 轴参考线
    ContextInfo.paint('Zero', 0, -1, 0, 'gray', 'noaxis')

代码关键点解析

  1. 数据获取 (get_market_data_ex)

    • 我们一次性获取了过去 22 天的数据。这是为了计算昨天的 20 日动量(需要 $T-1$ 和 $T-21$ 的数据)以及今天的收益率(需要 $T$ 和 $T-1$ 的数据)。
    • dividend_type='front':计算收益率和技术指标时,通常建议使用前复权数据,以消除分红配股造成的股价跳空影响。
  2. 时间错位 (Lag)

    • IC 的核心定义是 因子值($T$) 与 未来收益($T+1$) 的相关性。
    • handlebar 逐 K 线运行时,我们站在 $T$ 时刻。此时我们已知 $T$ 的收盘价。
    • 为了验证因子有效性,我们计算的是 因子值($T-1$)收益率($T$) 的相关性。这代表了“昨天选出的因子值,对今天收益的预测能力”。
  3. 数据清洗 (dropna)

    • 实际交易中,某些股票可能停牌或刚上市导致数据缺失(NaN)。
    • 计算相关系数前必须剔除这些无效数据,否则 scipy.stats 会报错或返回 NaN。
  4. 计算函数

    • stats.pearsonr(x, y):计算线性相关系数,即 IC。
    • stats.spearmanr(x, y):先将 x 和 y 转换为排名(Rank),再计算相关系数,即 Rank IC。
  5. 绘图 (paint)

    • 运行策略后,点击副图指标区域,可以看到 IC 和 Rank IC 的历史走势图。这对于观察因子的稳定性(是否持续大于0或小于0)非常有帮助。

常见问题 Q&A

Q: 如何计算多因子合成后的 IC?
A: 您需要在步骤 2 中,先对多个因子进行标准化(Z-Score)和加权求和,得到一个合成因子值 composite_factor,然后用这个合成值去和 ret 计算相关性。

Q: 为什么我的 Rank IC 经常是 NaN?
A: 通常是因为数据清洗不彻底。请确保 df_calc 中没有 np.nannp.inf。另外,如果当期有效股票数量太少(例如少于 2 只),相关系数也无法计算。

Q: 如何计算财务因子的 IC?
A: 可以使用 ContextInfo.get_financial_data 获取财务数据(如 PE、PB、ROE)。逻辑是一样的:取 $T-1$ 日已知的财务数据作为因子值,与 $T$ 日的收益率计算相关性。注意财务数据存在滞后性,必须确保使用的是当时已公告的数据。