基于 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 ⚡
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,界面如下图所示:
获取后,可在左侧菜单栏 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}
由于因子经常会增加、变动,因此多数因子表采用的是窄表结构,如下所示:
即每行对应一个股票、时间和因子与因子值。模型输入则主要使用的是宽表模式,在 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)
查询结果为宽表,每一行表示某只股票在某一分钟的因子值及收盘价,用于后续计算未来 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 | R² | Mean RankIC | RankIC Std | ICIR |
|---|---|---|---|---|
| 0.44341278 | 0.0164 | 0.311661 | 0.210042 | 1.483805 |
Fine-Tuned Model 预测结果
表 4-2
| MSE | R² | 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 被转换为各个区间上的概率: 。
若真实目标值 yi 落入第 ki 个区间,则训练时通过 bar distribution 的 negative log-likelihood,也就是 cross-entropy-like loss,鼓励模型提高该区间的预测概率密度。由于不同区间宽度可能不同,TabPFN 会用区间宽度 wki 对概率进行校正,因此单个样本的损失可以写为: 。
其中 wki 是第 ki 个分桶区间的宽度。换言之,TabPFN 回归并不是直接对预测值和真实值计算 MSE,而是先学习 y 的预测分布,再通过真实 y 所在区间的概率密度来计算损失。
crps_loss 和 crls_loss 是基于预测分布的累积分布函数 CDF 计算的。
CDF:
构造一个阶跃函数: ,视为真实标签的累积分布
Lcrls 则是在 CDF 上计算类似二分类交叉熵的 logarithmic score:
对于 mse_loss 和 mae_loss ,则是先对桶分布求一个预测均值:令 , 则 , 。
总 loss 由五部分加权组成,默认仅用 crps_loss 和 mse_loss: 。
5.2 评价指标
5.2.1 数值预测指标
假设每个时刻的真实和预测 30 分钟后收益率分别为 yi 和 ,那么 ,
5.2.2 截面排序指标
对某个时间点 t,若该时点有 mt 只股票,预测值和真实值分别为 ,yt,j,那么
某个时间截面所有股票真实收益率与预测收益率排序的相关性:
所有时间截面的 RankIC 均值:
所有时间截面的 RankIC 标准差:
信息比率:
6.附件
3.2 数据查询:
3.4 数据预处理:
3.5-3.6 dataloader、模型训练:
4.2 回测:
