ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Qlib量化平台实战:从因子计算到回测的完整学习记录

Qlib量化平台实战:从因子计算到回测的完整学习记录 如果你跟我一样手里已经攒了不少因子研究的代码也试过自己拼一套回测框架那么在第一次见到 Qlib微矿的时候大概率会有一种“终于有人把这条路铺好了”的感觉。Qlib 是微软开源的一套 AI 量化投资平台中文社区习惯叫它“微矿”它把量化研究这条链路上最费时间的脏活累活——数据清洗、因子计算、模型训练、策略回测、绩效分析——全部抽象成了标准化模块。你只要按照它的规则把数据和策略接进去就能快速验证自己的想法而不是每次都要重新造一遍轮子。这个项目适合两类人来学一类是想用 AI 模型做选股或者择时但不想被底层基础设施拖住的研究型选手另一类是已经有一定量化经验想进一步尝试 GNN、Transformer 这类前沿模型的工程师。我自己在这个项目上断断续续折腾了大半年从最开始只会照着官方示例敲命令到后来换自己的数据源、改策略逻辑、加自定义因子中间踩了不少坑。这篇笔记就是我踩坑之后整理出来的学习使用记录也是给自己留的一份参考资料后续随缘更新。1. 拆解 Qlib 的设计思路为什么它值得认真学1.1 量化研究的核心痛点Qlib 是怎么接住的做量化研究的人应该都有体会真正让人头疼的往往不是策略本身而是策略之外那一堆绕不开的工程问题。数据要从不同渠道拿格式还不统一因子算完要清洗、对齐、去极值、标准化模型训练完还要写一套回测逻辑跑完还要算夏普、最大回撤、信息比率这些指标。每一步单独看都不难但串起来之后代码量会迅速膨胀而且很容易出现两套代码、两套口径的混乱局面。Qlib 的设计目标就是把这些环节全部收编进一套框架。它把量化研究抽象成一条明确的工作流数据访问层管数据Handler 层负责生成因子和标签Dataset 层把处理好的数据切成训练集、验证集、测试集模型层负责训练和预测回测层负责模拟交易最后分析层输出绩效指标。你不需要在多个文件之间来回切换也不需要手工对齐数据格式只要沿着这条链路走下去整个实验过程就是可复现、可对比的。我自己最直观的感受是用了 Qlib 之后因子实验的“口径一致性”有了保障。以前我写因子时训练集和测试集经常因为数据窗口对不齐导致结论失真换到 Qlib 之后数据集切分由框架统一管理我只需要关心因子本身有没有逻辑问题而不需要反复检查数据有没有串。1.2 对比其他框架Qlib 的取舍在哪里市面上做量化的开源框架其实不少Backtrader、vn.py、zipline 都各有拥趸。但如果把选型放到“AI 量化建模”这个语境里Qlib 的定位其实很不一样。Backtrader 和 vn.py 的重心在交易执行和策略回测它们对回测引擎的细节处理得很好但在因子生成和模型训练这两个环节基本是空白。你仍然需要自己在外部把特征算好、模型训好再导入进去做回测。zipline 虽然有一整套 pipeline 的思路但它是为美股环境设计的而且社区活跃度相比前几年有所下降接国内市场数据需要自己折腾不少。Qlib 的取舍在于它在“研究”这个环节做得特别重。内置了 Alpha158、Alpha360 这样的大规模因子集也内置了 LightGBM、GRU、LSTM、Transformer 等一系列基线模型。官方还提供了大量 benchmark 实验结果方便你直接和它给出的基线做对比。代价也很明显它的学习曲线比 Backtrader 陡模块之间的抽象层次多新手前期会觉得有点绕。如果你只是想验证一个简单的均线突破策略Qlib 确实有点大材小用。但如果你要做的是“输入一堆特征让模型预测未来收益然后按预测值排序选股”这类 AI 量化研究Qlib 是目前开源社区里成熟度最高、最不用重复造轮子的一套。1.3 先记住这几个核心模块后面会反复用到我建议刚开始接触 Qlib 的时候不要急着写代码先把手感建立在对模块的认知上。下面这几个概念是 Qlib 的骨架后面所有操作都离不开它们。模块作用对应环节qlib.data底层数据访问负责读取价格、成交量、财务数据等原始行情数据层qlib.data.dataset数据集封装把特征、标签、时间切分统一管理数据处理Handler因子工程负责把原始行情加工成模型可用的特征和标签特征工程qlib.contrib.model模型库包含 LightGBM、GRU、LSTM 等可复用的模型实现模型训练qlib.contrib.strategy交易策略常见的有 TopkDropoutStrategy 等选股策略策略构建qlib.backtest回测引擎模拟交易并输出每日组合收益等结果回测执行qlib.contrib.evaluate绩效分析计算年化收益、夏普、最大回撤等指标绩效评估这几个模块不是彼此独立的而是层层嵌套的关系。数据流从 qlib.data 出发经过 Handler 加工成特征再由 Dataset 切分成训练和测试片段模型在这个数据集上完成训练然后把预测结果交给策略最后由回测引擎跑出绩效。你不需要一开始就搞懂每个模块的所有源码但最好在脑子里记住这个链路后面遇到问题都能定位到具体是哪一层出的问题。2. 环境搭建与数据准备这一步千万别想跳过2.1 安装 Qlib 和跑通第一个示例安装 Qlib 最直接的方式是 pip 安装包名是 pyqlib。我当时的做法是先建一个干净的虚拟环境避免和项目里的其他 Python 包冲突。python -m venv qlib_env source qlib_env/bin/activate pip install pyqlib这里有个小提醒Qlib 对 Python 版本有要求官方长期支持 3.8 以上的版本个别旧版本在 3.12 上可能会有依赖编译问题。如果你机器上默认版本比较新建议先确认一下当前 Python 版本不要在环境上卡太久。安装过程可能会比较慢因为 pyarrow、numpy、pandas 这些大户都在依赖列表里。装完之后可以用一行命令确认是否成功python -c import qlib; print(qlib.__version__)能正常输出版本号说明安装这关过了。如果这一步提示缺什么依赖就按提示补装不用慌基本都是常规操作。不过这里有一点需要注意pip 安装的 pyqlib 和 GitHub 仓库的源码版有时候会有细微差别。官方文档里很多示例默认你是在仓库根目录下执行的因为示例里会引用 examples/ 目录下的配置文件和 scripts/ 目录下的脚本。所以我的建议是如果你想跟着官方示例跑最好用 git clone 的方式拉一份源码到本地然后基于源码环境来操作。源码版和 pip 版在核心模块上是一致的但源码版更容易定位问题也更方便查看示例配置。2.2 下载数据与理解数据目录结构Qlib 官方提供了一键下载脚本可以下载 A 股和美股的历史数据。下载命令在源码仓库的 scripts/ 目录下。cd qlib python scripts/get_data.py qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn这个脚本会把日频的行情数据、交易日历、股票池信息全部下载到本地。数据量不算小我当时下载时大概等了一段时间具体时长取决于网络带宽。下载完成后目标目录下会出现几个关键子目录calendars交易日历文件记录了每一天是否交易日是回测时对齐时间的关键。instruments股票池列表定义了全市场有哪些股票、不同指数成分股有哪些。features核心行情数据按标的和日期维度存储包含 open、high、low、close、volume、factor 等字段。这三个目录尤其是 features 和 instruments后面会高频接触。比如你想取某只股票某段时间的收盘价Qlib 会从 features 目录里读取对应的数据文件你想知道沪深 300 有哪些成分股Qlib 会去 instruments 目录里查。数据下载好之后每次用 Qlib 前都要做初始化。最简单的初始化方式是指定数据路径和地区import qlib qlib.init(provider_uri~/.qlib/qlib_data/cn_data, regioncn)这一步等于告诉 Qlib“我的数据放在哪里”之后所有数据读取操作都会基于这个路径来执行。2.3 自备数据接入的补充方案不是所有人都想用官方数据很多人有自己的数据源例如从行情软件导出、或者买的数据商接口。Qlib 对自备数据也留了接口。官方提供了一套 dump 工具在源码的 scripts/dump_bin.py它可以把标准格式的 CSV 数据转换成 Qlib 的格式然后放进 features 目录。用起来不算复杂但需要注意字段命名要对得上 Qlib 的约定。Qlib 里的字段名一般带美元符号前缀比如 $open、$high、$low、$close、$volume、$factor 等你在组织 CSV 时就要按照这个命名来准备。另外instrument 文件也需要自己维护至少要把自备数据里的股票代码、上市日期、退市日期写清楚这样 Qlib 才知道哪些股票在哪些时间段是存在的。我建议第一次使用 Qlib 的时候先用官方数据把整个流程跑通再切换成自备数据。因为官方数据是经过验证的格式不会有问题。等你熟悉了目录结构和字段规范之后再动自己的数据源遇到问题也更容易判断是不是自己数据格式的问题。3. 从因子到回测完整跑通一条量化流程3.1 先分清 Data 和 Dataset避免概念混着用很多刚上手 Qlib 的人最先搞混的就是 Data 和 Dataset 这两个概念。Data 层是最底层的数据库访问入口它的主要作用是帮你取原始数据。比如你想取某只股票某段时间的成交量可以直接用 qlib.data.D 模块from qlib.data import D instruments D.instruments(marketcsi300) data D.features(instruments, [$close, $volume], start_time2020-01-01, end_time2021-12-31, freqday)这里 D.features 返回的是一张原始行情表每一行是某个股票在某天的行情数据。Data 层只负责“取数”不做任何加工。它就像仓库里的货架你要什么原材料它给你什么原材料。Dataset 层则是面向模型的一层封装。它做的事情是从 Data 层取到原始行情用 Handler 加工成特征和标签再按时间切成训练集、验证集、测试集。给模型的不是原始行情而是加工好的特征矩阵。你可以把 Data 理解为仓库Dataset 是后厨准备好的餐盒。仓库里堆满了米面粮油但你不能直接拿生米给客人吃Dataset 是经过清洗、切配、分装好的半成品到了模型那里就能直接“下锅”。3.2 用 Alpha158 快速生成因子集Qlib 内置了两套经典因子集Alpha158 和 Alpha360。Alpha158 是入门首选它包含 158 个因子涵盖价格、成交量、收益率、波动率等多个维度基于日频数据计算特征量适中训练起来也快。Alpha360 则基于更长的历史窗口生成 360 个特征信息更丰富适合对模型表达能力要求更高的场景。用 Alpha158 生成因子非常省事框架已经帮你写好了所有因子逻辑。示例代码如下from qlib.contrib.data.handler import Alpha158 from qlib.data.dataset import DatasetH handler Alpha158( instrumentcsi300, start_time2015-01-01, end_time2022-12-31, freqday, ) dataset DatasetH( handler, segments{ train: (2015-01-01, 2020-12-31), valid: (2021-01-01, 2021-12-31), test: (2022-01-01, 2022-12-31), }, )这段代码做完了几件事取沪深 300 成分股的行情数据、计算 158 个因子、生成未来 N 日的收益标签、按时间切分数据集。你不用写一行因子计算逻辑就能拿到一个模型可以直接训练的数据集。这里值得一提的细节是Alpha158 默认的标签是“未来 N 个交易日收益”的某种变换这是监督学习的目标。Qlib 在训练阶段会把当前时点的特征和未来收益对应起来模型学到的是“当前特征对未来收益的预测能力”。3.3 训练 LightGBM 模型的实操记录因子生成完之后就可以训练模型了。Qlib 官方在 examples/benchmarks 下准备了很多现成模板其中 LightGBM 的示例最适合新手跑通流程。最简单的方式是用 qrun 命令直接执行官方配置qrun examples/benchmarks/LightGBM/workflow_config_lightgbm_Alpha158.yamlqrun 是 Qlib 自带的配置驱动执行器它会读取 yaml 文件里的完整配置包括数据范围、因子集、模型参数、回测设置然后从头到尾跑一遍研究流程。对于刚开始接触 Qlib 的人我强烈建议先跑一遍这个命令哪怕不细看里面的参数也能对整体流程有个直观感受。如果你更倾向于用代码控制训练过程也可以直接用 Qlib 的模型接口。LightGBM 模型在 qlib.contrib.model.gbdt 里示例代码如下from qlib.contrib.model.gbdt import LGBModel model LGBModel( lossmse, colsample_bytree0.8, learning_rate0.05, subsample0.8, lambda_l1205, lambda_l2580, max_depth8, num_leaves210, num_threads20, early_stopping_rounds50, ) model.fit(dataset)这些参数不是随手写的它们来自 Qlib 官方 LightGBM benchmark 的调优结果。lambda_l1 和 lambda_l2 都设得比较大说明官方在实验中对正则化比较重视目的就是防止模型在因子过多时过拟合。我自己跑的时候发现把这两项降下来训练集分数会明显上涨但测试集分数反而会掉这个现象很有代表性。模型训练完成之后可以用 predict 方法生成预测值后面回测会用到pred model.predict(dataset)pred 里保存的是模型对每个股票在每个时点的预测收益。打分越高代表模型越看好这只股票未来的表现。接下来的选股逻辑就是基于这个分数来构建组合。3.4 回测环节的设定与结果评估有模型预测之后下一步就是回测。Qlib 本身带了一套回测引擎不需要你自己写撮合逻辑。官方示例里最常用的是 TopkDropoutStrategy它的逻辑比较符合实际每天持有分数最高的 topk 只股票定期把表现不够好的 n_drop 只股票淘汰再按分数补入新的股票。from qlib.contrib.strategy import TopkDropoutStrategy from qlib.backtest import backtest from qlib.backtest.executor import SimulatorExecutor from qlib.contrib.evaluate import risk_analysis strategy TopkDropoutStrategy( topk20, n_drop5, hold_thresh1, only_tradableTrue, ) portfolio_metric_dict, indicator_dict backtest( start_time2022-01-01, end_time2022-12-31, strategystrategy, executorSimulatorExecutor(time_per_stepday), ) analysis risk_analysis(portfolio_metric_dict[1day])回测跑完之后analysis 里会包含一堆绩效指标我最常用的是下面这几个指标含义参考值annualized_return年化收益率越高越好但需要结合风险看information_ratio信息比率衡量单位超额风险的收益通常希望 0.5max_drawdown最大回撤越低越好代表组合承受的最大损失annualized_volatility年化波动率波动越大持有体验越差这里多说一句回测结果好不代表实盘一定好。TopkDropoutStrategy 的策略逻辑里有一个 hold_thresh 参数控制的是调仓频率。官方默认是每隔几天调一次仓如果你改成每天调仓手续费和滑点的影响会明显放大回测出来的收益曲线看着很漂亮但扣掉成本可能就完全变味了。4. 踩坑记录与效率优化这些经验文档里没写4.1 高频报错对照与解决这大半年用下来我遇到过不少报错有些报错信息看着吓人其实原因很简单。整理几个高频问题方便你对症下药。报错现象可能原因解决办法ModuleNotFoundError: No module named sklearn缺少机器学习相关依赖补装 scikit-learn部分模型需要它FileNotFoundError: calendar.txt 不存在数据没下载完整或者 provider_uri 路径不对检查 qlib.init 的路径重新下载数据ValueError: data frequency mismatch数据频率和 handler 里设置的不一致确认数据是日频还是分钟频保持 config 一致MemoryError 或训练时卡顿数据集太大股票池和时间跨度过长缩小股票池、缩短时间窗口、减少因子数量KeyError: feature 之类字段缺失自备数据字段命名不符合 Qlib 规范检查字段名带上 $ 前缀并确认字段存在如果你遇到的是 Unknown instrument 这类问题大概率是股票池里有股票在你的时间范围内没有数据。这种情况可以检查 instruments 文件或者换一个更常见的时间范围试试。还有一次我卡了很久原因是我下载数据时只下了一部分结果回测时老是提示某一天的交易日历缺失。后来我把数据重新下载完整问题就消失了。所以我强烈建议下载数据这一步不要省下载完最好检查一下目标目录的大小和文件数量确认数据完整再继续。4.2 因子计算中的三个隐蔽陷阱Qlib 帮我们解决了很多工程问题但因子研究里有些方法论层面的坑框架本身是替不了你避开的。第一个是未来函数。自定义因子时如果你不小心在计算因子的时候用到了未来信息比如用当天的收盘价去预测当天的收益回测结果会好得离谱但实盘完全不可能复现。Qlib 的 label 是未来收益这是合理的因为我们要预测的是未来但因子本身的特征必须基于历史数据。最简单的方法是把因子的计算窗口往历史方向偏移确保计算时只用 T 日及之前的信息。第二个是幸存者偏差。如果你只用了今天还在上市的股票来跑历史和回测那些中途退市的股票根本不会出现在你的股票池里回测表现会被系统性高估。Qlib 的 instruments 里提供了全市场股票列表建议尽量使用全市场股票池而不是只选几只明星股票来测。第三个是调仓时点的影响。回测里的交易假设和实际执行的时点越接近结果越可信。如果你选的是日频数据建议回测时设置好截止时间确保最后一天不会因为未来数据不足而出现标签缺失。还有 only_tradable 这个参数它会在回测时过滤掉停牌、涨跌停不能交易的股票开启之后回测结果虽然会略保守但更接近实盘。4.3 资源占用与训练效率的优化方向Qlib 项目跑多了之后大家都会面临同一个问题数据和因子量越来越大训练一次的时间成本和内存占用都很可观。我调优的经验大致有三条方向。方向一是控制数据规模。如果你的实验只需要验证某个因子逻辑不需要全市场几千只股票都跑可以先只跑一部分代表股票或者一个指数成分股比如 csi300先快速验证想法再决定是否全市场展开。时间窗口也同理不要一上来就拉十年数据先用三五年跑通再补全长周期验证。方向二是利用好模型自身的学习参数。LightGBM 里有一个 num_threads 参数可以控制训练时使用的 CPU 核数early_stopping_rounds 可以在验证集分数不再提升时提前终止训练省掉无效轮次。我在实际使用中会先把这些参数固定下来再去做因子或数据的实验保证对比实验的公平性。方向三是做好中间结果的缓存。因子计算是重复劳动同一套数据集和因子配置每次跑都重新算一遍很浪费。Qlib 的 handler 和 dataset 在多次运行时的中间结果可以保存到本地第二次跑就能直接读取缓存。还有模型训练完之后的预测结果也可以保存下来这样你后续调整回测参数时就不需要重新训练模型直接加载历史预测结果来做回测实验效率能提升一截。最后说一下我自己在实际操作中的体会。Qlib 这套框架刚上手确实有点东西要学但一旦你把它的设计语言弄清楚后面所有项目都能复用同一条工业化管道。我个人的习惯是先用官方数据把标准流程完整跑通再慢慢换成自己的数据最后才去改策略和模型。每一步都有参照系出了问题也能很快定位是框架自身的问题还是自己改出来的问题。这篇笔记先记到这里后面如果我在 Qlib 上继续深入比如加入更多模型、自定义因子、或者接入更多数据源我再继续更新这个系列。随缘更新但每次更新都会尽量把踩过的坑和实测结论写清楚。
返回列表