2. Qlib基础回顾

在进入多模型集成与信号融合之前,有必要把 Qlib 的整体骨架重新梳理一遍。很多实战问题——例如“为什么模型训练完了却回测不出信号”“为什么数据更新后因子值对不上”——根源往往不在模型本身,而在于对 Qlib 分层职责与工作流机制理解不清。本节围绕架构分层、数据层、模型层、策略层与工作流五个部分展开,帮助你建立一张可复用的“Qlib 心智地图”。

一、核心原理:Qlib 的分层架构

Qlib 的设计遵循“数据—模型—策略—执行”的解耦思想。它把量化研究拆成若干职责单一的层,每层通过标准接口通信,从而让研究者可以独立替换某一层而不影响其他层。整体上可划分为四层加一条工作流主线:

  • 数据层(Data Layer):负责原始行情、财务、因子等数据的存储、表达式计算与高效读取,核心是 DataHandler 与表达式引擎。
  • 模型层(Model Layer):负责从特征到预测信号的映射,核心是 Model 基类及其派生模型(如 LightGBM、LSTM、Transformer)。
  • 策略层(Strategy Layer):负责把预测分数转换为具体交易决策,核心是 Strategy 基类,如 TopkDropoutStrategy。
  • 执行与回测层(Execution & Backtest):负责撮合、成本计算、持仓管理与绩效评估,核心是 Executor 与 Backtest。
  • 工作流(Workflow):以 qrun 与 YAML 配置为入口,把上述各层串联成可复现的流水线。

理解这张分层图的关键在于:每一层只关心自己的输入输出契约。数据层输出的是带时间索引的特征矩阵,模型层输出的是预测分数,策略层输出的是目标持仓,执行层输出的是成交与净值曲线。多模型集成本质上是在“模型层”与“策略层”之间插入一个信号融合环节,因此必须先看清这两层的接口形态。

二、数据层:表达式引擎与 DataHandler

Qlib 数据层最容易被低估的能力是表达式引擎。你可以在配置中直接写 Ref($close, 1)、Mean($volume, 5) 这类表达式,Qlib 会将其解析为算子并高效计算,而无需手动写 pandas 循环。数据以二进制格式(.bin)按列存储,配合内存缓存,读取速度远快于逐行 CSV。

数据层最常用的组件是 DataHandler,它决定了“模型看到什么”。典型配置如下:

data_handler_config:
  start_time: 2018-01-01
  end_time: 2022-12-31
  fit_start_time: 2018-01-01
  fit_end_time: 2020-12-31
  instruments: csi300
  infer_processors:
    - class: RobustZScoreNorm
      kwargs:
        fields_group: feature
        clip_outlier: true
    - class: Fillna
      kwargs:
        fields_group: feature
  learn_processors:
    - class: DropnaLabel
    - class: CSRankNorm
      kwargs:
        fields_group: label

这里有两个高频误区。第一,把 fit_start_time 与 start_time 混为一谈:前者用于拟合归一化参数,必须严格早于或等于训练区间,否则会造成未来信息泄露。第二,忘记 learn_processors 中的标签处理:标签若未做截面排序或去极值,模型学到的分布与回测评估口径会不一致。

建议在数据层就固定好“特征处理”和“标签处理”两条链路,后续多模型集成时所有模型共享同一份 DataHandler,才能保证信号可比、融合有意义。

三、模型层与策略层:接口与职责边界

模型层的统一接口是 fit(dataset) 与 predict(dataset)。无论底层是树模型还是深度网络,只要实现这两个方法,就能被工作流调度。模型输出的 pred 通常是一个 DataFrame,索引为 (datetime, instrument),值为预测分数。

策略层接收这个分数,并决定买卖。以 TopkDropoutStrategy 为例,它按分数排序取前 K 只,同时按 dropout 规则替换部分持仓,以控制换手。其核心参数包括 topk、n_drop、signal 等。策略层不关心分数是怎么来的,只关心分数的高低排序是否稳定。

层级核心组件输入输出常见误区
数据层DataHandler / 表达式引擎原始行情、财务数据特征矩阵 + 标签归一化拟合区间泄露未来
模型层Model 基类及派生模型特征矩阵预测分数 pred训练/验证切分未按时间
策略层Strategy 基类预测分数目标持仓忽略交易成本与换手约束
执行层Executor / Backtest目标持仓成交、净值曲线成本参数与实盘不符
工作流qrun + YAML配置文件可复现实验配置散落、无法版本管理

四、工作流:qrun 与 YAML 配置

Qlib 用一份 YAML 描述整个实验:数据、模型、策略、回测全部声明式配置,然后通过 qrun 一键执行。这带来的最大好处是可复现——同一份配置在任何机器上都能跑出相同结果(在数据一致的前提下)。

qrun workflow_config_lightgbm.yaml

一个典型工作流配置的结构如下:

qlib_init:
  provider_uri: "~/.qlib/qlib_data/cn_data"
  region: cn
market: &market csi300
benchmark: &benchmark SH000300
data_handler_config: *data_handler_config
task:
  model:
    class: LGBModel
    module_path: qlib.contrib.model.gbdt
    kwargs:
      loss: mse
      learning_rate: 0.05
  dataset:
    class: DatasetH
    kwargs:
      handler:
        class: Alpha158
        kwargs: *data_handler_config
      segments:
        train: [2018-01-01, 2020-12-31]
        valid: [2021-01-01, 2021-12-31]
        test: [2022-01-01, 2022-12-31]
  record:
    - class: SignalRecord
    - class: PortAnaRecord

注意 segments 的时间切分必须严格按时间顺序,且 train 与 valid 不能重叠。很多“回测虚高”的案例,都是因为验证集混入了训练区间,导致模型选择阶段就已经过拟合。

工作流中的 record 决定了你最终能看到什么。若只配置 SignalRecord,你只能拿到预测分数;要看到净值曲线与回撤,必须加上 PortAnaRecord 并配置回测参数。多模型集成时,建议先统一记录格式,再做信号对齐。

五、案例复盘:一次“信号对不上”的排查

某次实验中,研究者训练了 LightGBM 与 LSTM 两个模型,想把两者分数平均后送入策略,却发现融合后的回测反而变差。排查过程如下:

  1. 检查两个模型的 pred 索引是否一致——发现 LSTM 的预测日期比 LightGBM 少了一天,原因是 LSTM 的 step_len 导致首日无法预测。
  2. 检查分数分布——LightGBM 输出的是回归值,LSTM 输出的是经过 tanh 的值,量纲差异巨大,直接平均等于让 LSTM 主导。
  3. 检查策略层——TopkDropoutStrategy 只关心排序,但量纲差异会改变排序结果,尤其在分数接近时。

最终解决方案是:先对两个模型的分数做截面排序归一化,再对齐日期索引,最后融合。这个案例说明,多模型集成的第一步不是“融合算法”,而是把各层接口对齐。数据层、模型层、策略层的契约一旦清晰,融合才有稳定的基础。

回顾本节要点:Qlib 的分层架构让每一层职责单一、接口标准;数据层决定模型看到什么,模型层决定预测分数,策略层决定持仓,执行层决定绩效;工作流用 YAML 把这一切串起来。多模型集成与信号融合,本质上是在模型层与策略层之间增加一个“信号对齐与融合”环节,而它的前提,是对 Qlib 各层接口的准确理解。