Qlib 量化投资平台实战:从环境搭建到因子选股回测全流程
简介:这份PDF资料是华泰证券金工研究团队2020年12月发布的深度研究报告,聚焦微软AI量化投资平台Qlib的实操体验,面向量化投资研究者、金融从业者及希望降低AI算法使用门槛的学习者。报告以港股日频量价因子LightGBM选股策略为案例,分Qlib安装、数据准备、因子生成、策略构建、结果展示等步骤讲解基础功能,并进一步介绍自定义因子、标签、数据预处理方式与AI模型等进阶组件,同时对比传统量化开发流程,提炼Qlib在数据存储方案、表达式引擎和缓存系统上的工程创新。资源包共1个PDF文件,约4.71MB,内容完整、结构清晰,便于按章节检索学习。目前已有1173人学习下载,适合希望快速上手Qlib、理解其优势与局限并借鉴案例设计新应用场景的读者参考。
1. 从一份研报说起:Qlib 到底能不能直接拿来跑策略
2020 年底,一份金工深度研报在圈子里传开,主题是体验一个刚开源不久的 AI 量化投资平台。当时很多人第一反应是:又一个“覆盖全流程”的大饼。我最初也是这个态度,直到自己把它的安装、数据转换、因子生成、LightGBM 选股、回测这条链路完整走了一遍,才意识到它的价值不在“道”上,而在“术”上——它没有颠覆传统因子选股方法论,但把因子数据存储、表达式计算、缓存这些工程环节重做了一遍,让一个熟手从零到跑出第一条净值曲线的时间,从几天压到半天。这篇笔记就按我实际复现的顺序拆:它是什么、怎么装、数据怎么灌、策略怎么搭、坑在哪。适合手里有 csv 行情数据、想快速验证量价因子想法的人,不适合指望开箱即得 alpha 的人。
2. 装 Qlib 之前先把环境这关过了:C++ 生成工具与 setup.py 两条路
安装是绝大多数人翻车的第一站,而且报错信息往往指向一个和 Python 无关的东西。这一章把安装拆成“依赖准备”和“两种安装方式的选择”两块,顺带说清为什么我更推荐源码方式而不是 pip。
2.1 为什么 pip install pyqlib 经常不够用
官方给了两条安装路径:pip install pyqlib装最新稳定版,或者下载源码跑setup.py install装包含开发版在内的最新代码。pip 那条路看起来最省事,但它有两个隐性前提:一是你的机器上得有 Microsoft Visual C++ 14.0 的编译环境,二是 pip 包里的tests目录往往不全,后面跑官方数据下载脚本时会直接报ModuleNotFoundError: No module named 'qlib.tests'。
我当时的实际选择是源码方式,原因很朴素:办公网络拉 git 不稳,干脆下 zip 包解压到 site-packages 下再装。这条路的好处是源码、脚本、tests 全都在手边,后面改组件、看实现都方便。代价是要手动处理编译依赖。
先装编译工具。注意这里有个高频误解:只装 Microsoft Visual C++ Redistributable 运行组件是没用的,报错依旧。必须装完整的生成工具(Build Tools),安装时勾选“使用 C++ 的桌面开发”那一组工作负载,否则编译 cython 扩展时会继续报Microsoft Visual C++ 14.0 is required。
# 先确保 numpy 和 cython 是最新的,否则编译扩展容易失败 pip install numpy pip install --upgrade cython # 方式一:有 git 且网络通 git clone https://github.com/microsoft/qlib.git && cd qlib python setup.py install # 方式二:下 zip 源码包,解压到任意路径(示例放在 site-packages 下) # 确保 setup.py 就在该目录下,然后在该目录启动命令行 cd C:/Users/username/anaconda3/Lib/site-packages/qlib python setup.py install上面两段命令的逻辑差异要说清楚:git clone那条会拿到完整仓库历史,适合想跟进上游改动的人;zip 解压那条拿的是某个时间点的快照,适合网络受限或只想稳定复现的人。setup.py install会顺带把开发版依赖一起装上,装完的版本号可能带dev后缀(我装到的是 0.6.1.dev0 这一类),这不是问题,反而说明你拿到的是比 pip 稳定版更新的代码。
参数层面没什么可调的,真正要盯的是安装日志末尾有没有编译错误。如果看到 cython 编译阶段报缺失头文件,八成还是生成工具没装全,回去补勾工作负载,别在 Python 层面反复折腾。
2.2 装完先验证,别急着灌数据
装完不要直接冲数据准备,先花两分钟确认包能正常导入、脚本目录存在。常见做法是在 Python 里import qlib看有没有报错,再去安装路径下确认scripts/和qlib/tests/两个目录都在。前者是后面get_data.py、dump_bin.py的所在地,后者是 pip 安装方式最容易缺的东西。这一步省下来,后面会以更难排查的形式还给你。
3. 数据准备:官方数据下载与用户 csv 转 bin 的完整链路
Qlib 不吃原始 csv,它有自己的二进制存储格式。这一章分两条线:一条是拿官方 A 股/美股数据快速跑通,另一条是把你手里的港股 csv 灌进去。后者才是真实工作场景,也是坑最密集的地方。
3.1 get_data 下载官方数据,以及那个 tests 报错
官方数据下载命令要在 Qlib 安装路径下执行,因为它依赖同目录的scripts/get_data.py:
# 在 Qlib 安装路径下启动命令行 python scripts/get_data.py qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn参数含义:qlib_data是数据集的标识,--target_dir指定落地目录,--region cn表示 A 股(换成 us 就是美股)。下载完成后数据落在~/.qlib/qlib_data/cn_data,里面是 calendar、features、instruments 三块结构。
如果你是用 pip 装的,这条命令大概率报ModuleNotFoundError: No module named 'qlib.tests'。原因不复杂:pip 包裁剪了测试目录,而下载脚本内部引用了它。解决办法是把源码 zip 里的tests目录拷到安装路径下,或者干脆改用源码安装。这个坑我踩过一次,后来一律源码安装,省心。
3.2 dump_all 把港股 csv 转成 bin:字段和参数逐项说
真实场景里你手里是自己的行情数据。研报里用的是港股日频数据,组织方式是每只股票一个 csv,文件名就是股票代码,统一放在hk_data文件夹下,时间跨度 2008 年初到 2020 年 11 月末,含权证和已退市股票共 3739 只。每个 csv 内部至少要有这些字段:
| 字段 | 说明 | 字段 | 说明 |
|---|---|---|---|
| stock_code | 股票代码,与文件名一致 | low | 复权最低价 |
| date | 日频日期 | close | 复权收盘价 |
| volume | 成交量 | factor | 复权因子 |
| money | 成交额 | vwap | 复权均价 |
| open | 复权开盘价 | change | 相对前一交易日涨跌幅 |
| high | 复权最高价 |
这里有两个必须点名的坑。第一,价格要用复权价。原因在源码里:后续数据标注(qlib.contrib.data.handler)用 close 或 vwap 算未来收益率,回测(qlib.contrib.evaluate.backtest)用 close、open 或 vwap 成交,你喂不复权价,收益率和成交价全是错的。第二,数据里必须包含factor或change字段,缺了它,跑官方全流程范例examples/workflow_by_code.py时策略收益和净值会出现异常——不是报错,是数值不对,这种静默错误最难查。
除个股外还要准备基准指数,把恒生指数按同样格式存成hkhsi.csv放在同一路径。然后执行转换:
python scripts/dump_bin.py dump_all \ --csv_path ~/.qlib/csv_data/hk_data \ --qlib_dir ~/.qlib/qlib_data/hk_data \ --symbol_field_name stock_code \ --date_field_name date \ --include_fields open,high,low,close,volume,money,factor,vwap,change参数逐个解释:--csv_path是原始 csv 目录,--qlib_dir是转换后 bin 的落地目录,--symbol_field_name告诉脚本哪一列是股票代码(这里是stock_code),--date_field_name指定日期列,--include_fields列出要保留的其余字段。最后一个参数有个致命细节:逗号后面不能有空格,写成open, high, low会直接导致转换出错。这个空格问题坑过不少人,因为肉眼几乎看不出来。
转换成功后,--qlib_dir下会出现 calendar、features、instruments 三块,features 里是按股票代码分目录的 bin 文件。到这一步,数据层就算通了。
4. 从因子到净值:Alpha158 加 LightGBM 的选股策略怎么搭
数据灌进去只是开始,真正体现 Qlib 设计思路的是它把“因子生成—模型训练—回测”串成一条 workflow。这一章按初始化、股票池、因子、模型、回测的顺序走,每步给出可抄的代码和参数解释。
4.1 初始化运行环境与自定义股票池
一切从初始化开始,告诉 Qlib 数据在哪、用哪个市场:
import qlib from qlib.constant import REG_HK # 指向刚才 dump 出来的港股数据目录 qlib.init(provider_uri="~/.qlib/qlib_data/hk_data", region=REG_HK)provider_uri必须和dump_bin.py的--qlib_dir一致,region决定日历和交易规则。初始化之后可以取交易日历和全部股票代码,用来确认数据读得对不对:
from qlib.data import D # 取交易日历和股票列表,验证数据完整性 cal = D.calendar(start_time="2008-01-01", end_time="2020-11-30") instruments = D.instruments(market="all") codes = D.list_instruments(instruments, start_time="2008-01-01", end_time="2020-11-30")股票池这块,官方默认给的是全市场,实际研究里通常要自己筛。常见做法是写一个 instruments 文件,把可交易标的列进去,再在配置里引用。研报里强调自定义股票池,本质就是控制 universe,避免把低价股、低流动性股票混进来污染回测——这一点在风险提示里也被专门点出,案例本身没做这层过滤,所以只能当教学看。
4.2 Alpha158 因子库与标签生成
Qlib 内置 Alpha158 和 Alpha360 两套因子库。Alpha158 是 158 个量价类特征,标签默认是下期收益。生成特征和标签的核心是 handler 配置:
from qlib.contrib.data.handler import Alpha158 # 用 Alpha158 生成当期因子,标签为下期收益 handler = Alpha158( instruments="all", start_time="2008-01-01", end_time="2020-11-30", fit_start_time="2008-01-01", fit_end_time="2017-12-31", # 训练段,预处理参数只在这段上拟合 )这里的关键参数是fit_start_time/fit_end_time:数据预处理(比如标准化)的统计量只在这段上算,避免用到未来信息。这是很多人手写流程时容易忽略的地方,Qlib 用参数把它显式化了。取因子和标签时分别调handler.fetch拿特征、拿 label,逻辑上就是“当期因子对下期收益”,训练集测试集按时间切,别打乱。
4.3 LightGBM 模型训练与 task 参数组织
Qlib 把训练任务组织成一个task字典,里面分model和dataset两块。模型部分指定 LightGBM 及其超参,数据集部分指定 handler 和切分方式:
from qlib.contrib.model.gbdt import LGBModel from qlib.contrib.data.dataset import DatasetH # 模型参数:LightGBM 的核心超参 model = LGBModel( loss="mse", learning_rate=0.05, num_leaves=64, max_depth=8, num_threads=8, ) # 数据集参数:引用上面的 handler,按时间切训练/验证/测试 dataset = DatasetH( handler=handler, segments={ "train": ("2008-01-01", "2017-12-31"), "valid": ("2018-01-01", "2018-12-31"), "test": ("2019-01-01", "2020-11-30"), }, )learning_rate和num_leaves是最影响过拟合的两个旋钮,量价因子信噪比低,叶子数别开太大。segments的时间切分必须严格按时间先后,训练段在前、测试段在后,任何随机切分都会让回测结果虚高。训练就是model.fit(dataset),预测用model.predict(dataset),拿到的是每只股票每期的打分。
4.4 回测配置与绩效结果读取
回测配置port_analysis_config决定怎么用预测分数建仓、怎么算绩效:
from qlib.contrib.evaluate import backtest_daily from qlib.contrib.strategy import TopkDropoutStrategy # 策略:每期取打分最高的 topk,掉出 topk 的卖出 strategy = TopkDropoutStrategy(topk=50, n_drop=5, signal=pred_score) # 回测:指定基准、交易成本、调仓频率 report, positions = backtest_daily( start_time="2019-01-01", end_time="2020-11-30", strategy=strategy, benchmark="hkhsi", account=100000000, exchange_kwargs={"freq": "day", "limit_threshold": 0.095, "deal_price": "close"}, )topk=50是持仓数量,n_drop=5是每期最多换掉的仓位数,benchmark填你准备的指数代码,limit_threshold是涨跌停限制(港股没有涨跌停,这里按案例设了个宽松值),deal_price决定按收盘还是开盘成交。跑完拿report里的 IC、Rank IC、净值、超额收益、最大回撤、换手率。研报里展示的就是这几张图,IC 和 Rank IC 看因子有效性,净值曲线和最大回撤看策略稳健性,换手率看成本压力。
5. 自定义组件避坑:改参数、改源码与四类高频翻车
Qlib 的组件都通过 workflow 串联,每个组件有对应参数,也有对应源码。最简单的自定义是改参数,更灵活的是仿照源码写继承类。这一章先讲自定义的层次,再集中列踩坑记录。
5.1 自定义因子、标签、预处理与模型的四个入口
自定义特征:在 handler 里加表达式,或者继承 Alpha158 重写get_feature_config。自定义标签:改 label 表达式,比如把下期收益换成下期收益的排序分位。自定义预处理:Qlib 内置了一批 processor(在qlib.data.dataset.processor下),Alpha158 和 Alpha360 各有默认预处理链,想换就在 handler 配置里替换 processor 列表。自定义模型:内置模型在qlib.contrib.model下,想用 SVR 之类没内置的,就仿照现有 Model 子类写一个,实现fit和predict两个方法即可。
四个入口的共同点是:先看源码路径,再决定改参数还是继承。研报里给了一张“各组件参数及对应源码路径”的对照表,这个习惯值得学——改之前先定位源码,比盲调参数高效得多。
5.2 常见问题与排查记录
现象一:安装时报Microsoft Visual C++ 14.0 is required。原因:只装了运行组件,没装生成工具。解决:装完整 Build Tools,勾选 C++ 桌面开发工作负载,重装。
现象二:get_data.py报No module named 'qlib.tests'。原因:pip 包裁剪了 tests 目录。解决:下源码 zip,把 tests 拷进安装路径,或直接源码安装。
现象三:dump_all 转换报错或字段缺失。原因:--include_fields逗号后带了空格。解决:去掉所有空格,重跑转换,检查 features 目录下 bin 文件是否齐全。
现象四:策略净值和收益异常,但不报错。原因:csv 缺factor或change字段,或用了不复权价。解决:补齐字段、统一复权口径,重灌数据再回测。
现象五:回测 IC 高得离谱。原因:训练测试切分打乱,或预处理统计量用了全样本。解决:严格按时间切 segments,fit_end_time卡在训练段末尾。
6. 进阶验证:用 IC 衰减和换手率给策略做体检
跑出净值不等于策略能用。我后来养成一个习惯:任何一条新 workflow 跑通后,先不看净值,先看 IC 的时间序列和衰减。具体做法是把测试段按季度切开,逐段算 IC 和 Rank IC,看因子有效性是稳定还是集中在某几段。如果 IC 只在某一两个季度高、其余接近零,那大概率是过拟合到某段行情,净值曲线再漂亮也不能信。
第二个体检指标是换手率。TopkDropoutStrategy的n_drop直接控制换手,n_drop调大换手上升、成本吃掉收益;调小则持仓粘性高、可能错过调仓。我的经验是把换手率和交易成本一起看:在exchange_kwargs里把成本设成偏保守的值,如果策略在这个成本下净值还站得住,才值得进一步调参。研报的风险提示里专门说了案例未剔除低价股、低流动性股票,训练测试段也短,所以那些净值只能当流程演示,不能当业绩参考。
还有一个容易被忽略的验证点:把同一套因子换一个模型(比如从 LightGBM 换成线性模型)再跑一遍。如果 IC 量级差不多,说明信号主要来自因子本身;如果差距巨大,说明模型在拟合噪声。这个对照实验花不了多少时间,但能帮你判断到底是因子有用还是模型在“背答案”。
从那以后我每次接一个新数据源,都强制先跑一遍“字段完整性 + 复权口径 + 时间切分”三件套检查,再谈策略。这三步任何一步偷懒,后面调参调得再细都是白费。希望这套流程能帮你少走几个我踩过的坑。
本文还有配套的精品资源,点击获取