2. Qlib基础入门

在正式进入风险模型与组合优化之前,必须先建立对 Qlib 的整体认知。Qlib 是微软开源的量化投资平台,它并不是一个“策略脚本集合”,而是一套围绕数据、表达式、模型、回测四要素构建的工程化框架。理解它的架构与数据组织方式,决定了后续因子研究、风险建模和组合优化能否顺畅落地。

一、核心架构:分层解耦的设计

Qlib 的架构可以概括为“三层一引擎”。底层是数据层,负责行情、财务、因子等数据的存储与检索;中间是表达层,通过表达式引擎把原始字段组合成可计算的因子;上层是工作流层,用配置驱动的方式串联数据处理、模型训练、预测与回测。贯穿其中的是表达式引擎,它是 Qlib 区别于普通回测框架的关键。

这种分层带来的直接好处是:数据格式统一后,因子、模型、策略可以独立替换。你更换一个因子表达式,不需要改动数据加载代码;你更换模型,也不需要重写回测逻辑。

关键理解:Qlib 的工作流是“配置驱动”的。绝大多数操作通过 YAML 配置文件描述,而不是写大量 Python 胶水代码。这意味着可复现性和工程化程度更高,但也要求你理解配置项之间的依赖关系。

二、数据格式:二进制存储与表达式字段

Qlib 官方推荐的数据格式是 .bin 二进制格式,配合日历文件与 instruments 文件使用。相比 CSV,二进制格式在读取速度和内存占用上有明显优势,尤其适合全市场、长时间跨度的因子计算。

一个标准的数据目录通常包含以下结构:

qlib_data/
├── calendars/
│   └── day.txt
├── instruments/
│   └── all.txt
└── features/
    └── sh600000/
        ├── open.day.bin
        ├── close.day.bin
        ├── volume.day.bin
        └── ...

其中 calendars/day.txt 定义交易日历,instruments/all.txt 定义股票池及起止时间,features/ 下按股票代码分目录存放各字段的二进制文件。字段名如 $close、$volume、$factor 在表达式中直接引用。

组成部分作用常见文件
日历定义交易日序列,对齐所有时间索引calendars/day.txt
股票池定义可交易标的及有效区间instruments/all.txt
特征数据存储行情、财务、因子等字段features/*.bin
表达式缓存加速复杂因子重复计算由引擎自动管理

三、表达式引擎:Qlib 的“因子语言”

表达式引擎允许你用类似公式的字符串描述因子,例如 Ref($close, 1) 表示昨日收盘价,Mean($close, 5) 表示 5 日均价。引擎会自动解析依赖、按时间对齐、处理缺失值,并支持缓存。

常见运算符与函数包括:

  • 算术运算:+ - * /,支持字段与常数混合运算。
  • 时序函数:Ref(滞后)、Mean(均值)、Std(标准差)、Corr(相关性)。
  • 截面函数:Rank(排名)、ZScore(标准化),用于横截面比较。
  • 逻辑函数:If、Greater、Less,用于条件筛选。

例如,一个简单的动量因子可以写成:

Ref($close, 1) / Ref($close, 21) - 1

这表示过去 20 个交易日的收益率。引擎会自动完成对齐与计算,你无需手动处理停牌、缺失等问题。

技巧:表达式中的 $ 前缀表示字段引用。字段名必须与数据目录中的 .bin 文件名一致。若字段不存在,引擎会报错而非静默返回空值,这有助于尽早发现数据问题。

四、快速上手案例:从数据到预测

下面用一个最小工作流演示 Qlib 的典型使用方式。假设已完成数据准备,目标是训练一个简单模型并生成预测。

import qlib
from qlib.constant import REG_CN
from qlib.utils import init_instance_by_config

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

config = {
    "model": {
        "class": "LGBModel",
        "module_path": "qlib.contrib.model.gbdt",
        "kwargs": {"loss": "mse", "num_leaves": 31}
    },
    "dataset": {
        "class": "DatasetH",
        "module_path": "qlib.data.dataset",
        "kwargs": {
            "handler": {
                "class": "Alpha158",
                "module_path": "qlib.contrib.data.handler",
                "kwargs": {
                    "start_time": "2018-01-01",
                    "end_time": "2020-12-31",
                    "fit_start_time": "2018-01-01",
                    "fit_end_time": "2019-12-31",
                    "instruments": "csi300"
                }
            },
            "segments": {
                "train": ("2018-01-01", "2019-12-31"),
                "valid": ("2020-01-01", "2020-06-30"),
                "test": ("2020-07-01", "2020-12-31")
            }
        }
    }
}

model = init_instance_by_config(config["model"])
dataset = init_instance_by_config(config["dataset"])
model.fit(dataset)
pred = model.predict(dataset)

这段代码体现了 Qlib 的核心工作流:初始化 → 构建数据集 → 训练模型 → 预测。其中 Alpha158 是内置的因子集,内部大量使用表达式引擎生成因子。你可以把它替换成自定义表达式集合,从而控制因子内容。

常见误区:很多初学者直接修改 Alpha158 的源码来加因子,这会导致升级困难且难以复现。正确做法是继承 Handler 或使用 DataHandlerLP 自定义表达式列表,把因子定义与框架代码分离。

五、复盘与建议

Qlib 的学习曲线主要集中在前期的数据准备和表达式理解上。一旦数据格式正确、表达式写对,后续的模型与回测会非常顺畅。建议按以下顺序推进:

  1. 先用官方数据跑通一个完整案例,确认环境与数据无误。
  2. 再尝试用表达式引擎手写 3~5 个因子,观察输出结构。
  3. 最后替换模型或因子集,理解配置驱动的替换机制。

需要特别注意的是,Qlib 的表达式引擎虽然强大,但并非所有复杂逻辑都能用表达式表达。涉及跨股票、跨时间的复杂状态时,仍需在 Python 层处理。理解边界,才能用好工具。