基于 TabPFN 表格模型和因子模块的案例实践

本文介绍如何将 TabPFN 应用于股票收益率预测,并基于预测结果构建行业内选股策略。教程会从 TabPFN 的基本概念和最小可运行示例开始,逐步展示股票池构建、分钟频因子获取、收益率标签定义、数据预处理、模型预测、模型微调、指标评估和回测验证等完整流程。

本案例使用 DolphinDB 存储和处理股票分钟行情、分钟频因子以及行业成分股数据。通过 DolphinDB,可以完成行业股票池筛选、因子宽表转换、价格数据合并等数据准备工作;在 Python 中使用 TabPFN 完成建模和预测后,再将预测结果导入 DolphinDB,并使用 DolphinDB Backtest 插件完成行业内 TopN 选股策略回测。

通过阅读本文,读者可以了解 TabPFN 在金融表格数据中的基本使用方式,掌握从 DolphinDB 获取并处理股票因子数据的方法,并学习如何结合排名信息系数 Rank Information Coefficient(RankIC)指标和 DolphinDB Backtest 回测结果,评估模型预测信号的截面选股价值。

1. TabPF N 模型简介

论文原文:Accurate predictions on small data with a tabular foundation model | Nature

技术报告:TabPFN-3: Technical Report

Github仓库:PriorLabs/TabPFN: ⚡ TabPFN: Foundation Model for Tabular Data ⚡

官方文档:Overview - Prior Labs

TabPFN(Tabular Prior-data Fitted Network)是一种专门面向中小规模表格数据设计的基础模型。与传统机器学习模型不同,TabPFN 不需要针对每个新数据集从零开始训练,而是通过预训练获得通用的表格数据建模能力。在实际应用中,模型将训练样本作为上下文输入,并在一次前向传播中完成对测试样本的预测。

TabPFN 主要解决的是中小规模表格任务中建模和调参成本较高的问题。传统模型通常需要针对不同数据集反复训练、调参和验证,而 TabPFN 可以直接利用预训练模型快速得到 base model 结果;如果目标任务与预训练分布差异较大,也可以通过 fine-tuning 进一步适配特定数据。由于其输入阶段包含标准化、缺失值和异常值处理机制,TabPFN 对真实表格数据中的缺失值、不同量纲特征和噪声也具有一定适应性。

本文的股票收益率预测案例本质上是一个表格回归任务:每一行样本对应某只股票在某个时间点的因子特征,模型需要预测该股票未来一段时间的收益率。该任务具有因子数量多、收益信号弱、噪声大、需要反复比较特征处理和标签定义等特点,因此适合使用 TabPFN 快速建立基线模型,并进一步通过参数微调提升模型对特定行业数据的适应能力。最终,模型预测结果可以用于 RankIC 评估和行业内 TopN 选股回测,验证因子信号是否具有实际选股价值。

2. 环境准备与安装验证

本章节主要介绍使用 DolphinDB 数据和 TabPFN 模型进行建模所需要的准备工作,并提供了最简的示例。

2.1 开发环境

Python: 3.11.15

TabPFN: 0.8.3 (torch: 2.8.0)

DolphinDB: 3.00.4.2

TabPFN 安装:

源码安装方式(方便查看 src 目录):

git clone https://github.com/PriorLabs/TabPFN.git
cd TabPFN
pip install -e .

pip 安装方式:

pip install tabpfn

2.2 Base Mode l 调用样例

下面给出一个使用 TabPFNRegressor 进行回归任务的简单示例,使用官网给出的模型进行预测。 这里使用 make_regression 方法来生成回归问题的数据集样例:

from tabpfn import TabPFNRegressor 
from sklearn.datasets import make_regression 
from sklearn.model_selection import train_test_split 
from sklearn.metrics import mean_squared_error, r2_score 
X, y = make_regression( 
    n_samples=1000, 
    n_features=20, 
    noise=0.1, 
    random_state=42, ) 
X_train, X_test, y_train, y_test = train_test_split( 
    X, 
    y, 
    test_size=0.2, 
    random_state=42, ) 

API_TOKEN = ""
tabpfn_client.set_access_token(API_TOKEN)

model = TabPFNRegressor() 
model.fit(X_train, y_train) 
pred = model.predict(X_test) 
print("MSE:", mean_squared_error(y_test, pred)) 
print("R2:", r2_score(y_test, pred))

2.3 API Keys

使用TabPFN 模型需要在官网上申请相应的 API Key 。本案例主要使用模型的 v3 版本进行训练,其 API Keys的申请链接为:https://ux.priorlabs.ai/accept-license?hf_repo_id=tabpfn_3

按照链接中的步骤,选择语言为 python ,安装 tabpfn_client 包后,生成 API Keys,界面如下图所示:

1. 图 2-1 API Keys获取示例

获取后,可在左侧菜单栏 API Keys 处随时查看已有的 API Key 。

3. 股票收益率预测案例 实践

本文以分钟频股票因子预测未来 30 分钟收益率为例,基于预测结果构建行业内截面选股策略。

3.1 案例说明

在每个分钟级时间截面上,模型根据当前时刻的因子数据预测各股票未来 30 分钟收益率,随后按照预测收益率进行排序,用于后续 TopN 选股回测。训练时间选用 2026.01.01-2026.03.31,测试时间选用2026.04.01-2026.04.20。本案例使用 DolphinDB 现有因子模块中的 335 个因子,包括 MyTT 常见指标、WorldQuant 101 Alpha 因子指标库中的因子和国泰君安 191 Alpha 因子库中的因子。此处,可以通过调用 DolphinDB 因子模块来快速计算需要的因子:

use mytt
use wq101alpha
use gtja191Alpha

预测目标定义为:

return_30min = close(t + 30min) / close(t) - 1

其中,close(t) 表示当前时刻收盘价,close(t + 30min) 表示同一交易日内 30 分钟后的收盘价。为避免跨日收益率计算,样本仅保留 14:30 及之前的分钟数据。

3.2 数据查询

从 DolphinDB 数据库中,获取需要的股票和因子数据。本案例中主要涉及三种数据来源:能够获取行业股票的行业分类表、分钟频的因子表和分钟 K 线表。

3.2.1 股票池与数据来源

本案例以申万二级行业“普钢”为例,行业代码为 “801044.SI” ,共包含 23 支股票。首先从 DolphinDB 的行业成分表中获取该行业下的股票代码:

def fetch_code_list_from_ddb(session):
    script = f"""
        exec code
        from {INDUSTRY_TABLE}
        where l2_code == "{INDUSTRY_CODE}"
    """
    code_df = session.run(script)
    if code_df is None or len(code_df) == 0:
        return []
    return code_df.tolist()

3.2.2 分钟频因子与价格数据获取

根据股票池代码,从 DolphinDB 中分别读取分钟频因子数据和分钟收盘价数据。

其中,因子数据表参数为:{FACTOR_TABLE},收盘价数据表参数为:{PRICE_TABLE}

由于因子经常会增加、变动,因此多数因子表采用的是窄表结构,如下所示:

2. 图 3-1 因子表结构示例

即每行对应一个股票、时间和因子与因子值。模型输入则主要使用的是宽表模式,在 DolphinDB 中,可以通过 pivot by 语句来简洁、高效地将窄表转换为宽表形式。本教程中,将价格表与展开后的因子宽表按照进行左连接,得到每只股票在每个分钟时点的收盘价和对应因子特征。

示例查询脚本如下:

script = f"""
    factor = select factorvalue
    from {FACTOR_TABLE}
    where code in {code_filter}
      and trade_date >= {start_time.strftime('%Y.%m.%d')}
      and trade_date < {end_time.strftime('%Y.%m.%d')}
      and trade_time >= 09:30:00
      and trade_time <= 15:00:00
    pivot by code, trade_date, trade_time, factorname
  
    price = select code, trade_date, trade_time, close
            from {PRICE_TABLE}
            where code in {code_filter}
              and trade_date >= {start_time.strftime('%Y.%m.%d')}
              and trade_date < {end_time.strftime('%Y.%m.%d')}
              and trade_time >= 09:30:00
              and trade_time <= 15:00:00
  
    lj(price, factor, `code`trade_date`trade_time)
"""
chunk_df = session.run(script)
3. 图3-2 数据结构样例

查询结果为宽表,每一行表示某只股票在某一分钟的因子值及收盘价,用于后续计算未来 30 分钟收益率,其余因子列作为模型输入特征。

从 DolphinDB 查询得到 pandas DataFrame 后,需要对时间字段进行格式处理。DolphinDB 库表中的 trade_time 为 TIME 类型,传入 Python 后会被 pandas 转换为带默认日期的 datetime64/Timestamp datetime64 / Timestamp 格式(见https://docs.dolphindb.cn/zh/pydoc/AdvancedOperations/DataTypeCasting/PROTOCOL_DDB.html)。该日期仅是类型转换时自动补充的占位日期,不代表真实交易日期。

因此,在本地处理时,先将 trade_time 转换为纯时间字符串格式,例如 HH:MM:SS,再与 trade_date 拼接生成完整时间戳 timestamps。这样可以保证后续按照股票代码和时间戳进行排序、合并以及计算未来收益率时,时间字段具有统一且明确的格式。

chunk_df["trade_time"] = chunk_df["trade_time"].dt.strftime("%H:%M:%S")
chunk_df["timestamps"] = pd.to_datetime(
    chunk_df["trade_date"].astype(str) + " " + chunk_df["trade_time"].astype(str)
)

数据获取完整脚本见附件。

取数耗时:44.46s

3.3 数据集划分

为避免出现未来信息干扰训练,数据集按照时间顺序划分训练集和测试集。本文实验设置如下:

表 3-1

数据集 时间范围 样本量
训练集 2026.01.05 – 2026.03.31 271, 768
测试集 2026.04.01 – 2026.04.20 2, 392

由于预测时耗时过长,测试集按30分钟采样,即保留整点和整30分钟的数据,与预测目标的 30min 预测保持一致。

3.4 数据预处理

数据规模限制

TabPFN 在训练和预测时需要将训练样本作为上下文(context) 与 查询样本(query) 同时输入模型,并通过行维度和列维度的注意力机制提取信息。因此,当样本数量或特征数量较大时,模型的显存占用和计算复杂度都会明显增加。

本文原始因子数量为 335 个,如果直接使用全部因子进行训练,会显著增加模型输入维度和 GPU 显存压力,也可能引入较多冗余因子和噪声因子。因此,在建模前需要对因子进行筛选或降维,在保留有效信息的同时减少特征数量,从而提高训练效率和模型稳定性。

因子筛选

原始因子数量为 335 个。首先,基于训练集移除缺失率大于 0.3 的因子,从原始 335 个因子去除 49 个因子。检查剩下的因子中,没有只出现单一值的因子,全部保留。

在实际业务中,一般需要结果的可解释性,单因子 rankIC 的方式在可解释性方面效果较好,因此本文以此方法为例进行因子筛选。筛选条件采用 rankIC 值绝对值排名前80、icir 绝对值排名前80,并且 rankIC 正负方向一致比例大于 55%。计算单因子 rankIC 值时,为了节省计算时间,仅保留训练集整点和整 30 分钟数据进行计算。

rankIC 计算部分代码:(具体可见附件)

for ts, group in tqdm(grouped, desc="IC by timestamps", total=len(grouped)):
  if len(group) < 2:
      continue

  group_target = group["target"]

  for col in factor_cols:
      # 用 dropna 同时剔除因子和 target 的缺失值
      valid = group[[col, "target"]].dropna()

      if len(valid) < 2:
          continue

      if valid[col].nunique() <= 1 or valid["target"].nunique() <= 1:
          continue
      
      # 计算 Spearman 相关系数
      ic = valid[col].corr(valid["target"], method="spearman")
      ic_rows.append({"timestamp": ts, "factor": col, "ic": ic})

因子相关性分析:

从上述过程筛选得到的 80 个因子中相关系数绝对值大于 0.9 的因子对有 65 组,去除其中 rankIC 值较低的因子,使得每一对中只保留一个因子。进行处理后,保留至 45 个因子。后续的建模训练都基于这 45 个因子。

rankIC 值绝对值最大的 6 个因子:

数据预处理完整脚本见附件。

3.5 实验设置与训练细节

本文所使用的是 TabPFN V3 Regressor 结构,主要用于连续型目标变量的预测任务。

Base Model 预测时间:392s

Fine-tuning 每轮训练时间:64s

3.5.1 Base Model 预测

首先使用 TabPFN V3 Regressor 作为 Base Model 进行预测。该方式不对模型参数进行微调,而是直接加载官方预训练权重,将训练集作为 context、测试集作为 query 输入模型,并输出测试样本的预测收益率。

示例代码如下:

model = TabPFNRegressor()
model.fit(X_train, y_train)
pred = model.predict(X_test)

其中,fit(X_train, y_train) 将训练样本作为预测阶段的 context ;predict(X_test) 时,模型根据 context 中的特征与标签关系,对测试样本进行预测。

每次调用 predict 都会重新计算训练集。分别对 100 个样本进行预测,比单次调用慢将近 100 倍。因此尽可能地一次预测全部样本。如果测试集非常大,可以将其拆分为每个包含一定数量样本的块进行预测。

3.5.2 Fine-tuning 设置

在微调实验中,使用 FinetunedTabPFNRegressor() 对 TabPFN 进行参数更新。Fine-tuning 会先加载预训练 checkpoint,并在目标任务数据上继续训练模型参数,使模型进一步适应当前行业的因子分布、收益率尺度和样本结构。

示例代码如下:

model = FinetunedTabPFNRegressor(
    device=DEVICE,
    epochs=EPOCHS,
    learning_rate=LEARNING_RATE,
    n_finetune_ctx_plus_query_samples=N_FINETUNE_CTX_PLUS_QUERY_SAMPLES,
)

model.fit(
    X_train,
    y_train,
    output_dir=OUTPUT_DIR,
)

其中,output_dir 用于指定微调后模型权重的保存路径。默认情况下,模型会从训练集中划分 10% 作为验证集,也可以显式传入验证集进行训练和早停判断。

n_finetune_ctx_plus_query_samples 控制每个 fine-tuning step 中 context 样本和 query 样本的总数量。若训练过程中出现 CUDA 显存不足,可以适当调小该参数。本文实验中将其设置为 5,000。

如果想通过训练好的权重进行预测及测试,可在 TabPFNRegressor() 中通过 model_path 参数指定权重路径。

model = TabPFNRegressor(
    model_path=str(CHECKPOINT_PATH),
    device=DEVICE,
)
model.fit(X_train, y_train)
pred = model.predict(X_test)

3.6 评价指标计算

对于股票截面选股任务,仅使用均方误差 Mean Squared Error(MSE)等点预测误差无法充分衡量模型效果,因此本文重点计算 RankIC。具体地:在每个时间截面上,计算模型预测收益率与真实未来 30 分钟收益率之间的 Spearman 秩相关系数。具体数学定义与计算公式可见附录。

示例代码如下:

predictions = meta_test.copy()
predictions["y_pred"] = np.asarray(pred)

rankic_rows = []
for ts, group in predictions.groupby("timestamps", sort=True):
    valid_group = group[["target", "y_pred"]].dropna()
    sample_count = len(valid_group)

    if sample_count < 2:
        rankic = np.nan
    else:
        rankic = valid_group["y_pred"].corr(
            valid_group["target"],
            method="spearman",
        )

    rankic_rows.append(
        {
            "timestamp": ts,
            "sample_count": sample_count,
            "rankic": rankic,
        }
    )

rankic_df = pd.DataFrame(rankic_rows)
mean_rankic = rankic_df["rankic"].dropna().mean()
std_rankic = rankic_df["rankic"].dropna().std()
icir = mean_rankic / std_rankic if std_rankic > 0 else np.nan

其中,mean_rankic 表示所有时间截面 RankIC 的均值,反映模型整体截面排序能力;std_rankic 表示 RankIC 的波动程度;信息系数信息比率 Information Coefficient Information Ratio(ICIR)为 RankIC 均值与标准差之比,用于衡量排序能力的稳定性。

数据加载及训练完整脚本见附件。在代码中 mode 部分,可通过选择不同的模式 ( base / finetune ) 来选择进行基础模型或微调模型的预测。

3.7 调参

学习率设置:

模型微调时使用 warmup 与 decay 结合的学习率调度策略。在前 warmup_steps 内,学习率从 0 线性上升至基础学习率。若 lr_warmup_only=True,warmup 结束后学习率保持不变;若 lr_warmup_only=False,warmup 结束后学习率按照 cosine decay 衰减至 0。默认设置为 lr_warmup_only=False,即前 10% steps 进行 linear warmup,之后进行 cosine decay。基础学习率默认值为 1e-5,实践中可进行调整。

TabPFN 文档https://docs.priorlabs.ai/capabilities/fine-tuning#tuning-guidelines 指出对于较大规模的数据集(例如训练样本量超过 10k 行),可以适当尝试更高的学习率,例如 3e-5 到 1e-4,以加快模型微调过程中的收敛速度。

此外,由于各个申万二级行业股票数量不同(分布于2-268支),训练时数据量不同,训练不同行业股票数据时所用的学习率也应进行调整,否则可能导致效果不佳。另外,因子也可根据行业做不同筛选。

训练轮数与早停策略:

模型最大训练轮数设置为 30 个 epochs。训练过程中使用验证集 MSE 作为早停判断指标,当验证集 MSE 连续 8 个 epochs 没有一定程度的提升时,提前停止训练,以降低过拟合风险。

损失函数:

模型训练默认使用 crps_loss 和 mse_loss。其中,mse_loss 衡量预测值与真实收益率之间的平方误差,强调点预测精度;crps_loss 衡量预测分布与真实值之间的差异,更关注概率预测质量和不确定性刻画。两者结合可以同时约束点预测误差和预测分布质量。对于回归任务,模型提供五种loss及权重,具体可见附录。

训练样本采样方式:

由于 TabPFN 每次输入模型的样本数量受到上下文长度和显存限制,微调过程中通常不会一次性将全部训练数据输入模型,而是从完整训练集中反复采样子集进行训练。可以调整N_FINETUNE_CTX_PLUS_QUERY_SAMPLES 控制每个训练 step 中 context 样本和 query 样本的总数量,避免数据量超过GPU显存。

预测集成参数:

n_estimators 用于控制 TabPFN 在预测阶段的集成次数。数值越大,模型会基于更多组不同的数据顺序、特征顺序或内部随机变换进行多次预测,并对结果进行集成平均,从而提升预测稳定性和鲁棒性。但较大的 n_estimators 会增加推理时间和显存/内存占用,因此需要在预测稳定性和计算成本之间进行权衡。本教程采用默认值2。

4. 实验结果与回测

本章节展示了实验结果,并基于结果在 DolphinDB 中使用回测进行策略效果的判断。

4.1 实验结果

Base Model 预测结果

表 4-1

MSE Mean RankIC RankIC Std ICIR
0.44341278 0.0164 0.311661 0.210042 1.483805

Fine-Tuned Model 预测结果

表 4-2

MSE Mean RankIC RankIC Std ICIR
0.45892538 -0.0180 0.343308 0.203209 1.689436

4.2 回测设计与 结果

本结果仅为示例演示,实际应用需在更长时间段和更多行业上验证。

4.2.1 回测策略

基于 TabPFN 模型对未来 30 分钟收益率的预测结果,构建行业内截面 TopN 选股策略。具体而言,每 30 分钟进行一次调仓,在每个调仓时点,根据模型预测收益率 pred_ret_30m 对行业内股票进行降序排序,选取预测收益率最高的前 3 只股票等权买入,并持有 30 分钟。

基准组合设置为行业等权组合,即在每个调仓时点等权买入行业内所有股票,并持有 30 分钟。

4.2.2 回测实现

传入预测的股票 30 分钟收益率作为 msg,采用 dolphindb 的 backtest 插件对策略进行回测。

//将预测结果存储在分布式库表pt中
pt = loadTable("dfs://test_tabpfn", "prediction_30m")

pred2 = select
            code as symbol,timestamp(timestamps) as tradeTime,
            double(pred_ret_30m) as pred_ret,double(target_ret_30m) as target_ret_30m
        from pt


minuteData2 = select
            symbol(trim(string(symbol))) as symbol,timestamp(tradeTime) as tradeTime,
            open,low,high,close,volume,amount,upLimitPrice,downLimitPrice,prevClosePrice
          from minuteData

data = lj(minuteData2, pred2, `symbol`tradeTime)

update data set signal = pred_ret

创建回测引擎,把数据传给回测引擎运行。

engine = Backtest::createBacktester(engineName, config, callbacks)
Backtest::appendQuotationMsg(engine, data)

4.2.3 回测结果

表 4-3

组合 总收益率 最大回撤
行业等权组合 -0.85% 4.14%
TabPFN 基准模型选股策略 1.76% 1.89%
TabPFN 微调后选股策略 6.88% 1.14%

回测结果显示,TabPFN 基准模型选股策略相对行业等权组合的超额收益为2.61%,微调后策略超额收益提升至7.73%。

该结果表明,在本次测试区间内,TabPFN 模型对行业内股票未来 30 分钟收益率具有一定的截面排序能力。基于模型预测结果构建的 Top3 组合跑赢了行业等权基准,说明模型预测信号在该行业内具有一定的选股价值。本文主要展示模型训练结果在 DolphinDB 中使用的功能,以及模型预测的效果,实际交易中受到手续费、T+1 结算等影响,结果有较大出入。

回测脚本见附件。

5. 附录

5.1 Loss设计

回归任务的 Loss 由五部分组成:ce_loss,crps_loss,crls_loss,mse_loss,mae_loss

模型前向传播后,会输出针对回归目标 y 的 bar distribution,即将连续的回归目标离散化到若干有序区间中,并预测 y 落入每个区间的概率。因此,TabPFN 的回归训练可以理解为把连续值预测转化为一种“有序分桶上的概率分布预测”问题,而不是直接预测单个标量值。

在默认设置下,TabPFN v3 的 regression checkpoint 使用预定义的非均匀分桶边界 borders,这些边界来自模型 checkpoint 中保存的参考分布,并用于对标准化后的y进行分桶。默认分桶数为 5,000,即模型对每个样本输出一个长度为 5,000 的 logits 向量:zi=[zi,1, zi,2,…,zi,5000],

经过 softmax 后,logits 被转换为各个区间上的概率: p i , k = exp ( z i , k ) j = 1 K exp ( z i , j )

若真实目标值 yi 落入第 ki 个区间,则训练时通过 bar distribution 的 negative log-likelihood,也就是 cross-entropy-like loss,鼓励模型提高该区间的预测概率密度。由于不同区间宽度可能不同,TabPFN 会用区间宽度 wki 对概率进行校正,因此单个样本的损失可以写为: L ce = log ( p i , k i w k i )

其中 wki 是第 ki 个分桶区间的宽度。换言之,TabPFN 回归并不是直接对预测值和真实值计算 MSE,而是先学习 y 的预测分布,再通过真实 y 所在区间的概率密度来计算损失。

crps_loss 和 crls_loss 是基于预测分布的累积分布函数 CDF 计算的。

CDF: P ( y i b k + 1 ) = F i , k = j = 1 k p i , j

构造一个阶跃函数: Y i , k = 1 ( k k i ) ,视为真实标签的累积分布 L crps = 1 N i = 1 N k = 1 K w k ( F i , k Y i , k ) 2

Lcrls 则是在 CDF 上计算类似二分类交叉熵的 logarithmic score:

L crls = 1 N i = 1 N k = 1 K w k [ Y i , k log F i , k ( 1 Y i , k ) log ( 1 F i , k ) ]

对于 mse_loss 和 mae_loss ,则是先对桶分布求一个预测均值:令 m k = b k + b k + 1 2 ŷ i = k = 1 K p i , k m k L mse = 1 N i = 1 N ( ŷ i y i ) 2 L mae = 1 N i = 1 N | ŷ i y i |

总 loss 由五部分加权组成,默认仅用 crps_loss 和 mse_loss: L total = λ ce L ce + λ crps L crps + λ crls L crls + λ mse L mse + λ mae L mae

5.2 评价指标

5.2.1 数值预测指标

假设每个时刻的真实和预测 30 分钟后收益率分别为 yi y i ^ ,那么 MSE = 1 n i = 1 n ( y i y ^ i ) 2 R 2 = 1 i = 1 n ( y i y ^ i ) 2 i = 1 n ( y i y ¯ ) 2

5.2.2 截面排序指标

对某个时间点 t,若该时点有 mt 只股票,预测值和真实值分别为 y ^ t , j ,yt,j,那么

某个时间截面所有股票真实收益率与预测收益率排序的相关性: RankIC t = SpearmanCorr ( { y ^ t , j } j = 1 m t , { y t , j } j = 1 m t )

所有时间截面的 RankIC 均值: Mean RankIC = 1 T t = 1 T RankIC t

所有时间截面的 RankIC 标准差: RankIC Std = 1 T 1 t = 1 T ( RankIC t RankIC ¯ ) 2

信息比率: ICIR = Mean RankIC RankIC Std