资讯动态

Qlib港股日频选股实战:从数据转换到LightGBM回测全流程

发布时间:2026/10/23 3:32:40 来源:尧图企业网站定制
简介这份PDF是华泰证券金工研究团队出品的深度报告《微软AI量化投资平台Qlib体验》面向量化投资研究者、金融工程从业者及希望上手AI选股的学生与开发者。报告系统梳理了Qlib的定位与优势覆盖量化投资全过程在因子数据存储、表达式引擎、缓存系统等工程环节提出创新方案性能优于MySQL、MongoDB、InfluxDB与HDF5。内容以港股日频量价因子LightGBM选股策略为例分安装、数据准备、因子生成、策略构建、结果展示讲解基础功能并延伸至自定义因子、标签、预处理方式与AI模型的进阶用法同时给出作者使用体会与风险提示。资源包为1个PDF文件大小约4.71MB结构完整、便于检索。目前已有1172人学习下载适合作为快速了解Qlib架构与实战流程的入门参考。1. 港股日频选股这条链路Qlib 到底替你省掉了哪几块活如果你写过 A 股或港股的日频量价选股脚本大概率经历过这种局面行情数据散在几十个 CSV 里因子计算用 pandas 一行行 rolling模型训练换一套框架回测再换一套最后绩效分析还得自己拼净值曲线。整条链路能跑通但每换一个环节就要重新对齐字段、日期和股票池调试成本高得离谱。Qlib 想解决的正是这件事——它把数据存储、因子表达式、模型训练、回测和绩效分析串成一条工作流你只需要按它的约定把数据喂进去剩下的组件通过配置参数就能替换。这篇笔记以港股日频量价因子加 LightGBM 选股为例把安装、数据准备、因子生成、策略构建、结果展示这条链路拆开走一遍重点讲清楚哪些地方容易翻车、参数该怎么设。适合已经会用 Python 做因子研究、但还没把整条链路工程化的从业者。2. 环境搭建与数据准备从 CSV 到 bin 的完整转换链路2.1 安装环节的两个硬依赖Qlib 的安装本身不复杂但有两个前置条件绕不开。第一是 Python 版本建议 3.7 及以上第二是 Windows 环境下必须装 Microsoft Visual C 14.0 的编译工具。注意这里说的不是运行库Redistributable而是完整的 C 生成工具否则安装过程中会直接报error: Microsoft Visual C 14.0 is required。很多人看到这个报错第一反应是去装运行库装完发现没用因为编译扩展模块需要的是编译器而不是运行时。安装方式有两种。pip 方式最省事pip install pyqlib但我在 Windows 和 Mac 上都试过更推荐用 setup.py 方式因为它会自动拉取开发版的最新代码功能比稳定版更全。具体做法是先更新 numpy 和 cythonpip install numpy pip install --upgrade cython然后把 Qlib 源码包下载到本地进入含 setup.py 的目录执行python setup.py install这里有个细节如果你没有配置 git 环境直接下载 zip 源码包解压到任意路径即可只要确保 setup.py 在那个路径下。我当时的办公网络不支持 git 协议用的就是 zip 包方式安装过程没有额外报错。提示pip 安装后运行数据下载脚本可能报ModuleNotFoundError: No module named qlib.tests原因是 pip 包没有包含 tests 目录。解决办法是从源码包里把 tests 文件夹拷贝到安装路径下。2.2 官方数据下载与用户数据格式转换Qlib 自带 A 股和美股的学习数据下载命令如下python scripts/get_data.py qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn这条命令会把数据拉到~/.qlib/qlib_data/cn_data目录下。但官方数据只有 A 股和美股如果你想用自己的港股数据就需要走 dump_all 转换流程。转换前CSV 文件的组织方式有明确要求每只股票一个 CSV 文件文件名就是股票代码所有文件放在同一个文件夹下。比如港股数据放在hk_data文件夹里腾讯控股对应0700.csv。每个 CSV 内部至少需要包含以下字段字段名说明字段名说明stock_code股票代码与文件名一致low复权最低价date日频日期close复权收盘价volume成交量factor复权因子money成交额vwap复权均价open复权开盘价change相对前一交易日涨跌幅high复权最高价这里有两个容易踩的坑。第一价格数据建议用复权价。原因是 Qlib 后续的数据标注模块qlib.contrib.data.handler会用 close 或 vwap 计算未来收益率回测模块qlib.contrib.evaluate.backtest也会用 close、open 或 vwap 来模拟交易。如果你喂的是不复权价格遇到除权除息日收益率计算会直接失真。第二数据里必须包含 factor 或 change 字段否则跑官方全流程范例examples/workflow_by_code.py时策略收益和净值会出现异常——这个现象很隐蔽因为代码不会报错只是结果不对。除了个股数据还需要准备指数数据作为基准。把恒生指数行情按同样的 CSV 格式保存命名为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是转换后的输出目录--symbol_field_name指定股票代码列名--date_field_name指定日期列名--include_fields列出需要转换的字段。注意--include_fields里逗号后面不能有空格否则转换会报错。转换完成后输出目录下会生成 calendar、features 和 instruments 三个子目录分别存放交易日历、因子数据和股票列表。3. 因子生成与 LightGBM 策略构建从 Alpha158 到回测配置3.1 初始化环境与自定义股票池数据转换完成后第一步是初始化 Qlib 运行环境并指向你的数据目录import qlib from qlib.constant import REG_HK qlib.init(provider_uri~/.qlib/qlib_data/hk_data, regionREG_HK)provider_uri指向 dump_all 的输出目录region指定市场区域。初始化之后就可以获取交易日历和股票列表from qlib.data import D # 获取交易日历 cal D.calendar(start_time2018-01-01, end_time2020-11-30, freqday) # 获取全部股票代码 instruments D.instruments(marketall) stock_list D.list_instruments(instrumentsinstruments, start_time2018-01-01, end_time2020-11-30)实际使用时通常不会拿全市场股票去训练需要自定义股票池。常见做法是过滤掉低价股和低流动性股票比如剔除收盘价低于 1 港元、日均成交额低于某个阈值的标的。这一步没有标准答案取决于你的策略定位但一定要做否则回测结果会被大量不可交易的标的污染。3.2 Alpha158 因子库与标签生成Qlib 内置了 Alpha158 和 Alpha360 两个因子库。Alpha158 包含 158 个量价因子覆盖 K 线形态、滚动统计量、时序排名等维度。生成因子和标签的代码如下from qlib.contrib.data.handler import Alpha158 handler Alpha158( instrumentsall, start_time2018-01-01, end_time2020-11-30, fit_start_time2018-01-01, fit_end_time2019-06-30, infer_processors[ {class: RobustZScoreNorm, kwargs: {fields_group: feature, clip_outlier: True}}, {class: Fillna, kwargs: {fields_group: feature}}, ], learn_processors[ {class: DropnaLabel}, {class: CSRankNorm, kwargs: {fields_group: label}}, ], label[Ref($close, -2) / Ref($close, -1) - 1], )这段配置里有几个关键参数需要理解。fit_start_time和fit_end_time是数据预处理的拟合区间必须严格早于验证集和测试集否则会引入未来信息。infer_processors定义推理阶段的数据处理流程RobustZScoreNorm 做稳健标准化并裁剪异常值Fillna 填充缺失值。learn_processors定义训练阶段的处理DropnaLabel 丢弃标签缺失的样本CSRankNorm 对标签做横截面排名归一化。label参数用表达式引擎定义标签Ref($close, -2) / Ref($close, -1) - 1表示未来第二天的收盘价相对未来第一天收盘价的收益率这就是预测目标。3.3 LightGBM 模型训练与回测配置因子和标签准备好之后接下来配置模型训练任务from qlib.contrib.model.gbdt import LGBModel from qlib.workflow import R model LGBModel( lossmse, colsample_bytree0.8, learning_rate0.05, subsample0.8, lambda_l10.1, lambda_l20.1, max_depth8, num_leaves64, num_threads8, verbosity-1, ) dataset_config { class: DatasetH, module_path: qlib.data.dataset, kwargs: { handler: handler, segments: { train: (2018-01-01, 2019-06-30), valid: (2019-07-01, 2019-12-31), test: (2020-01-01, 2020-11-30), }, }, }LightGBM 的参数里learning_rate和num_leaves对结果影响最大。学习率设 0.05 是一个比较稳的起点num_leaves 控制在 64 以内可以降低过拟合风险。lambda_l1和lambda_l2是正则化项量价因子信噪比低正则化不能省。训练集、验证集、测试集的切分要严格按时间顺序不能随机打乱。回测配置如下port_analysis_config { strategy: { class: TopkDropoutStrategy, module_path: qlib.contrib.strategy, kwargs: { signal: PRED, topk: 50, n_drop: 5, }, }, backtest: { start_time: 2020-01-01, end_time: 2020-11-30, account: 100000000, benchmark: hkhsi, 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 只并买入新的标的。limit_threshold设 0.095 是港股涨跌停限制的近似值open_cost和close_cost分别对应买入和卖出的手续费率。这些参数直接决定回测结果的可信度手续费设得太低会让策略看起来比实际能赚钱。4. 避坑与排查那些官方文档没写的翻车点4.1 数据字段缺失导致净值异常现象策略回测跑完没有报错但净值曲线是一条直线或者波动极小。原因CSV 数据里缺少 factor 或 change 字段Qlib 在计算复权收益时无法正确还原价格序列。解决检查 dump_all 的--include_fields参数是否包含了 factor 和 change如果原始数据里没有这两个字段需要从行情终端导出时勾选复权因子和涨跌幅。4.2 预处理拟合区间穿越未来现象模型在测试集上的 IC 高得离谱实盘模拟却完全不行。原因fit_start_time和fit_end_time设置时覆盖了验证集或测试集的时间段标准化参数用到了未来数据。解决拟合区间必须严格早于验证集起始日期建议留出至少半年的间隔。我一般会把拟合区间设在训练集的前 70% 时间段内。4.3 dump_all 转换时逗号后带空格现象数据转换脚本报错退出提示字段解析失败。原因--include_fields参数里逗号后面加了空格比如写成了open, high, low。解决改成open,high,low逗号后不留空格。这个坑很小但很隐蔽因为肉眼不容易注意到空格。4.4 股票池未过滤导致回测失真现象回测年化收益很高但持仓里全是日均成交额几十万的仙股。原因股票池用了marketall没有剔除低价股和低流动性标的。解决在获取股票列表后加过滤条件比如$close 1且$volume * $vwap 1000000具体阈值根据市场情况调整。4.5 模型训练时验证集损失不下降现象训练集 loss 持续下降验证集 loss 从某个 epoch 开始反弹。原因LightGBM 的 num_leaves 或 max_depth 设得太大模型在量价因子上过拟合。解决先把 num_leaves 降到 32 以下max_depth 降到 6观察验证集 loss 是否稳定。如果仍然过拟合增加 lambda_l1 和 lambda_l2 的值或者减少特征数量。5. 自定义组件与进阶技巧改参数还是改源码5.1 自定义因子、标签与预处理Qlib 的进阶用法分两个层次改参数和改源码。最简单的自定义方式是直接修改配置参数。比如换标签只需要改 handler 的 label 参数# 改成预测未来 5 日收益率 label [Ref($close, -5) / Ref($close, -1) - 1]换预处理方法也是改参数infer_processors [ {class: ZScoreNorm, kwargs: {fields_group: feature}}, {class: Fillna, kwargs: {fields_group: feature}}, ]Qlib 内置的预处理方法在qlib.data.dataset.processor模块下常用的有 RobustZScoreNorm、ZScoreNorm、MinMaxNorm、CSRankNorm、DropnaLabel 等。Alpha158 和 Alpha360 的默认预处理方式不同Alpha158 用 RobustZScoreNorm 加 CSRankNormAlpha360 用 ZScoreNorm 加 CSRankNorm。换预处理方法时要注意 fit 区间和 infer 区间的处理逻辑是否匹配。如果改参数不够用就需要改源码或创建继承类。比如自定义一个因子可以在 handler 的get_feature_config方法里添加表达式class MyAlpha158(Alpha158): def get_feature_config(self): conf super().get_feature_config() conf[feature].append($close / Ref($close, 20) - 1) return conf这种方式适合添加少量自定义因子。如果要大规模替换因子库建议直接仿照 Alpha158 的源码结构写一个新的 handler 类。5.2 更换 AI 模型与验证方法Qlib 内置了多种 AI 模型放在qlib.contrib.model下包括 LightGBM、XGBoost、CatBoost、Linear、MLP、LSTM、GRU、Transformer 等。换模型只需要改 model 配置from qlib.contrib.model.pytorch_lstm import LSTM model LSTM( d_feat158, hidden_size64, num_layers2, dropout0.2, n_epochs100, lr0.001, batch_size2048, )如果内置模型不满足需求可以创建新的 Model 子类。比如用 SVRfrom 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_keyDataHandlerLP.DK_L) self.model.fit(X_train.values, y_train.values.ravel()) def predict(self, dataset): X_test, _ dataset.prepare(test, col_set[feature, label], data_keyDataHandlerLP.DK_I) return pd.Series(self.model.predict(X_test.values), indexX_test.index)验证方法上除了看 IC 和 Rank IC我习惯加一步分层回测把预测值分成 5 组看多空收益差是否单调。如果分组收益没有单调性说明模型学到的信号不稳定换模型或调参的意义不大应该回头检查因子质量和数据预处理。从那以后我每次跑完回测都会强制走一遍分层收益单调性检查确认信号方向一致再去看净值曲线。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑