如果你跟我一样,手里已经攒了不少因子研究的代码,也试过自己拼一套回测框架,那么在第一次见到 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", region="cn")这一步等于告诉 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(market="csi300") data = D.features(instruments, ["$close", "$volume"], start_time="2020-01-01", end_time="2021-12-31", freq="day")这里 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( instrument="csi300", start_time="2015-01-01", end_time="2022-12-31", freq="day", ) 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( loss="mse", colsample_bytree=0.8, learning_rate=0.05, subsample=0.8, lambda_l1=205, lambda_l2=580, max_depth=8, num_leaves=210, num_threads=20, early_stopping_rounds=50, ) 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( topk=20, n_drop=5, hold_thresh=1, only_tradable=True, ) portfolio_metric_dict, indicator_dict = backtest( start_time="2022-01-01", end_time="2022-12-31", strategy=strategy, executor=SimulatorExecutor(time_per_step="day"), ) analysis = risk_analysis(portfolio_metric_dict["1day"])回测跑完之后,analysis 里会包含一堆绩效指标,我最常用的是下面这几个:
| 指标 | 含义 | 参考值 |
|---|---|---|
| annualized_return | 年化收益率 | 越高越好,但需要结合风险看 |
| information_ratio | 信息比率,衡量单位超额风险的收益 | 通常希望 > 0.5 |
| max_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 上继续深入,比如加入更多模型、自定义因子、或者接入更多数据源,我再继续更新这个系列。随缘更新,但每次更新都会尽量把踩过的坑和实测结论写清楚。