资讯动态

Qlib实战:从港股数据到LightGBM选股全流程

发布时间:2026/10/29 13:25:07 来源:尧图企业网站定制
简介这份PDF资料是华泰证券金工研究团队2020年12月发布的深度研究报告聚焦微软AI量化投资平台Qlib的实操体验面向量化投资研究者、金融工程从业者及希望降低AI算法使用门槛的开发者。报告以港股日频量价因子LightGBM选股策略为案例分Qlib安装、数据准备、因子生成、策略构建、结果展示等步骤讲解基础功能并进一步介绍自定义因子、标签、数据预处理方式与AI模型等进阶用法同时对比传统量化策略开发流程提炼Qlib在数据存储方案、表达式引擎和缓存系统上的工程创新。资源包共1个PDF文件约4.71MB内容完整、结构清晰适合按章节系统阅读。目前已有1173人学习可作为快速上手Qlib、理解其优势与局限的参考材料。1. 从一份港股数据说起Qlib 到底能帮你省掉哪些活手里有一份 2008 年到 2020 年的港股日频行情三千多只标的每只一个 csv字段是开高低收、成交量、成交额、复权因子。想拿它跑一个量价因子加 LightGBM 的选股策略传统做法是自己写数据清洗、自己拼因子矩阵、自己接模型、自己写回测、自己算 IC 和净值。每一步都能跑但每一步都要重新造一遍轮子中间任何一个字段对不上回测结果就是一笔糊涂账。Qlib 想解决的就是这段重复劳动。它把数据存储、因子表达式、模型训练、策略回测、绩效分析串成一条工作流你只需要准备符合格式的行情数据剩下的用配置和少量代码驱动。这篇笔记按我实际拆源码的顺序走一遍先看它怎么装、怎么把 csv 灌成自己的 bin 格式再看 Alpha158 因子库和 LightGBM 工作流怎么串起来最后落到自定义因子、标签、预处理和模型这几处真正要改的地方。适合已经会写 Python、想快速把一套 AI 选股流程跑通再动手改的开发者。2. 环境与数据管线从 csv 到 bin 的完整落地路径Qlib 的安装和数据准备是整个流程里最容易翻车的两段官方文档写得偏简很多坑要自己踩一遍才知道。这一章把安装、官方数据下载、自定义 csv 转换三件事拆开讲每一步都给出可复制的命令和参数含义。2.1 安装为什么我更推荐 setup.py 而不是 pip先说结论pip install pyqlib能装但它拉的是稳定版而 Qlib 迭代快很多示例代码对应的是开发版接口稳定版跑官方 workflow 时偶尔会对不上。我一般用源码包加setup.py装拿到的是包含开发版在内的最新代码。Windows 上第一道坎是编译依赖。Qlib 的部分扩展需要 Microsoft Visual C 14.0只装运行库Redistributable不够必须装完整的 C 生成工具。报错长这样error: Microsoft Visual C 14.0 is required. Get it with Build Tools for Visual Studio去装 Visual Studio Build Tools安装时勾选「使用 C 的桌面开发」这一组别只勾单个组件。装完再走下面的流程# 先保证 numpy 和 cython 是最新的否则编译扩展会失败 pip install numpy pip install --upgrade cython # 方式一有 git 环境 git clone qlib 源码地址 cd qlib python setup.py install # 方式二没有 git下载 zip 解压到任意路径确保 setup.py 在该目录下 cd /path/to/qlib python setup.py installsetup.py install会自动处理依赖并编译扩展装完可以用python -c import qlib; print(qlib.__version__)验证。这里有个细节如果你在 conda 环境里装确认当前命令行激活的就是目标环境否则包装到 base 里去后面 import 找不到会怀疑人生。2.2 官方数据下载与那个经典的 ModuleNotFoundError装完之后先拿官方数据验证链路通不通。Qlib 提供 A 股和美股两套测试数据下载命令在 Qlib 安装路径下执行python scripts/get_data.py qlib_data \ --target_dir ~/.qlib/qlib_data/cn_data \ --region cn--target_dir是数据落地目录--region选cn或us。下载完成后数据在~/.qlib/qlib_data/cn_data里面是 calendar、features、instruments 三块后面初始化时会用到这个路径。用 pip 装的人这里大概率会撞上ModuleNotFoundError: No module named qlib.tests原因是 pip 包没打包 tests 目录而get_data.py依赖里面的工具函数。解决办法很直接下载源码 zip把tests文件夹整个拷到安装路径下对应位置。这也是我推荐源码安装的原因之一省掉这类补丁活。2.3 自定义 csv 转 bindump_all 的参数与两个隐藏要求官方数据只是让你验证环境真正做研究得用自己的数据。Qlib 支持 csv 导入但要求每只标的一个文件文件名就是代码所有文件放同一个目录。转换命令是dump_bin.py dump_allpython 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指定 csv 里哪一列是代码列名不必叫 stock_code但必须和文件名一致--date_field_name指定日期列--include_fields列出要保留的字段逗号后面绝对不能有空格否则字段解析错位转换出来的数据是坏的。字段层面有两个官方文档没明说、但实际绕不开的要求。第一价格要用复权价。因为后续标签计算用的是 close 或 vwap 算未来收益回测成交也用 close/open/vwap用未复权价会让收益率在除权日出现假跳空。第二数据里必须带factor或change字段缺了它跑官方全流程示例时策略净值和收益会明显异常这个现象很隐蔽容易误判成模型问题。转换完成后目录结构是这样的路径内容calendar/交易日历文件features/每只标的的 bin 二进制数据按字段分文件instruments/股票池定义决定哪些标的参与回测bin 格式是 Qlib 性能优势的来源之一它按列存、按需读比每次从 csv 或关系库全表扫描快得多。数据管线到这里就算打通了下一章进入因子和模型。3. 核心工作流拆解Alpha158 因子库到 LightGBM 选股这一章是整篇的重点。Qlib 把一次完整的 AI 选股拆成几个可配置的组件数据处理器生成因子和标签、模型、回测。它们通过一个task字典串起来理解这个字典的结构基本就理解了 Qlib 的工作方式。3.1 初始化与数据读取先确认你能拿到数在写任何策略之前先做一次最小验证确认数据能被正确读出来。Qlib 用qlib.init挂载数据目录之后所有读取都基于这个上下文import qlib from qlib.data import D # provider_uri 指向 dump 出来的 bin 目录region 影响交易日历和交易规则 qlib.init(provider_uri~/.qlib/qlib_data/hk_data, regionhk) # 拿交易日历和全部标的确认数据不是空的 cal D.calendar(start_time2020-01-01, end_time2020-06-30) instruments D.instruments(marketall) codes D.list_instruments(instruments, start_time2020-01-01, end_time2020-06-30) # 取单只标的的指定字段验证字段名和复权是否正确 df D.features([0700.HK], [$close, $volume], start_time2020-01-01, end_time2020-06-30) print(df.tail())provider_uri就是上一章--qlib_dir的路径region决定日历和涨跌停规则港股填hk。D.features里的字段用$前缀这是 Qlib 表达式引擎的语法$close表示取 close 列。这一步能打印出正常的价格序列说明数据管线没问题可以往下走。3.2 自定义股票池与 Alpha158 因子生成股票池通过 instruments 文件控制。最简单的做法是写一个文本文件每行「代码 起止日期」然后在D.instruments里引用。实际研究里我一般会先按流动性、上市天数过滤一遍再写进去避免把低价股和长期停牌的标的混进训练集。因子这块 Qlib 内置了 Alpha158 和 Alpha360 两套因子库。Alpha158 是 158 个量价类因子表达式覆盖动量、波动、量价相关性等常见维度开箱即用。生成因子和标签的核心是 handler 配置from qlib.contrib.data.handler import Alpha158 # handler 负责把原始行情按表达式算成因子矩阵并生成标签 handler Alpha158( instrumentscsi300, # 股票池换成自己的 instruments 名 start_time2008-01-01, end_time2020-11-30, fit_start_time2008-01-01, # 预处理器拟合区间避免用未来数据 fit_end_time2017-12-31, infer_processors[], # 推理阶段的预处理 learn_processors[DropnaLabel], # 训练阶段预处理先丢掉标签缺失样本 ) # 取训练集X 是因子y 是下期收益标签 dataset handler.fetch(col_setfeature, data_settrain) print(dataset.shape)这里几个参数值得展开。fit_start_time/fit_end_time是给标准化类预处理器拟合用的必须落在训练区间内否则就是拿未来数据做归一化回测好看实盘崩。learn_processors里DropnaLabel很关键标签是未来收益最后几天天然没有标签不丢掉会在训练时报错。col_setfeature取因子取标签用col_setlabel。Alpha158 的标签默认是下期收益具体用 close 还是 vwap 由 handler 源码里的配置决定想改就去看qlib.contrib.data.handler里对应类的实现这是后面自定义标签的入口。3.3 用 task 字典串起 LightGBM 训练Qlib 的工作流用一个嵌套字典描述最外层是task里面分model和dataset两块。先看模型部分from qlib.contrib.model.gbdt import LGBModel model LGBModel( lossmse, learning_rate0.05, max_depth6, num_leaves31, num_threads8, early_stopping_rounds50, )lossmse对应回归任务预测的是未来收益率early_stopping_rounds配合验证集防止过拟合。这些参数直接透传给 LightGBM熟手可以按自己的调参习惯改。数据集部分把 handler 的配置塞进去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, max_depth: 6}, }, 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, 2017-12-31), valid: (2018-01-01, 2019-12-31), test: (2020-01-01, 2020-11-30), }, }, }, }segments是时间切分训练、验证、测试三段不能重叠这是回测可信度的底线。module_path加class的组合是 Qlib 的反射机制它按字符串动态导入类所以自定义组件只要写对路径就能替换进去不用改框架代码。训练和预测一行搞定from qlib.utils import init_instance_by_config model init_instance_by_config(task[model]) dataset init_instance_by_config(task[dataset]) model.fit(dataset) # 内部按 segments 自动切训练/验证 pred model.predict(dataset) # 输出测试段每只标的每期的预测分数init_instance_by_config是 Qlib 的工厂函数把配置字典实例化成对象。pred是一个带 MultiIndex日期、代码的 Series后面回测直接吃这个结果。3.4 回测配置与绩效分析预测分数只是排序要变成净值曲线得走回测。回测配置单独一个字典port_analysis_config { strategy: { class: TopkDropoutStrategy, module_path: qlib.contrib.strategy.signal_strategy, kwargs: { signal: pred, # 模型预测分数 topk: 50, # 每期持有 50 只 n_drop: 5, # 每期最多换掉 5 只控制换手 }, }, backtest: { start_time: 2020-01-01, end_time: 2020-11-30, account: 100000000, benchmark: SH000300, # 基准换成自己的指数代码 exchange_kwargs: { freq: day, limit_threshold: 0.095, # 涨跌停阈值 deal_price: close, open_cost: 0.0005, close_cost: 0.0015, min_cost: 5, }, }, }TopkDropoutStrategy是最常用的选股策略每期按预测分数取前 topk只换掉分数掉出榜单的 n_drop 只其余持有不动这样换手率可控。exchange_kwargs里的成本参数别偷懒open_cost/close_cost是双边费率min_cost是最低佣金不设这些回测净值会虚高。跑完回测拿分析结果from qlib.contrib.evaluate import backtest_daily from qlib.contrib.evaluate import risk_analysis report, positions backtest_daily( start_time2020-01-01, end_time2020-11-30, strategyport_analysis_config[strategy], account100000000, benchmarkSH000300, ) # 算超额收益的风险指标 excess report[return] - report[bench] analysis risk_analysis(excess, freqday) print(analysis)risk_analysis输出年化收益、信息比率、最大回撤这一组指标。到这一步一条从原始 csv 到净值曲线的链路就完整跑通了。下一章讲怎么在这条链路上换零件。4. 自定义组件因子、标签、预处理、模型四处可改点跑通官方流程只是起点真正做研究一定要改组件。Qlib 的设计是每个组件都有对应源码改法分两档改参数最省事改源码或继承类最灵活。这一章按因子、标签、预处理、模型四个位置逐个说。4.1 自定义因子往表达式引擎里加字段因子本质是表达式。Qlib 的表达式引擎支持$close、Ref($close, 1)、Mean($volume, 5)这类写法自定义因子最轻的方式是在 handler 里追加表达式。以 Alpha158 为例它内部维护一个字段列表你可以继承后覆盖from qlib.contrib.data.handler import Alpha158 class MyAlpha158(Alpha158): def get_feature_config(self): fields, names super().get_feature_config() # 追加一个 5 日量价比因子 fields.append($volume / Mean($volume, 5)) names.append(VOL_RATIO_5) return fields, namesget_feature_config返回字段表达式列表和对应列名父类已经拼好 158 个追加即可。表达式里的Mean、Ref、Std都是引擎内置算子写法和 pandas 类似但走的是 Qlib 自己的计算路径能吃到缓存。改完把task里 handler 的class换成MyAlpha158、module_path指向你的文件工作流其余部分不用动。4.2 自定义标签改收益计算口径默认标签是下期收盘价收益想换成开盘价买入、持有 N 日的收益有两种改法。轻量的是在 handler 配置里指定标签表达式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, # 用未来 5 日 vwap 收益作为标签 label: [Ref($vwap, -5) / $vwap - 1], }Ref($vwap, -5)表示向后取 5 期负号是未来方向。这种写法适合口径微调。如果要改的是标签的生成逻辑本身比如加行业中性化就得继承 handler 覆盖get_label_config思路和上面改因子一样。4.3 更换数据预处理processor 链的插入位置Qlib 的预处理是一串 processor按顺序作用在因子矩阵上。内置的有DropnaLabel、RobustZScoreNorm、Fillna等配置里分learn_processors训练段用和infer_processors推理段用。加一个自定义 processor 只要继承基类实现__call__from qlib.data.dataset.processor import Processor class ClipExtreme(Processor): def __init__(self, lower-3, upper3): self.lower lower self.upper upper def __call__(self, df): # 对因子做截尾抑制极端值对树模型的干扰 return df.clip(self.lower, self.upper)然后在 handler 配置的learn_processors列表里加上ClipExtreme和它的module_path。注意顺序标准化类 processor 要放在截尾之后否则截尾会破坏标准化后的分布假设。这个顺序问题我在第一次配的时候搞反过模型指标没明显变化但因子分布图一看就不对。4.4 换模型从 LightGBM 到任意 sklearn 风格模型Qlib 内置了 LightGBM、XGBoost、Linear、MLP 等模型都在qlib.contrib.model下。换模型最省事的是改task[model]的class和module_path。要接一个内置没有的模型比如 SVR继承Model基类实现fit和predict即可from qlib.model.base import Model from sklearn.svm import SVR class SVRModel(Model): def __init__(self, C1.0, kernelrbf): self.model SVR(CC, kernelkernel) def fit(self, dataset): # dataset 是 DatasetHprepare 后拿到训练段 X、y df_train, df_valid dataset.prepare( [train, valid], col_set[feature, label], data_keylearn ) X df_train[feature].values y df_train[label].values.ravel() self.model.fit(X, y) def predict(self, dataset): df_test dataset.prepare(test, col_setfeature, data_keylearn) return pd.Series(self.model.predict(df_test.values), indexdf_test.index)prepare的data_keylearn表示走训练段的预处理链col_set选特征还是标签。predict返回的 Series 索引必须保持「日期 代码」的 MultiIndex回测策略靠这个索引对齐。写完把task[model]的class改成SVRModel、module_path指向你的模块整条工作流照常跑。5. 避坑与排查五个真实踩过的坑这一章单独列因为下面这几条每一条都让我多花过半天以上现象和原因对不上号的时候最耗时间。现象安装时报 Microsoft Visual C 14.0 is required。原因只装了运行库没装编译工具链Qlib 的 Cython 扩展编译不过。 解决装 Visual Studio Build Tools勾选「使用 C 的桌面开发」整组组件装完重开命令行再执行setup.py install。现象get_data.py报 ModuleNotFoundError: No module named qlib.tests。原因pip 安装的包没带 tests 目录脚本依赖里面的工具函数。 解决下载源码 zip把tests文件夹拷到安装路径对应位置或者干脆用源码setup.py安装一步到位。现象dump_all 转换成功但回测净值和收益明显异常。原因csv 里缺factor或change字段复权处理链断了收益率在除权日出现假跳空。 解决原始数据补上复权因子字段价格统一用复权价重新 dump 一遍。这个坑最隐蔽因为转换过程不报错。现象dump_all 报字段解析错误或数据错位。原因--include_fields参数逗号后面带了空格字段名被当成带前导空格的字符串。 解决写成open,high,low,close这种紧凑格式一个空格都不要有。现象训练集指标很好测试段净值一塌糊涂。原因fit_start_time/fit_end_time落在了测试区间标准化用了未来数据或者 segments 有重叠。 解决把预处理拟合区间严格限制在训练段内训练、验证、测试三段首尾不重叠改完重跑一遍对比。提示每次改完数据或配置先跑一遍最小验证读几只标的的 close 序列确认数据没问题再进模型能省掉大量「到底是数据错还是模型错」的排查时间。6. 二次开发技巧把工作流拆成可复用的实验骨架跑通一遍之后真正影响效率的是怎么组织实验。我踩过的最大坑是把所有配置写在一个脚本里改一个参数就要动代码跑十几组对比实验时版本管理直接失控。后来我把结构改成「配置外置 组件注册」两层。配置层用一个 YAML 或 dict 文件描述 task模型、handler、processor 全部用classmodule_path字符串引用脚本只负责读配置、调init_instance_by_config、跑训练和回测。这样换模型、换因子库、换预处理链都只是改配置文本实验记录天然可追溯。下面是我现在常用的骨架import yaml from qlib.utils import init_instance_by_config from qlib.contrib.evaluate import backtest_daily, risk_analysis def run_experiment(config_path): with open(config_path) as f: cfg yaml.safe_load(f) # 按配置实例化模型和数据集组件全部由字符串反射创建 model init_instance_by_config(cfg[task][model]) dataset init_instance_by_config(cfg[task][dataset]) model.fit(dataset) pred model.predict(dataset) # 回测配置里的 signal 换成当前预测结果 cfg[port_analysis_config][strategy][kwargs][signal] pred report, _ backtest_daily(**cfg[port_analysis_config][backtest], strategycfg[port_analysis_config][strategy]) return risk_analysis(report[return] - report[bench], freqday)这个骨架的关键点是signal在运行时注入而不是写死在配置里这样同一份回测配置能复用到不同模型。验证方法上我习惯每次改组件后先只跑测试段一小段时间比如一个月确认预测分数分布和持仓数量正常再放开全区间回测。全区间一次跑几分钟小段验证几十秒排查效率差好几倍。还有一个值得做的改造是给自定义组件建一个独立包所有继承类放一起module_path统一指向这个包。好处是换机器、换环境时只要把这个包和配置文件带走工作流就能复现不用去翻散落在各处的脚本。因子表达式也建议集中管理别散在 handler 里后面做因子有效性分析时能直接复用同一份定义。从那以后我每次开新实验都强制先写配置文件再写代码配置跑不通就不动模型。这个习惯帮我省掉了大量「结果对不上但不知道哪版代码跑的」的后悔药。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑