本文将通过一个完整的示例,展示如何使用 QLib 构建量化投资研究流程,包括数据准备、模型训练、回测和结果分析。我们将从最基础的代码开始,逐步深入核心 API 的使用方法,并介绍如何可视化和分析策略结果,最后分享一些常见错误和调试技巧。
作为一名量化研究员,最关心的是如何快速验证自己的投资想法。QLib 提供了一套完整的工具链,让我们可以从数据获取到策略评估,一站式完成整个量化研究流程。
在开始编写代码之前,需要确保 QLib 已经正确安装并初始化。如果你按照第一章的步骤安装了 QLib,那么现在只需要简单的几行代码就可以完成数据初始化:
import qlib
from qlib.constant import REG_CN
from qlib.utils import exists_qlib_data
# 数据存储路径
provider_uri = "~/.qlib/qlib_data/cn_data"
# 检查数据是否存在,如果不存在则下载
if not exists_qlib_data(provider_uri):
print(f"Qlib 数据不存在,正在下载到 {provider_uri}")
from qlib.tests.data import GetData
GetData().qlib_data(target_dir=provider_uri, region=REG_CN)
# 初始化 QLib
qlib.init(provider_uri=provider_uri, region=REG_CN)
print("QLib 初始化成功!")
这段代码首先检查指定路径下是否存在 QLib 数据,如果不存在,就会自动下载。然后通过 qlib.init()
函数初始化 QLib 环境,这个函数会加载数据并准备好所有必要的组件。
下面来看一个完整的 QLib 程序,它包含了从数据准备、模型训练到回测评估的整个流程:
import qlib
import pandas as pd
from qlib.constant import REG_CN
from qlib.utils import init_instance_by_config, flatten_dict
from qlib.workflow import R
from qlib.workflow.record_temp import SignalRecord, PortAnaRecord
from qlib.data import D
# 初始化 QLib
provider_uri = "~/.qlib/qlib_data/cn_data"
qlib.init(provider_uri=provider_uri, region=REG_CN)
# 定义市场和基准
market = "csi300"
benchmark = "SH000300"
# 定义数据处理配置
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,
}
# 定义任务配置
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"),
},
},
},
}
# 初始化模型和数据集
model = init_instance_by_config(task["model"])
dataset = init_instance_by_config(task["dataset"])
# 开始实验并训练模型
with R.start(experiment_name="train_model"):
R.log_params(**flatten_dict(task))
model.fit(dataset)
R.save_objects(trained_model=model)
rid = R.get_recorder().id
# 定义回测配置
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": {
"model": model,
"dataset": dataset,
"topk": 50,
"n_drop": 5,
},
},
"backtest": {
"start_time": "2017-01-01",
"end_time": "2020-08-01",
"account": 100000000,
"benchmark": benchmark,
"exchange_kwargs": {
"freq": "day",
"limit_threshold": 0.095,
"deal_price": "close",
"open_cost": 0.0005,
"close_cost": 0.0015,
"min_cost": 5,
},
},
}
# 执行回测和分析
with R.start(experiment_name="backtest_analysis"):
recorder = R.get_recorder(recorder_id=rid, experiment_name="train_model")
model = recorder.load_object("trained_model")
# 生成预测信号
recorder = R.get_recorder()
ba_rid = recorder.id
sr = SignalRecord(model, dataset, recorder)
sr.generate()
# 执行回测并分析结果
par = PortAnaRecord(recorder, port_analysis_config, "day")
par.generate()
print("策略回测完成!")
这个程序虽然不长,但包含了一个完整的量化研究流程。它首先初始化 QLib 环境,然后定义了一个使用 LightGBM 模型的预测任务,接着训练模型并进行回测,最后生成策略评估报告。
上面的示例程序虽然看起来有些复杂,但实际上遵循了一个清晰的结构。理解这个结构和其中使用的核心 API,将帮助你更好地使用 QLib 进行量化研究。
一个典型的 QLib 程序通常包含以下几个部分:
1.** 环境初始化 :通过 qlib.init()
函数初始化 QLib 环境
2. 配置定义 :定义数据处理、模型和回测等配置
3. 组件初始化 :通过配置初始化数据集、模型等组件
4. 模型训练 :使用数据集训练模型
5. 预测与回测 :使用训练好的模型生成预测信号并进行回测
6. 结果分析 **:分析回测结果并生成报告
下面来详细解析其中的核心 API。
qlib.init()
是使用 QLib 的第一个关键函数,它负责初始化整个 QLib 环境。其主要参数包括:
qlib.init(
provider_uri="~/.qlib/qlib_data/cn_data", # 数据存储路径
region=REG_CN, # 市场区域,REG_CN 表示中国市场
redis_host="localhost", # Redis 主机地址
redis_port=6379, # Redis 端口
redis_task_db=1, # Redis 数据库编号
logging_level=logging.INFO, # 日志级别
)
这个函数会根据提供的参数,初始化数据提供器、缓存系统和日志系统等核心组件。在大多数情况下,只需要指定 provider_uri
和 region
两个参数。
QLib 采用了基于配置的设计理念,几乎所有组件都可以通过配置字典来定义。init_instance_by_config()
函数则负责将这些配置字典转换为实际的对象实例:
model = init_instance_by_config(task["model"])
dataset = init_instance_by_config(task["dataset"])
这种设计有几个好处:首先,它使得组件的定义更加灵活,可以通过修改配置而不是代码来改变组件行为;其次,它便于序列化和存储实验配置,有利于实验的可复现性。
QLib 的 Workflow 模块提供了一套完整的实验管理功能,能够自动记录实验参数、模型和结果。核心 API 包括:
# 开始一个实验
with R.start(experiment_name="train_model"):
# 记录实验参数
R.log_params(**flatten_dict(task))
# 训练模型
model.fit(dataset)
# 保存模型
R.save_objects(trained_model=model)
# 获取记录器 ID
rid = R.get_recorder().id
这个模块解决了量化研究中一个常见的痛点:如何系统地管理大量的实验。通过 Workflow,可以轻松地跟踪不同实验的参数、模型和结果,便于比较和分析。
数据集是机器学习的基础,QLib 提供了强大而灵活的数据处理能力。在示例中,我们使用了 DatasetH
和 Alpha158
:
"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"),
},
},
}
这里,Alpha158
是一个预定义的数据处理器,它实现了 158 个常用的 Alpha 因子。DatasetH
则负责将数据划分为训练集、验证集和测试集,并提供数据加载接口。
QLib 内置了多种常用的机器学习模型,示例中使用的是 LightGBM 模型:
"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,
},
}
LGBModel 是对 LightGBM 的封装,它实现了 QLib 的模型接口,能够与其他组件无缝集成。除了 LightGBM,QLib 还支持 XGBoost、CatBoost、MLP 等多种模型。
策略和回测是量化研究的核心环节。示例中使用了 TopkDropoutStrategy
策略和 SimulatorExecutor
执行器:
"strategy": {
"class": "TopkDropoutStrategy",
"module_path": "qlib.contrib.strategy.signal_strategy",
"kwargs": {
"model": model,
"dataset": dataset,
"topk": 50,
"n_drop": 5,
},
},
"executor": {
"class": "SimulatorExecutor",
"module_path": "qlib.backtest.executor",
"kwargs": {
"time_per_step": "day",
"generate_portfolio_metrics": True,
},
}
TopkDropoutStrategy
是一个简单但有效的策略,它每天选择模型预测分数最高的 50 只股票,并剔除其中 5 只持仓最久的股票。SimulatorExecutor
则负责模拟交易执行过程,计算交易成本和投资组合收益。
量化研究不仅仅是构建模型和回测,更重要的是分析结果,理解模型的表现和潜在问题。QLib 提供了丰富的可视化工具,帮助直观地分析策略表现。
在示例程序的最后,已经生成了回测结果。要进行可视化分析,首先需要获取这些结果:
from qlib.contrib.report import analysis_model, analysis_position
# 获取记录器
recorder = R.get_recorder(recorder_id=ba_rid, experiment_name="backtest_analysis")
# 加载回测结果
pred_df = recorder.load_object("pred.pkl") # 预测结果
report_normal_df = recorder.load_object("portfolio_analysis/report_normal_1day.pkl") # 普通报告
positions = recorder.load_object("portfolio_analysis/positions_normal_1day.pkl") # 持仓记录
analysis_df = recorder.load_object("portfolio_analysis/port_analysis_1day.pkl") # 分析报告
这些结果包含了策略的各种表现指标,如收益率、最大回撤、信息比率等。
QLib 提供了一个便捷的函数,可以一键生成综合的策略表现报告:
# 生成综合报告图表
analysis_position.report_graph(report_normal_df)
这个函数会生成一个包含多个子图的综合报告,包括:
这些图表可以帮助快速了解策略的整体表现。
除了综合报告,还可以进行更深入的风险分析:
# 生成风险分析图表
analysis_position.risk_analysis_graph(analysis_df, report_normal_df)
风险分析图表通常包括:
这些指标可以帮助评估策略的风险收益特征。
对于因子模型,IC(信息系数)是一个重要的评估指标,它衡量了因子预测值与实际收益率之间的相关性:
# 生成 IC 分析图表
analysis_position.score_ic_graph(pred_df, freq="day")
IC 分析图表通常包括:
高 IC 值表明因子具有较强的预测能力。
生成图表后,需要能够正确解读这些结果。以综合报告为例,应该关注以下几点:
1.** 累计收益率 :策略是否跑赢基准?有无明显的绩效拐点? 2. 最大回撤 :策略的风险水平如何?回撤发生在什么时间段? 3. 换手率 :策略的交易频率如何?是否过高导致交易成本侵蚀收益? 4. 超额收益稳定性 **:超额收益是否稳定?是否存在明显的周期性?
通过这些分析,可以判断策略的优劣,并找出改进的方向。
在使用 QLib 的过程中,难免会遇到各种问题。本节将介绍一些常见的错误和调试技巧,帮助更顺利地进行量化研究。
问题 1:数据不存在或路径错误
当运行程序时,如果遇到类似以下的错误:
Qlib data is not found in ~/.qlib/qlib_data/cn_data
这通常意味着指定路径下没有 QLib 数据。解决方法很简单,只需运行数据下载脚本:
from qlib.tests.data import GetData
GetData().qlib_data(target_dir=provider_uri, region=REG_CN)
问题 2:数据版本不兼容
QLib 的数据格式可能会随着版本更新而变化。如果升级了 QLib,可能需要重新下载数据:
# 强制重新下载数据(添加 force=True 参数)
GetData().qlib_data(target_dir=provider_uri, region=REG_CN, force=True)
QLib 使用 Redis 进行缓存和任务管理,有时可能会遇到 Redis 相关的错误。
问题 1:Redis 锁冲突
qlib.data.cache.QlibCacheException: It sees the key(...) of the redis lock has existed in your redis db now.
这个错误通常发生在之前的程序异常退出,导致 Redis 中残留了锁信息。解决方法是清除 Redis 中的相关键:
# 在终端中执行
redis-cli
> select 1 # QLib 默认使用数据库 1
> flushdb # 清除当前数据库中的所有键
或者在 Python 代码中设置不同的 Redis 数据库:
qlib.init(redis_task_db=2) # 使用数据库 2,避免与其他程序冲突
问题 1:缺少 Cython 编译的模块
ModuleNotFoundError: No module named 'qlib.data._libs.rolling'
这个错误通常发生在从源码安装 QLib 但没有正确编译 Cython 模块的情况下。解决方法是重新编译:
cd qlib # 进入 QLib 源码目录
python setup.py build_ext --inplace # 编译 Cython 模块
问题 2:运行目录问题
如果在 QLib 源码目录下直接运行程序,可能会遇到导入问题。解决方法是将工作目录切换到源码目录之外:
cd .. # 退出 QLib 源码目录
python your_script.py # 运行你的程序
问题 1:socketio 版本不兼容
BadNamespaceError: / is not a connected namespace
或
TypeError: send() got an unexpected keyword argument 'binary'
这些错误通常是由于 python-socketio 和 python-engineio 版本不兼容导致的。解决方法是安装兼容的版本:
pip install -U python-socketio==3.1.2 python-engineio==3.13.2
技巧 1:启用详细日志
在调试时,启用详细日志可以帮助了解程序的运行过程:
import logging
qlib.init(logging_level=logging.DEBUG) # 设置为 DEBUG 级别,获取详细日志
技巧 2:检查数据
在训练模型之前,先检查数据是否符合预期:
# 查看数据集的前几行
example_df = dataset.prepare("train")
print(example_df.head())
# 检查数据统计信息
print(example_df.describe())
技巧 3:逐步执行
对于复杂的程序,可以采用逐步执行的方式,检查每一步的结果是否符合预期:
# 1. 初始化数据
# 2. 检查数据
# 3. 初始化模型
# 4. 训练模型(先使用少量数据)
# 5. 检查模型预测
# 6. 执行回测(先使用较短时间窗口)
# 7. 分析结果
技巧 4:使用 Jupyter Notebook
Jupyter Notebook 提供了交互式编程环境,非常适合调试和探索性分析。QLib 提供了多个 Notebook 示例,可以在 examples
目录下找到它们。
本章通过一个完整的示例,介绍了 QLib 的基本使用方法。从环境初始化开始,逐步讲解了数据准备、模型训练、回测执行和结果分析的整个流程。同时,解析了 QLib 的核心 API 和代码结构,并分享了一些常见错误和调试技巧。
掌握这些知识后,应该能够使用 QLib 构建简单的量化策略了。但 QLib 的功能远不止于此,在后续章节中,将深入学习 QLib 的更多高级特性,如特征工程、自定义模型和高级策略设计等。
记住,量化研究是一个迭代的过程。不要期望一次就能构建出完美的策略,而是要不断尝试、分析和改进。QLib 提供了强大的工具,但最终的成功还是取决于研究思路和不懈努力。