简介:这份PDF资料是华泰证券金工研究团队2020年12月发布的深度研究报告,聚焦微软AI量化投资平台Qlib的体验与评测,面向量化投资研究者、金融工程从业者及希望降低AI选股门槛的开发者。报告以港股日频量价因子LightGBM选股策略为案例,完整梳理Qlib安装、数据准备、因子生成、策略构建与结果展示的基础流程,并进一步讲解自定义因子、标签、数据预处理方式与AI模型等进阶组件,同时对比传统量化开发流程,提炼Qlib在数据存储方案、表达式引擎和缓存系统上的工程创新及性能优势。资源包共1个PDF文件,约4.71MB,内容结构清晰,涵盖研究导读、快速上手、进阶功能与风险提示等模块。目前已有1173人学习,适合希望系统了解Qlib框架、借鉴券商研究视角并动手复现案例的读者参考。
1. 从一份港股数据开始:Qlib 到底能替你省掉哪几步
手里有一批港股日频行情 csv,想跑一个量价因子选股的 AI 策略,传统做法是 pandas 读数据、手写因子、sklearn 训模型、再自己搭回测,中间还要处理复权、对齐交易日历、算 IC——每一步都能单独翻车。微软亚洲研究院开源的 Qlib 就是冲着这条链路来的:它把数据存储、因子表达式、模型训练、回测评估串成一套工作流,官方定位是覆盖量化投资全过程的 AI 量化开源平台。这份资源适合已经会写 Python、想快速验证量价选股想法的人,也适合想看清一个工业级量化框架内部怎么组织数据与组件的工程师。下面按安装、数据、因子、策略、回测、自定义组件的顺序拆,坑在哪一并说清。
2. 装 Qlib 与备数据:两个最容易卡住的前置环节
Qlib 的安装本身不难,难在依赖和网络环境。官方给了 pip 和 setup.py 两条路,我实测下来更推荐后者,原因不是 pip 不能用,而是 pip 装出来的稳定版有时和你手头源码包的目录结构对不上,后面跑脚本会莫名其妙找不到模块。这一章把安装、官方数据下载、用户 csv 转换三件事按顺序落地。
2.1 先解决 C++ 生成工具这个硬依赖
Qlib 的部分扩展模块在安装时要本地编译,Windows 上缺 Microsoft Visual C++ 14.0 会直接报错:
error: Microsoft Visual C++ 14.0 is required. Get it with "Build Tools for Visual Studio"注意,只装运行库(Redistributable)解决不了这个问题,必须装完整的 C++ 生成工具。装的时候在安装器里勾选「使用 C++ 的桌面开发」这一组工作负载,别只勾一个空壳。Mac 和 Linux 一般自带编译链,跳过这步即可。
依赖装好后,走 setup.py 方式:
# 先保证 numpy 和 cython 是最新的 pip install numpy pip install --upgrade cython # 有 git 的情况 git clone https://github.com/microsoft/qlib.git && cd qlib python setup.py install # 没有 git:下载 zip 源码包解压到任意路径,确保 setup.py 在该目录下 cd /path/to/qlib python setup.py installsetup.py install装的是包含开发版在内的最新代码,版本号会带 dev 后缀,这对想跟进新功能的人是好事,但也意味着稳定性不如正式版,生产环境要自己评估。装完在 Python 里import qlib不报错,第一步就算过了。
2.2 官方数据下载与 csv 转换的字段要求
官方提供 A 股和美股数据用于学习。在 Qlib 安装路径下执行:
python scripts/get_data.py qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn--target_dir是数据落地目录,--region cn指定 A 股。如果用的是 pip 安装,这条命令可能报ModuleNotFoundError: No module named 'qlib.tests',原因是 pip 包没带 tests 目录。解决办法是从源码包里把tests文件夹拷到安装路径下,这也是我前面推荐 setup.py 的原因之一。
用自己的数据时,Qlib 要求每只股票一个 csv,文件名即股票代码,所有文件放同一目录。字段至少包含下表这些:
| 字段 | 含义 | 备注 |
|---|---|---|
| stock_code | 股票代码 | 与文件名一致,列名不唯一 |
| date | 日频日期 | 列名不唯一 |
| open / high / low / close | 复权价 | 建议后复权 |
| vwap | 复权均价 | 回测会用到 |
| volume / money | 成交量 / 成交额 | 量价因子基础 |
| factor | 复权因子 | 强烈建议保留 |
| change | 相对前一日涨跌幅 | 强烈建议保留 |
两个血泪坑:一是价格务必用复权价,因为后续标签计算用 close 或 vwap 算未来收益,回测也用这几个字段成交,不复权会让收益和净值失真;二是factor或change字段至少要有其一,缺了跑官方全流程范例时策略收益会出现异常,而且不报错,属于典型的黑匣子问题。
转换命令如下:
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和--date_field_name对应你 csv 里的代码列和日期列名,--include_fields列出其余字段。这里有个细节:逗号后不能有空格,写成open, high会直接转换失败。转换完成后目录下会出现 calendar、features、instruments 三块,分别存交易日历、因子数据和股票池,后面所有读取都基于这个结构。
3. 因子生成与 LightGBM 选股:把工作流串起来
数据就位后,核心是把「读数据 → 算因子 → 训模型 → 回测」这条 workflow 跑通。Qlib 的设计是把每个环节做成可配置组件,用一份配置字典驱动,理解这份配置的结构比背 API 更重要。
3.1 初始化环境与 Alpha158 因子库
先初始化 Qlib 运行环境并指向自己的数据目录:
import qlib from qlib.constant import REG_HK # provider_uri 指向 dump 后的数据目录,region 决定交易日历和交易规则 qlib.init(provider_uri="~/.qlib/qlib_data/hk_data", region=REG_HK)provider_uri必须指向含 calendar/features/instruments 的目录,region影响涨跌停、交易成本等默认规则,港股用REG_HK。初始化后可以用D.calendar()取交易日、D.instruments()取股票池,再用D.features()按字段取数,这一步是验证数据转换是否成功的最快方式。
因子不用手写,Qlib 内置 Alpha158 因子库,用表达式引擎描述量价因子,比如Ref($close, 1)/$close - 1就是一日反转。生成特征和标签:
from qlib.contrib.data.handler import Alpha158 # 标签用下期收益,特征用当期量价因子 handler = Alpha158( instruments="csi300", # 换成自己的股票池名称 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是预处理(如标准化)的拟合区间,必须早于测试区间,否则就是未来函数。这是很多人第一次跑通却指标虚高的根源。
3.2 模型训练参数 task 的拆解
Qlib 用一个大字典task描述训练任务,关键在model和dataset两块:
from qlib.contrib.model.gbdt import LGBModel from qlib.contrib.data.dataset import DatasetH from qlib.contrib.data.handler import Alpha158 data_handler_config = { "start_time": "2008-01-01", "end_time": "2020-11-30", "fit_start_time": "2008-01-01", "fit_end_time": "2017-12-31", "instruments": "csi300", } task = { "model": { "class": "LGBModel", "module_path": "qlib.contrib.model.gbdt", "kwargs": {"loss": "mse", "learning_rate": 0.05, "num_leaves": 64}, }, "dataset": { "class": "DatasetH", "module_path": "qlib.contrib.data.dataset", "kwargs": { "handler": {"class": "Alpha158", "module_path": "qlib.contrib.data.handler", "kwargs": data_handler_config}, "segments": {"train": ("2008-01-01", "2017-12-31"), "valid": ("2018-01-01", "2018-12-31"), "test": ("2019-01-01", "2020-11-30")}, }, }, }segments把时间切成训练、验证、测试三段,测试段绝不能参与拟合。learning_rate和num_leaves是 LightGBM 的核心调参项,学习率小、叶子多容易过拟合,先用默认量级跑通再调。训练只需一行:
from qlib.model.trainer import task_train model = task_train(task, experiment_name="hk_lgb_demo")experiment_name是实验名,Qlib 会把模型、预测结果、配置都归档,方便复现和对比,这个机制比手动存 pkl 靠谱得多。
3.3 回测配置与绩效读取
训练完拿到预测分数,回测配置决定怎么把分数变成持仓:
port_analysis_config = { "strategy": { "class": "TopkDropoutStrategy", "module_path": "qlib.contrib.strategy.signal_strategy", "kwargs": {"signal": model.predict, "topk": 50, "n_drop": 5}, }, "backtest": { "start_time": "2019-01-01", "end_time": "2020-11-30", "account": 100000000, "benchmark": "SH000300", # 换成对应港股基准 "exchange_kwargs": {"freq": "day", "limit_threshold": 0.095, "deal_price": "close"}, }, }topk是持仓数,n_drop是每期换出的股票数,limit_threshold控制涨跌停不可成交的阈值,deal_price决定按收盘还是均价成交。回测跑完后用risk_analysis读 IC、Rank IC、净值、最大回撤和换手率。这里要清醒:官方和研报里的港股案例仅作教学,没剔除低价股和低流动性股票,训练测试区间也短,指标好看不代表策略可用。
4. 自定义组件:改参数、改源码还是写子类
跑通默认流程只是入门,真正干活时因子、标签、预处理、模型几乎都要换。Qlib 每个组件都有对应参数和源码,自定义有三档:改参数最轻,改源码最直接,继承写子类最干净。
4.1 自定义特征与标签
特征可以在 handler 里追加表达式,标签则通过label配置改:
from qlib.contrib.data.handler import Alpha158 class MyHandler(Alpha158): def get_label_config(self): # 改成未来 5 日收益,字段名自定义 return ["Ref($close, -5) / $close - 1"], ["LABEL5"] def get_feature_config(self): fields, names = super().get_feature_config() # 追加一个量价背离因子 fields.append("$volume / Mean($volume, 20)") names.append("VOL_RATIO20") return fields, namesRef($close, -5)里的负号表示向未来取数,只在标签里用,特征里用就是未来函数。追加因子时注意表达式引擎的语法,Mean、Ref、Std这些算子都是内置的,写错不会报语法错,而是返回空值,排查时先看因子覆盖率。
4.2 换预处理与换模型
预处理在qlib.data.dataset.processor下,常见的有DropnaLabel、CSZScoreNorm、RobustZScoreNorm。Alpha158 默认带一套预处理,想换就在 dataset 的 kwargs 里显式指定 processor 列表,顺序会影响结果,标准化要放在去极值之后。
换模型最省事的是改task["model"]的class和module_path,Qlib 内置了 LightGBM、XGBoost、线性模型、部分深度模型。内置没有的,仿照现有模型写子类:
from qlib.model.base import Model from sklearn.svm import SVR class SVRModel(Model): def __init__(self, **kwargs): self.model = SVR(**kwargs) def fit(self, dataset): x_train, y_train = dataset.prepare("train", col_set=["feature", "label"], data_key="learn") self.model.fit(x_train.values, y_train.values.ravel()) def predict(self, dataset): x_test, _ = dataset.prepare("test", col_set=["feature", "label"], data_key="learn") return x_test关键是fit和predict的入参出参要和框架约定一致,prepare的data_key决定取哪段数据。写子类的好处是原组件不动,出问题好回退。
5. 避坑与排查:五个真实会卡住你的地方
5.1 安装报 C++ 14.0 缺失
现象:pip install或setup.py install中途报 Microsoft Visual C++ 14.0 required。原因:只装了运行库,没装编译工具。解决:装完整 C++ 生成工具并勾选桌面开发工作负载,重开命令行再装。
5.2 get_data 报找不到 qlib.tests
现象:下载官方数据时ModuleNotFoundError: No module named 'qlib.tests'。原因:pip 安装包未包含 tests 目录。解决:从源码 zip 里拷 tests 到安装路径,或直接用 setup.py 方式安装。
5.3 dump_all 转换后因子全空
现象:数据转换成功,但D.features()取出来全是 NaN。原因:--include_fields逗号后带了空格,或 csv 字段名与参数不一致。解决:去掉空格,逐字段核对列名,转换后先抽查一只股票的 close 序列是否连续。
5.4 回测净值曲线异常平滑或收益离谱
现象:策略收益高得不真实,或净值几乎不动。原因:价格未复权、缺 factor/change 字段,或预处理拟合区间覆盖了测试段。解决:统一用后复权价,补齐字段,严格保证fit_end_time早于测试起点。
5.5 自定义因子不生效
现象:改了 handler 但训练结果和默认一模一样。原因:task 里 dataset 的 handler 仍指向原类,或实验缓存复用了旧结果。解决:确认module_path和class指向新类,换一个experiment_name重跑,别在旧实验上覆盖。
6. 用实验归档做对比:一个我每次都会走的验证习惯
Qlib 最容易被低估的功能是实验管理。很多人跑完一次回测看一眼净值就完事,下次改个参数再跑,两次结果混在一起,根本说不清是因子起作用还是模型调参起作用。我的做法是每次训练都带独立的experiment_name,然后用 recorder 把关键指标拉出来横向对比:
from qlib.workflow import R # 列出所有实验,按需取某次运行的指标 experiments = R.list_experiments() for exp_name in experiments: rec = R.get_recorder(experiment_name=exp_name) metrics = rec.list_metrics() ic = rec.load_object("pred_score") # 预测分数对象 print(exp_name, metrics)R.list_experiments()返回实验列表,get_recorder定位到具体一次运行,list_metrics拿到 IC、Rank IC、年化收益等记录值。这样改因子、换模型、调topk的每一次尝试都有独立档案,对比时不会串味。
验证层面,我一般固定三步:先看预测分数的 IC 和 Rank IC 是否稳定为正,再看净值相对基准的超额是否单调,最后看换手率和最大回撤是否在可接受范围。三步里任何一步异常,先回去查数据复权和预处理区间,而不是急着调模型参数——十次里有七八次问题出在前处理,不在模型本身。
还有一个具体技巧:把segments的验证段单独留出来做早停,测试段只在最后看一次。反复在测试段上调参,等于把测试集变成了训练集,指标再漂亮也是自欺欺人。这套流程跑顺之后,换股票池、换市场只是改region和数据目录的事,工作流本身不用重写。
从那以后我每次接一个新的量价策略,都强制先跑一遍默认 Alpha158 加 LightGBM 拿到基线,再动任何自定义组件,基线不对就先修数据。希望这套拆法能帮到你,少在安装和数据格式上耗时间,把精力留给因子和模型。
本文还有配套的精品资源,点击获取