首页
/ Qlib 工作流管理详解:用一份 YAML 配置驱动 qrun 完成数据、训练与回测全流程

Qlib 工作流管理详解:用一份 YAML 配置驱动 qrun 完成数据、训练与回测全流程

2026-09-05 23:03:01作者:管翌锬

本篇指南围绕 Qlib 的 Workflow 管理模块(qrun)展开,完整讲解 qrun 配置文件的每个字段含义(qlib_init、task 下的 model/dataset/record 等),并结合 qlib/cli/run.pyqlib/model/trainer.py 的源码剖析一次 execution 从配置解析、组件实例化到记录产物的底层调用链。读完后你可以独立编写、调试并扩展自己的量化研究工作流配置,理解 Qlib 如何把松散耦合的组件自动拼装成一条可复现的端到端流水线。

什么是 Qlib 的 Workflow:从组件到 execution

Qlib 框架的组件在设计上是松散耦合(loosely-coupled)的。用户既可以像搭积木一样用代码自行拼装量化研究工作流(仓库中给出了代码示例 examples/workflow_by_code.py),也可以使用 Qlib 提供的更友好的命令行接口 qrun——用一份配置文件自动运行完整工作流。

一次 qrun 的运行在 Qlib 中被称为一次 execution(执行),它固定包含三大阶段:

  • Data(数据):Loading(加载)、Processing(处理)、Slicing(切片);
  • Model(模型):Training and inference(训练与推理)、Saving & loading(保存与加载);
  • Evaluation(评估):Forecast signal analysis(预测信号分析)、Backtest(回测)。

对每一次 execution,Qlib 都有一套完整的实验跟踪体系,记录训练、推理与评估阶段产生的所有信息和产物(模型、预测、回测报告等)。这部分机制由 Recorder 模块承担,详见 Recorder: Experiment Management

完整示例:一份典型的 qrun 配置

在进入细节之前,先看一个定义典型量化研究工作流的完整配置示例。这就是仓库内置基准配置(如 workflow_config_lightgbm_Alpha158.yaml)所遵循的结构:

qlib_init:
    provider_uri: "~/.qlib/qlib_data/cn_data"
    region: cn
market: &market csi300
benchmark: &benchmark SH000300
data_handler_config: &data_handler_config
    start_time: 2008-01-01
    end_time: 2020-08-01
    fit_start_time: 2008-01-01
    fit_end_time: 2014-12-31
    instruments: *market
port_analysis_config: &port_analysis_config
    strategy:
        class: TopkDropoutStrategy
        module_path: qlib.contrib.strategy.strategy
        kwargs:
            topk: 50
            n_drop: 5
            signal: <PRED>
    backtest:
        start_time: 2017-01-01
        end_time: 2020-08-01
        account: 100000000
        benchmark: *benchmark
        exchange_kwargs:
            limit_threshold: 0.095
            deal_price: close
            open_cost: 0.0005
            close_cost: 0.0015
            min_cost: 5
task:
    model:
        class: LGBModel
        module_path: qlib.contrib.model.gbdt
        kwargs:
            loss: mse
            colsample_bytree: 0.8879
            learning_rate: 0.0421
            subsample: 0.8789
            lambda_l1: 205.6999
            lambda_l2: 580.9768
            max_depth: 8
            num_leaves: 210
            num_threads: 20
    dataset:
        class: DatasetH
        module_path: qlib.data.dataset
        kwargs:
            handler:
                class: Alpha158
                module_path: qlib.contrib.data.handler
                kwargs: *data_handler_config
            segments:
                train: [2008-01-01, 2014-12-31]
                valid: [2015-01-01, 2016-12-31]
                test: [2017-01-01, 2020-08-01]
    record:
        - class: SignalRecord
          module_path: qlib.workflow.record_temp
          kwargs: {}
        - class: PortAnaRecord
          module_path: qlib.workflow.record_temp
          kwargs:
              config: *port_analysis_config

保存为 configuration.yaml 后,只需一条命令即可启动整个工作流:

qrun configuration.yaml

如果想以调试模式运行 qrun(例如在断点下检查配置解析与执行流程),可以使用:

python -m pdb qlib/cli/run.py examples/benchmarks/LightGBM/workflow_config_lightgbm_Alpha158.yaml

两点需要注意:

  • 安装 Qlib 后,qrun 可执行入口会放进 $PATH 目录,本质上是调用 qlib/cli/run.py 中的 workflow 函数(由 fire 包装成命令行参数);
  • YAML 中的 & 是锚点(anchor)语法。以 &market&benchmark 定义后,其他字段可用 *market*benchmark 引用。上例中如果更换标的池,只需修改 market 一行的值,而无需遍历整份配置逐一修改——这是该配置结构刻意利用 YAML 锚点保持可维护性的设计。

配置文件的设计逻辑:init_instance_by_config

qrun 配置文件的设计逻辑非常直接:Qlib 预先定义了固定的工作流骨架(初始化数据 → 构造数据集 → 训练模型 → 记录评估),配置文件的作用只是告诉 Qlib 每个组件用什么类、带什么参数来初始化

其底层遵循 qlib/utils/mod.py 中的 init_instance_by_config 约定:一段组件配置通常包含 classmodule_pathkwargs 三个字段。例如上面的 model 配置:

model:
    class: LGBModel
    module_path: qlib.contrib.model.gbdt
    kwargs:
        loss: mse
        colsample_bytree: 0.8879
        learning_rate: 0.0421
        subsample: 0.8789
        lambda_l1: 205.6999
        lambda_l2: 580.9768
        max_depth: 8
        num_leaves: 210
        num_threads: 20

与下面这段 Python 代码完全等价:

from qlib.contrib.model.gbdt import LGBModel
kwargs = {
    "loss": "mse",
    "colsample_bytree": 0.8879,
    "learning_rate": 0.0421,
    "subsample": 0.8789,
    "lambda_l1": 205.6999,
    "lambda_l2": 580.9768,
    "max_depth": 8,
    "num_leaves": 210,
    "num_threads": 20,
}
LGBModel(kwargs)

从源码实现看(qlib/utils/mod.py),init_instance_by_config 还具备一些配置里没有写明的能力:

  • 默认模块:若配置缺省 module_path,会尝试从 default_module 参数指定的模块中加载类。工作流中 record 组件正是利用了这一点——_exe_task 传入 default_module="qlib.workflow.record_temp",所以 record 配置里可以省略 module_path 直接写 - class: SignalRecord
  • 直接接受实例:通过 accept_types 参数,若配置本身已经是某个类型(如 Model)的实例则直接返回;在 _exe_task 中对 model、dataset 都传了 accept_types,意味着高级用户可以在配置里直接复用已构造好的对象;
  • pickle 加载:配置值若为 file:// 形式的 URI,init_instance_by_config 会将其当作 pickle 文件加载并返回反序列化后的对象;
  • try_kwargs 容错:初始化时先尝试附加传入 try_kwargs(record 组件用其自动注入 modeldataset),若触发 TypeError 则回退为不附加参数重试,这让 record 的声明形式保持极简。

源码视角:qrun 的完整调用链

qrun 的入口实现位于 qlib/cli/run.pyworkflow(config_path, experiment_name="workflow", uri_folder="mlruns") 函数,实际执行顺序为:

  1. 模板渲染render_template 会用 Jinja2 解析配置文件,把其中未声明的变量从环境变量 os.environ 取值填充(qlib/cli/run.py)。因此配置文件支持环境变量插值,便于在不同机器/数据目录间复用同一份配置;

  2. BASE_CONFIG_PATH 继承:若配置中存在 BASE_CONFIG_PATH 字段,会先加载该基础配置,再用当前配置做增量更新(update_config),支持“只覆盖少量自定义字段”的用法。文档中的示例形如:

    qlib_init:
        provider_uri: "~/.qlib/qlib_data/cn_data"
        region: cn
    BASE_CONFIG_PATH: "workflow_config_lightgbm_Alpha158_csi500.yaml"
    market: csi300
    

    查找顺序为:先按绝对路径/当前工作目录查找,找不到再相对配置文件所在目录查找,仍找不到则抛出 FileNotFoundError

  3. sys 段sys.path / sys.rel_path 可把额外目录加入模块搜索路径(相对路径以配置文件所在目录为基准),用于加载自定义模型/策略所在的代码目录;

  4. 初始化 Qlib:以 config["qlib_init"] 的字段调用 qlib.init;若其中指定了 exp_manager 则直接使用,否则把 MLflow 的 uri 设为当前工作目录下的 mlruns 文件夹后注入;

  5. 执行训练:调用 qlib/model/trainer.pytask_train(config.get("task"), experiment_name=...),随后把整份配置通过 recorder.save_objects(config=config) 存入 recorder,保证实验可复现。

task_train 内部进入 R.start 上下文后,先由 _log_task_info 把任务配置写入 recorder 的 params/objects,再进入真正执行任务的核心函数 _exe_task_exe_task 的流程与配置文件的三大块一一对应:

# model & dataset initialization
model: Model = init_instance_by_config(task_config["model"], accept_types=Model)
dataset: Dataset = init_instance_by_config(task_config["dataset"], accept_types=Dataset)
reweighter: Reweighter = task_config.get("reweighter", None)
# model training
auto_filter_kwargs(model.fit)(dataset, reweighter=reweighter)
R.save_objects(**{"params.pkl": model})
# this dataset is saved for online inference. So the concrete data should not be dumped
dataset.config(dump_all=False, recursive=True)
R.save_objects(**{"dataset": dataset})
# fill placehorder
placehorder_value = {"<MODEL>": model, "<DATASET>": dataset}
task_config = fill_placeholder(task_config, placehorder_value)
# generate records: prediction, backtest, and analysis
records = task_config.get("record", [])
for record in records:
    r = init_instance_by_config(
        record,
        recorder=rec,
        default_module="qlib.workflow.record_temp",
        try_kwargs={"model": model, "dataset": dataset},
    )
    r.generate()

几个值得注意的实现细节:

  • 模型训练后以 params.pkl 为产物名保存模型对象;数据集在剥离具体数据(dump_all=False)后也一并保存,以支持在线推理时复用同一数据管道;
  • <MODEL><DATASET> 是 Qlib 的占位符(placeholder),会被 fill_placeholder 递归替换进 record 配置,所以 record 里可以直接引用模型与数据集实例而无需显式传递;
  • record 列表支持单个 dict 或 list 两种写法(源码中做了归一化),每条 record 在实例化后立即调用其 generate() 方法产出预测、信号分析或回测产物;
  • 此外 task 配置还支持可选的 reweighter 字段(对应 qlib/data/dataset/weight.pyReweighter),_exe_task 会将其传给 model.fit,用于样本加权训练。

配置字段详解

Qlib Init 段

配置文件首先需要包含 Qlib 初始化所需的基本参数:

provider_uri: "~/.qlib/qlib_data/cn_data"
region: cn

各字段含义:

  • provider_uri:str 类型,Qlib 数据的 URI,通常是 get_data.py(对应 scripts/get_data.py)下载的数据存放位置;
  • region:取值 "us" 时 Qlib 以美股模式初始化,取值 "cn" 时以 A 股模式初始化。注意 region 的取值必须与 provider_uri 中实际存放的数据对齐,二者不匹配会导致数据层行为异常。

Task 段总览

task 字段对应 Qlib 中的一个训练任务(task),包含三个子段:

  • model:训练与推理所用模型的参数;
  • dataset:数据集参数,其中又嵌套了数据处理器 DataHandler 的参数;
  • record:评估与记录模块的参数,负责以标准格式跟踪训练过程与结果(如 IC、回测报告)。

Model 段

model 段描述训练/推理使用的模型,classmodule_pathkwargs 三个字段含义为:

  • class:str,模型类名;
  • module_path:str,该模型类在 Qlib 中的模块路径;
  • kwargs:模型的构造关键字参数,具体参数与取值请参考各模型实现(qlib/contrib/model 目录下包含 LGB、XGBoost、CatBoost、各类 PyTorch 深度模型等)。

Qlib 提供了 init_instance_by_config 这一工具函数,任何符合 class/module_path/kwargs 约定的类都可以用同一套配置方式初始化——这也是为什么换模型(例如把 LGBModel 换成 XGBModelGRU)只需要改 model 段而不动其余配置。

Dataset 段

dataset 段描述 Qlib 的 Dataset 模块参数,以及其中 DataHandler 模块的参数。DataHandler 的关键字参数配置如下:

data_handler_config: &data_handler_config
    start_time: 2008-01-01
    end_time: 2020-08-01
    fit_start_time: 2008-01-01
    fit_end_time: 2014-12-31
    instruments: *market

各字段(start/end 时间、fit 时间段、标的池 instruments 等)的含义可参考 DataHandler 文档 的说明。fit_start_time/fit_end_time 定义了处理器参数(如标准化均值)的拟合窗口,把它限制在训练期内可避免用未来信息拟合统计量。

负责在训练/测试阶段做数据预处理与切片的 Dataset 模块配置如下:

dataset:
    class: DatasetH
    module_path: qlib.data.dataset
    kwargs:
        handler:
            class: Alpha158
            module_path: qlib.contrib.data.handler
            kwargs: *data_handler_config
        segments:
            train: [2008-01-01, 2014-12-31]
            valid: [2015-01-01, 2016-12-31]
            test: [2017-01-01, 2020-08-01]

segments 把时间轴划分为 train/valid/test 三段(注意 test 段的起止与下文回测区间保持一致),handler 子配置通过锚点 *data_handler_config 复用前面定义的时间与标的池参数。

Record 段

record 字段对应 Qlib 的 Record 模块。Record 负责以标准格式跟踪训练过程与结果,例如信息系数(IC)与回测(backtest)。

其中关于策略与回测的配置为:

port_analysis_config: &port_analysis_config
    strategy:
        class: TopkDropoutStrategy
        module_path: qlib.contrib.strategy.strategy
        kwargs:
            topk: 50
            n_drop: 5
            signal: <PRED>
    backtest:
        limit_threshold: 0.095
        account: 100000000
        benchmark: *benchmark
        deal_price: close
        open_cost: 0.0005
        close_cost: 0.0015
        min_cost: 5

(在完整示例中,backtest 还带有 start_time/end_time,并与 exchange_kwargs 组合使用,表示回测交易区间的起止、涨跌停阈值、成交价取值、开/平手续费率与最低手续费。)strategybacktest 各字段含义可分别参考 Strategy 文档Backtest 文档 中回测相关部分。

各 Record 模板(如 SignalRecordPortAnaRecord)的配置细节如下:

record:
    - class: SignalRecord
      module_path: qlib.workflow.record_temp
      kwargs: {}
    - class: PortAnaRecord
      module_path: qlib.workflow.record_temp
      kwargs:
          config: *port_analysis_config

SignalRecord 负责产出预测信号并做信号层面的分析,PortAnaRecord 接收 *port_analysis_config 锚点,依据其中的策略与回测配置完成组合层面分析。Record 模板的具体实现在 qlib/workflow/record_temp.pyqlib.contrib.workflow.record_temp 同样提供该模板),更多机制详见 Recorder 文档的 Record Template 小节

代码方式:与 qrun 等价的编程接口

qrun 并非唯一入口。examples/workflow_by_code.py 展示了“用代码组装工作流”的等价方式,其文件头注释明确指出:(1) 用简单配置定义工作流(qrun XXX.yaml)与 (2) 模块化地用代码搭建研究流水线“几乎做同样的事”。代码示例的核心片段:

provider_uri = "~/.qlib/qlib_data/cn_data"
GetData().qlib_data(target_dir=provider_uri, region=REG_CN, exists_skip=True)
qlib.init(provider_uri=provider_uri, region=REG_CN)

model = init_instance_by_config(CSI300_GBDT_TASK["model"])
dataset = init_instance_by_config(CSI300_GBDT_TASK["dataset"])

port_analysis_config = {
    "executor": {
        "class": "SimulatorExecutor",
        "module_path": "qlib.backtest.executor",
        "kwargs": {"time_per_step": "day", "generate_portfolio_metrics": True},
    },
    "strategy": {
        "class": "TopkDropoutStrategy",
        "module_path": "qlib.contrib.strategy.signal_strategy",
        "kwargs": {"signal": (model, dataset), "topk": 50, "n_drop": 5},
    },
    "backtest": { ... },
}

可以看到配置方式与代码方式共享同一套“class + module_path + kwargs”的实例化约定:在代码中通过 init_instance_by_config 加载 model/dataset 配置(预定义任务常量来自 qlib/tests/config.py),策略的 signal 直接引用 (model, dataset) 元组,最后用 R(Recorder)驱动 SignalRecord/PortAnaRecord 生成分析产物。需要说明的是,代码方式下 PortAnaRecord 的 config 中通常还会显式给出 executor(如 SimulatorExecutor),这在 YAML 方式中则可由回测配置隐式完成。

小结

qrun 的价值在于把“数据 → 模型 → 评估”这条固定流水线收敛为一份声明式 YAML:

  • qlib_init(provider_uri + region)对齐数据环境与数据本身;
  • task.modeltask.dataset(含 handler 与 segments)描述模型与数据管道,YAML 锚点(&/*)让标的池、基准、时间区间等参数单点定义、多处复用;
  • task.record 挂接 SignalRecord/PortAnaRecord,一次 execution 同时产出信号分析与回测报告,且全部产物由 Recorder 体系 归档,保证实验可复现、可比对;
  • 底层由 qlib/cli/run.py 的模板渲染、配置继承与 qlib.init,以及 qlib/model/trainer.pytask_train_exe_task 的实例化-训练-记录链条承载,所有组件均通过 init_instance_by_config 统一约定初始化。

若需要深入组件细节,可继续参考:Qlib ModelQlib DataStrategyRecorder,以及仓库 examples/benchmarks 下各模型的现成 workflow 配置文件。

登录后查看全文
热门项目推荐
相关项目推荐