4. 金融数据基础与Qlib数据格式

在Qlib中做深度学习量化预测,第一步不是搭模型,而是把数据“喂对”。Qlib对金融数据有一套明确的抽象:行情数据描述市场状态,因子数据描述可计算的解释变量,标签数据描述预测目标,而表达式引擎负责把原始字段组合成因子和标签。理解这四者的关系,才能正确使用Qlib的数据存储格式与查询接口。

一、核心原理:Qlib的数据分层

Qlib把数据组织成“字段—表达式—数据集”三层。最底层是字段,例如收盘价、成交量、换手率;中间层是表达式,例如 Ref($close, 1) 表示昨日收盘价;最上层是数据集,例如 Alpha158、Alpha360,它们把大量表达式批量计算并缓存为可训练的特征矩阵。

行情数据通常包含时间序列上的OHLCV、复权因子、涨跌停状态等。因子数据是从行情数据派生出的数值特征,例如动量、波动率、均线偏离。标签数据则是未来收益或未来价格方向,例如 Ref($close, -2)/Ref($close, -1)-1 表示未来一期的收益率。三者共享同一套时间索引和股票代码索引,因此Qlib使用统一的多级索引:第一级是日期,第二级是股票代码。

关键点:Qlib中所有数据查询最终都返回带 datetime 和 instrument 两级索引的 DataFrame。模型训练时,特征和标签必须按同一时间轴对齐,否则会出现“未来函数”或标签错位。

二、方法流程:从原始数据到可训练数据集

一个标准流程如下:

  1. 准备原始数据:通常使用 scripts/get_data.py 下载Qlib官方数据,或把自有CSV转换为Qlib格式。
  2. 定义表达式:在配置文件中用字符串表达式描述因子和标签,例如 "$close/Ref($close,1)-1"。
  3. 计算并缓存:Qlib的表达式引擎会解析表达式,按股票和时间批量计算,并写入磁盘缓存。
  4. 查询数据集:通过 Dataset 或 DataHandler 接口按时间区间、股票池、特征列查询。
  5. 送入模型:得到 X 和 y,再按时间切分训练集、验证集、测试集。

下面是一个最小示例,展示如何用Qlib查询行情数据并计算简单因子:

import qlib
from qlib.data import D

qlib.init(provider_uri="~/.qlib/qlib_data/cn_data", region="cn")

# 查询行情数据
df = D.features(
    instruments=["SH600000"],
    fields=["$open", "$high", "$low", "$close", "$volume"],
    start_time="2023-01-01",
    end_time="2023-03-31"
)
print(df.head())

# 查询表达式因子
factor = D.features(
    instruments=["SH600000"],
    fields=["$close/Ref($close,1)-1", "Mean($close,5)"],
    start_time="2023-01-01",
    end_time="2023-03-31"
)
print(factor.tail())

运行后,返回的DataFrame索引为 (datetime, instrument),列名为字段表达式。若表达式写错,Qlib会在解析阶段报错,而不是静默返回空值,这一点对调试非常友好。

三、数据存储格式与查询接口

Qlib默认使用二进制列式存储,每个字段按股票分别存储为 .bin 文件,并配合 instruments 目录下的股票池文件和 calendars 目录下的交易日历。这种设计使按时间区间和股票池的切片查询非常快,适合深度学习反复读取大批量特征。

数据类别典型字段/表达式存储位置查询接口
行情数据$open, $high, $low, $close, $volumefeatures/<instrument>/*.binD.features
因子数据Ref($close,1), Mean($close,5), Std($close,20)运行时计算或缓存D.features / DataHandler
标签数据Ref($close,-2)/Ref($close,-1)-1与因子共用表达式引擎DataHandlerLP
股票池csi300, csi500, allinstruments/*.txtD.instruments
交易日历交易日列表calendars/day.txtD.calendar

查询接口方面,D.features 适合快速取数;DataHandler 适合把原始字段转成模型输入;Dataset 则进一步封装了训练/验证/测试切分。实际项目中,建议先用 D.features 验证表达式正确性,再写入配置文件交给 DataHandler 批量处理。

四、案例与误区复盘

假设我们要预测未来5日收益率,并构造一个动量因子。正确做法是:

# 标签:未来5日收益率
label = "Ref($close, -5)/$close - 1"

# 因子:过去5日动量
factor = "$close/Ref($close, 5) - 1"

# 在配置中同时声明
data_handler_config = {
    "start_time": "2018-01-01",
    "end_time": "2023-12-31",
    "fit_start_time": "2018-01-01",
    "fit_end_time": "2020-12-31",
    "instruments": "csi300",
    "fields": [factor, label]
}

常见误区有三个:

  • 标签方向写反:Ref($close, -5) 表示未来第5日,若写成 Ref($close, 5) 就变成过去,模型会学到“用历史预测历史”。
  • 忽略复权:未复权价格在除权日会出现跳空,因子和标签都会被污染。Qlib官方数据已做复权处理,自有数据需自行处理。
  • 训练集与测试集时间重叠:深度学习模型对时间泄漏非常敏感,必须按时间切分,不能随机打乱。

调试建议:先用单只股票、短时间区间查询,打印 df.head() 和 df.tail(),确认索引、列名和数值范围符合预期,再扩展到全市场。这样能快速定位表达式错误或数据缺失问题。

注意:Qlib的表达式引擎区分大小写,字段名如 $close 必须小写,函数名如 Ref、Mean 必须首字母大写。写错时不会自动纠正,而是直接报错或返回空列。