问题描述
解决方案
在量化交易中,IC (Information Coefficient) 和 Rank IC 是评估选股因子有效性的两个核心指标。
- IC (Normal IC):是指某期因子值与下期收益率之间的 Pearson 相关系数。它衡量因子值与未来收益的线性相关程度。
- Rank IC:是指某期因子值的排名与下期收益率的排名之间的 Spearman 相关系数(秩相关系数)。它消除了异常值的影响,衡量因子排序对未来收益排序的预测能力,通常比 Normal IC 更稳健。
在 QMT 中计算这两个指标,通常需要结合 pandas 和 scipy.stats 库。
实现思路
- 获取数据:在
handlebar周期内,获取股票池中所有股票的历史行情。 - 计算因子值:基于历史数据计算当期因子值(例如 $T$ 日的因子)。
- 计算未来收益:获取下一期的收益率(例如 $T$ 日到 $T+1$ 日的收益)。
- 注意:在回测
handlebar中,我们通常是在 $T$ 日收盘后计算。为了验证因子的有效性,我们通常计算 上一期因子值 ($T-1$) 与 当期收益率 ($T-1 \to T$) 的相关性。
- 注意:在回测
- 数据对齐与清洗:将因子值和收益率按股票代码对齐,去除空值。
- 计算相关系数:使用
scipy.stats.pearsonr计算 IC,使用scipy.stats.spearmanr计算 Rank IC。 - 绘图或输出:使用
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')
代码关键点解析
-
数据获取 (
get_market_data_ex):- 我们一次性获取了过去 22 天的数据。这是为了计算昨天的 20 日动量(需要 $T-1$ 和 $T-21$ 的数据)以及今天的收益率(需要 $T$ 和 $T-1$ 的数据)。
dividend_type='front':计算收益率和技术指标时,通常建议使用前复权数据,以消除分红配股造成的股价跳空影响。
-
时间错位 (Lag):
- IC 的核心定义是 因子值($T$) 与 未来收益($T+1$) 的相关性。
- 在
handlebar逐 K 线运行时,我们站在 $T$ 时刻。此时我们已知 $T$ 的收盘价。 - 为了验证因子有效性,我们计算的是 因子值($T-1$) 与 收益率($T$) 的相关性。这代表了“昨天选出的因子值,对今天收益的预测能力”。
-
数据清洗 (
dropna):- 实际交易中,某些股票可能停牌或刚上市导致数据缺失(NaN)。
- 计算相关系数前必须剔除这些无效数据,否则
scipy.stats会报错或返回 NaN。
-
计算函数:
stats.pearsonr(x, y):计算线性相关系数,即 IC。stats.spearmanr(x, y):先将 x 和 y 转换为排名(Rank),再计算相关系数,即 Rank IC。
-
绘图 (
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.nan 或 np.inf。另外,如果当期有效股票数量太少(例如少于 2 只),相关系数也无法计算。
Q: 如何计算财务因子的 IC?
A: 可以使用 ContextInfo.get_financial_data 获取财务数据(如 PE、PB、ROE)。逻辑是一样的:取 $T-1$ 日已知的财务数据作为因子值,与 $T$ 日的收益率计算相关性。注意财务数据存在滞后性,必须确保使用的是当时已公告的数据。