3. Qlib数据层详解

Qlib数据层详解 学习卡片

在Qlib中,数据层是整个量化研究流水线的地基。策略写得再精巧,如果数据格式不统一、特征口径不一致、训练集与验证集发生信息泄漏,回测结果就只是“看起来很美”。本节围绕五个关键问题展开:Qlib把数据存成什么格式、表达式引擎如何按需计算特征、特征工程如何组织、数据集如何划分、预处理如何避免未来函数。

一、核心原理:二进制列存与表达式引擎

Qlib默认使用.bin格式存储行情数据,每个字段(如$close、$volume)单独存为一个二进制文件,按交易日顺序排列。这种列式存储的优点是读取快、内存占用低,且天然支持按时间切片。数据目录通常如下:

~/.qlib/qlib_data/cn_data/
├── calendars/day.txt
├── instruments/all.txt
├── features/
│   ├── sh600000/
│   │   ├── close.day.bin
│   │   ├── volume.day.bin
│   │   └── factor.day.bin
└── datasets/

表达式引擎是Qlib数据层的灵魂。它允许你用类似公式的字符串描述特征,例如Ref($close, 1)表示昨日收盘价,Mean($volume, 5)表示5日均量。引擎会解析表达式、按依赖顺序计算,并缓存中间结果。这意味着你不需要手动写循环去对齐日期,Qlib会自动处理停牌、缺失和复权。

表达式引擎的核心价值:把“特征定义”与“数据读取”解耦。你只需声明“我要什么”,Qlib负责“怎么算、从哪取、如何对齐”。

二、方法流程:从原始数据到模型输入

一个标准的数据准备流程如下:

  1. 数据获取与转换:使用scripts/get_data.py下载并转换为.bin格式。
  2. 定义特征表达式:在配置中列出特征字段,如$close/Ref($close,1)-1表示日收益率。
  3. 构建数据集:通过DatasetH封装数据处理器,指定训练、验证、测试时间段。
  4. 预处理:在处理器中配置缺失值填充、标准化、去极值等步骤。
  5. 生成样本:调用dataset.prepare()得到DataFrame或Tensor。

下面是一个典型配置片段:

data_handler_config = {
    "start_time": "2015-01-01",
    "end_time": "2022-12-31",
    "fit_start_time": "2015-01-01",
    "fit_end_time": "2019-12-31",
    "instruments": "csi300",
    "infer_processors": [
        {"class": "RobustZScoreNorm", "kwargs": {"fields_group": "feature"}},
        {"class": "Fillna", "kwargs": {"fields_group": "feature"}}
    ],
    "learn_processors": [
        {"class": "DropnaLabel"},
        {"class": "CSZScoreNorm", "kwargs": {"fields_group": "label"}}
    ]
}

注意fit_start_time与fit_end_time:它们限定预处理参数的拟合区间。标准化均值、方差只能来自训练段,否则测试集信息会泄漏到训练中。

三、特征工程与数据集划分

Qlib的特征工程围绕“字段组”组织,常见分组为feature和label。特征可以来自原始行情,也可以由表达式派生。例如:

  • 动量类:Ref($close,20)/$close-1
  • 波动类:Std($close,20)/Mean($close,20)
  • 量价类:Corr($close,$volume,10)
  • 标签类:Ref($close,-2)/Ref($close,-1)-1(未来两日收益)

数据集划分必须严格按时间切分。Qlib的DatasetH接受segments字典:

segments = {
    "train": ("2015-01-01", "2019-12-31"),
    "valid": ("2020-01-01", "2020-12-31"),
    "test":  ("2021-01-01", "2022-12-31")
}

下表总结了各阶段的职责与常见误区:

阶段时间范围主要职责常见误区
训练集较早年份拟合模型参数与预处理参数用全量数据拟合标准化参数
验证集中间年份调参、早停、模型选择反复调参导致过拟合验证集
测试集最近年份最终评估,只跑一次根据测试结果反向修改特征

四、案例与复盘:一个信息泄漏的典型场景

假设你想构建“过去20日收益率”特征,并直接使用CSZScoreNorm做横截面标准化。如果标准化在全部时间段上计算,那么2015年的均值会包含2022年的信息,模型在训练时“偷看”了未来。正确做法是:在learn_processors中只对训练段拟合,再应用到验证和测试段。

Qlib的处理器分为infer_processors和learn_processors。前者用于推理阶段(如填充缺失),后者涉及统计量拟合(如标准化),必须严格限制拟合区间。

另一个常见问题是标签对齐。若标签表达式为Ref($close,-2)/Ref($close,-1)-1,则最后两个交易日的样本标签为空。Qlib会自动丢弃这些样本,但你需要确认DropnaLabel已启用,否则空标签会污染损失函数。

复盘时建议检查三点:第一,训练、验证、测试的时间段是否严格递增且无重叠;第二,所有涉及统计量的处理器是否只在训练段拟合;第三,特征表达式中是否误用了未来函数,例如Ref($close,-1)出现在特征而非标签中。

实用技巧:在data_handler_config中把fit_start_time和fit_end_time显式设置为训练段起止,这是防止信息泄漏最直接的一步。