Qlib 工作流管理详解:用一份 YAML 配置驱动 qrun 完成数据、训练与回测全流程
本篇指南围绕 Qlib 的 Workflow 管理模块(qrun)展开,完整讲解 qrun 配置文件的每个字段含义(qlib_init、task 下的 model/dataset/record 等),并结合 qlib/cli/run.py 与 qlib/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 约定:一段组件配置通常包含 class、module_path、kwargs 三个字段。例如上面的 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 组件用其自动注入model与dataset),若触发TypeError则回退为不附加参数重试,这让 record 的声明形式保持极简。
源码视角:qrun 的完整调用链
qrun 的入口实现位于 qlib/cli/run.py 的 workflow(config_path, experiment_name="workflow", uri_folder="mlruns") 函数,实际执行顺序为:
-
模板渲染:
render_template会用 Jinja2 解析配置文件,把其中未声明的变量从环境变量os.environ取值填充(qlib/cli/run.py)。因此配置文件支持环境变量插值,便于在不同机器/数据目录间复用同一份配置; -
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; -
sys 段:
sys.path/sys.rel_path可把额外目录加入模块搜索路径(相对路径以配置文件所在目录为基准),用于加载自定义模型/策略所在的代码目录; -
初始化 Qlib:以
config["qlib_init"]的字段调用qlib.init;若其中指定了exp_manager则直接使用,否则把 MLflow 的uri设为当前工作目录下的mlruns文件夹后注入; -
执行训练:调用 qlib/model/trainer.py 的
task_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.py 的Reweighter),_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 段描述训练/推理使用的模型,class、module_path、kwargs 三个字段含义为:
- class:str,模型类名;
- module_path:str,该模型类在 Qlib 中的模块路径;
- kwargs:模型的构造关键字参数,具体参数与取值请参考各模型实现(qlib/contrib/model 目录下包含 LGB、XGBoost、CatBoost、各类 PyTorch 深度模型等)。
Qlib 提供了 init_instance_by_config 这一工具函数,任何符合 class/module_path/kwargs 约定的类都可以用同一套配置方式初始化——这也是为什么换模型(例如把 LGBModel 换成 XGBModel 或 GRU)只需要改 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 组合使用,表示回测交易区间的起止、涨跌停阈值、成交价取值、开/平手续费率与最低手续费。)strategy 与 backtest 各字段含义可分别参考 Strategy 文档 与 Backtest 文档 中回测相关部分。
各 Record 模板(如 SignalRecord 与 PortAnaRecord)的配置细节如下:
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.py(qlib.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.model、task.dataset(含 handler 与 segments)描述模型与数据管道,YAML 锚点(&/*)让标的池、基准、时间区间等参数单点定义、多处复用; - 用
task.record挂接SignalRecord/PortAnaRecord,一次 execution 同时产出信号分析与回测报告,且全部产物由 Recorder 体系 归档,保证实验可复现、可比对; - 底层由 qlib/cli/run.py 的模板渲染、配置继承与
qlib.init,以及 qlib/model/trainer.py 中task_train→_exe_task的实例化-训练-记录链条承载,所有组件均通过 init_instance_by_config 统一约定初始化。
若需要深入组件细节,可继续参考:Qlib Model、Qlib Data、Strategy、Recorder,以及仓库 examples/benchmarks 下各模型的现成 workflow 配置文件。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00