无噪 logo
  1. qlib 速读指南
  2. 快速入门示例

3. 快速入门示例

本文将通过一个完整的示例,展示如何使用 QLib 构建量化投资研究流程,包括数据准备、模型训练、回测和结果分析。我们将从最基础的代码开始,逐步深入核心 API 的使用方法,并介绍如何可视化和分析策略结果,最后分享一些常见错误和调试技巧。

第一个 QLib 程序:预测与回测

作为一名量化研究员,最关心的是如何快速验证自己的投资想法。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 解析

上面的示例程序虽然看起来有些复杂,但实际上遵循了一个清晰的结构。理解这个结构和其中使用的核心 API,将帮助你更好地使用 QLib 进行量化研究。

QLib 程序的基本结构

一个典型的 QLib 程序通常包含以下几个部分:

1.** 环境初始化 :通过 qlib.init() 函数初始化 QLib 环境 2. 配置定义 :定义数据处理、模型和回测等配置 3. 组件初始化 :通过配置初始化数据集、模型等组件 4. 模型训练 :使用数据集训练模型 5. 预测与回测 :使用训练好的模型生成预测信号并进行回测 6. 结果分析 **:分析回测结果并生成报告

下面来详细解析其中的核心 API。

初始化函数:qlib.init()

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_uriregion 两个参数。

配置初始化:init_instance_by_config()

QLib 采用了基于配置的设计理念,几乎所有组件都可以通过配置字典来定义。init_instance_by_config() 函数则负责将这些配置字典转换为实际的对象实例:

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

这种设计有几个好处:首先,它使得组件的定义更加灵活,可以通过修改配置而不是代码来改变组件行为;其次,它便于序列化和存储实验配置,有利于实验的可复现性。

实验记录:Workflow 模块

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,可以轻松地跟踪不同实验的参数、模型和结果,便于比较和分析。

数据集:Dataset 与 DataHandler

数据集是机器学习的基础,QLib 提供了强大而灵活的数据处理能力。在示例中,我们使用了 DatasetHAlpha158

"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 则负责将数据划分为训练集、验证集和测试集,并提供数据加载接口。

模型:LGBModel

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

策略和回测是量化研究的核心环节。示例中使用了 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(信息系数)是一个重要的评估指标,它衡量了因子预测值与实际收益率之间的相关性:

# 生成 IC 分析图表
analysis_position.score_ic_graph(pred_df, freq="day")

IC 分析图表通常包括:

  • IC 值序列
  • IC 均值和标准差
  • 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)

Redis 相关问题

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 提供了强大的工具,但最终的成功还是取决于研究思路和不懈努力。