☰
Qlib 港股日频选股实战:Alpha158 因子与 LightGBM 全流程
2026/10/23 3:32:33 网站建设 项目流程

简介:这份PDF是华泰证券金工研究团队出品的深度研究报告《微软AI量化投资平台Qlib体验》,面向量化投资研究者、金融工程从业者及希望上手AI选股的学生与开发者。报告系统梳理了Qlib的定位与优势,指出其覆盖量化投资全过程,并在数据存储、表达式引擎、缓存系统等工程环节做出创新,读取原始数据与生成因子的性能优于MySQL、MongoDB、InfluxDB和HDF5。内容以港股日频量价因子LightGBM选股策略为例,分安装、数据准备、因子生成、策略构建、结果展示讲解基础功能,并进一步介绍自定义因子、标签、预处理方式与AI模型的进阶用法,同时给出作者使用体会与风险提示。资源为1个PDF文件,压缩包约4.71MB,已有1172人学习下载,适合作为快速了解Qlib架构与实战流程的参考材料。

1. 港股日频选股跑通全流程:为什么我拿 Qlib 当量化 AI 的脚手架

去年帮一个做资管的朋友看策略代码,他团队三个人维护了四套脚本:一套从本地 csv 读行情,一套用 MySQL 存因子,一套跑 LightGBM 训练,还有一套单独做回测。每次改一个因子,四套代码都得动,光对齐字段就耗掉半天。这种“重复造轮子”的痛,在中小量化团队里太常见了。Qlib 想解决的正是这件事——它把数据存储、因子计算、模型训练、回测评估串成一条流水线,用一套 Python 接口覆盖量化投资全过程。这篇笔记不讲虚的,就按“装环境 → 灌数据 → 生成因子 → 训模型 → 跑回测 → 改组件”的顺序,把港股日频量价因子 LightGBM 选股策略完整走一遍,顺带把官方文档里没写的坑填上。适合手里有行情数据、想快速搭一套 AI 选股原型的开发者,也适合被多套脚本折磨过的熟手。

2. 环境搭建与数据准备:从 csv 到 bin 的完整链路

2.1 安装环节的两个硬门槛

Qlib 官方给了 pip 和 setup.py 两条安装路径。pip install pyqlib 最省事,但我在 Windows 和 Mac 上都试过,更稳的是 setup.py 方式,因为它会把开发版的最新特性一并装上。不过走 setup.py 之前,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++ 的桌面开发”那一组选项,否则编译扩展模块时照样翻车。

装完 C++ 工具后,先更新两个基础库:

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 在根目录,再执行 python setup.py install。我装到的版本是 0.6.1.dev0,这个版本号在后续排查依赖冲突时有用。

2.2 官方数据下载与用户数据转换

Qlib 自带 A 股和美股数据用于学习测试。下载 A 股数据的命令是:

python scripts/get_data.py qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn

如果你用 pip 装的 Qlib,这条命令可能报 ModuleNotFoundError: No module named 'qlib.tests'。原因是 pip 包没带 tests 目录。解决办法是从 GitHub 下载 zip 源码包,把里面的 tests 文件夹拷到安装路径下。

真正做研究时,用的肯定是自己的数据。Qlib 支持 csv 导入,但要求每只股票一个 csv 文件,文件名就是股票代码,统一放在一个文件夹里。以港股为例,我准备的数据从 2008 年初到 2020 年 11 月末,共 3739 只股票(含权证和已退市),文件夹命名为 hk_data。每个 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

参数含义很直白:symbol_field_name 指定 csv 里股票代码的列名,date_field_name 指定日期列名,include_fields 列出其余要转换的字段。注意 include_fields 里逗号后面不能有空格,否则转换会报错。转换完成后,~/.qlib/qlib_data/hk_data 下会生成 calendar、features 和 instruments 三个目录,分别存交易日历、二进制特征数据和股票列表。

3. 因子生成与模型训练:Alpha158 和 LightGBM 怎么串起来

3.1 初始化环境与自定义股票池

数据就位后,先初始化 Qlib 运行环境,指定数据路径和区域:

import qlib from qlib.constant import REG_HK qlib.init(provider_uri="~/.qlib/qlib_data/hk_data", region=REG_HK)

region 参数决定交易日历和股票代码格式,港股用 REG_HK。初始化之后,可以拉交易日和全部股票代码:

from qlib.data import D # 获取交易日历 trade_dates = D.calendar(start_time="2010-01-01", end_time="2020-11-30", freq="day") # 获取全部股票代码 instruments = D.instruments(market="all") stock_list = D.list_instruments(instruments=instruments, start_time="2010-01-01", end_time="2020-11-30", as_list=True)

实际选股时不会全市场都买,得自定义股票池。Qlib 支持通过 filter 表达式筛选,比如剔除低价股和低流动性股票:

from qlib.data.filter import NameDFilter # 自定义股票池:只保留收盘价大于 1 港元且成交额大于 100 万港元的股票 stock_pool = D.instruments(market="all") filtered = D.list_instruments( instruments=stock_pool, start_time="2010-01-01", end_time="2020-11-30", as_list=True, filter_pipe=[NameDFilter(name_rule="close > 1 and money > 1000000")] )

这一步在官方示例里经常被跳过,但实盘研究绕不开。不剔低价股和低流动性股票,回测收益会被不可交易的标的拉高,结果没有参考价值。

3.2 Alpha158 因子库与标签生成

Qlib 内置了 Alpha158 和 Alpha360 两个因子库。Alpha158 包含 158 个量价因子,覆盖 K 线形态、动量、波动率等维度。生成因子和标签的代码如下:

from qlib.contrib.data.handler import Alpha158 # 定义数据处理器,生成 Alpha158 特征和标签 handler = Alpha158( instruments="all", start_time="2010-01-01", end_time="2020-11-30", freq="day", label=["Ref($close, -2)/Ref($close, -1) - 1"] # 下期收益作为标签 ) # 获取特征和标签 features = handler.fetch(col_set="feature") labels = handler.fetch(col_set="label")

标签表达式 Ref($close, -2)/Ref($close, -1) - 1 表示用后一天收盘价买入、再后一天收盘价卖出的收益率。Ref 是 Qlib 表达式引擎的引用函数,-1 表示向前偏移一天,-2 表示向前偏移两天。这个表达式可以根据策略周期调整,比如做周频就改成 Ref($close, -5)/Ref($close, -1) - 1。

Alpha158 默认的预处理方式包括 DropnaLabel、CSZScoreNorm 等,这些在 qlib.data.dataset.processor 里定义。如果因子分布和默认假设不符,可以替换预处理方法,后面进阶部分会讲。

3.3 LightGBM 模型训练与工作流配置

Qlib 把模型训练、因子生成、回测串成一个 workflow,核心配置是一个 task 字典。先导入必要模块:

from qlib.contrib.model.gbdt import LGBModel from qlib.contrib.data.handler import Alpha158 from qlib.workflow import R from qlib.workflow.record_temp import SignalRecord, PortAnaRecord

然后定义模型参数和数据集参数:

# AI 模型参数 model_config = { "class": "LGBModel", "module_path": "qlib.contrib.model.gbdt", "kwargs": { "loss": "mse", "learning_rate": 0.05, "num_leaves": 64, "num_threads": 8, "early_stopping_rounds": 50, } } # 数据集参数 dataset_config = { "class": "DatasetH", "module_path": "qlib.data.dataset", "kwargs": { "handler": { "class": "Alpha158", "module_path": "qlib.contrib.data.handler", "kwargs": { "start_time": "2010-01-01", "end_time": "2020-11-30", "fit_start_time": "2010-01-01", "fit_end_time": "2017-12-31", "instruments": "all", }, }, "segments": { "train": ("2010-01-01", "2017-12-31"), "valid": ("2018-01-01", "2018-12-31"), "test": ("2019-01-01", "2020-11-30"), }, }, }

这里 fit_start_time 和 fit_end_time 控制预处理器的拟合区间,必须早于 valid 和 test 区间,否则会引入未来信息。train、valid、test 三段的时间划分也要根据实际数据量调整,训练集太短模型学不到东西,太长又容易过拟合。

配置好后启动训练:

# 启动工作流 with R.start(experiment_name="hk_lgb_experiment"): model = LGBModel(**model_config["kwargs"]) dataset = DatasetH(**dataset_config["kwargs"]) model.fit(dataset) R.save_objects(model=model)

训练完成后,模型和数据集对象会保存在 Qlib 的 experiment 目录下,后续回测直接加载即可。

4. 回测评估与结果解读:IC、净值、回撤怎么看

4.1 回测参数配置与执行

模型训完,接下来跑回测。Qlib 的回测配置通过 port_analysis_config 控制:

port_analysis_config = { "strategy": { "class": "TopkDropoutStrategy", "module_path": "qlib.contrib.strategy", "kwargs": { "topk": 50, "n_drop": 5, }, }, "backtest": { "start_time": "2019-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 是 Qlib 内置的选股策略:每期持有 topk 只股票,每期从持仓中剔除 n_drop 只表现最差的,再补入新的。topk=50、n_drop=5 是常用起点,资金量小的话 topk 可以降到 20 左右。exchange_kwargs 里 limit_threshold 是涨跌停限制,港股没有涨跌停,这里设 0.095 只是示例,实际港股应设为 1 或去掉。open_cost 和 close_cost 是买卖手续费率,min_cost 是最低手续费。

执行回测:

with R.start(experiment_name="hk_lgb_experiment"): # 加载训练好的模型 model = R.load_object("model") dataset = DatasetH(**dataset_config["kwargs"]) # 生成预测信号 signal = model.predict(dataset) # 回测 port_analysis = PortAnaRecord( recorder=R, config=port_analysis_config, risk_analysis_freq="day", ) port_analysis.generate()

4.2 绩效指标解读

回测跑完后,Qlib 会输出 IC、Rank IC、净值曲线、最大回撤和换手率。IC 是预测值和实际收益的相关系数,Rank IC 是秩相关系数。一般来说,日频量价因子的 IC 在 0.03 到 0.05 之间算正常,Rank IC 在 0.05 到 0.08 之间算不错。如果 IC 低于 0.02,说明因子和模型没有捕捉到有效信号,得回头检查因子生成或标签定义。

净值曲线看的是策略相对基准的超额收益。港股恒生指数作为基准,如果策略净值长期跑输基准,要么是因子失效,要么是回测参数设置有问题。最大回撤反映策略的风险控制能力,日频选股策略的最大回撤通常在 20% 到 40% 之间,超过 50% 就得审视持仓集中度和止损逻辑。换手率看的是交易频率,TopkDropoutStrategy 的日频换手率一般在 10% 到 30% 之间,太高的话手续费会吃掉大部分收益。

读取回测结果的代码:

# 读取 IC 和 Rank IC ic = R.load_object("analysis/IC") rank_ic = R.load_object("analysis/RankIC") # 读取净值曲线 net_value = R.load_object("analysis/portfolio_analysis/net_value")

5. 避坑与常见问题:那些官方文档没写的细节

5.1 数据字段缺失导致净值异常

现象:回测跑完,策略净值曲线是一条直线或者剧烈波动,和预期完全不符。 原因:csv 数据里缺少 factor 或 change 字段。Qlib 在计算收益率和交易价格时依赖这两个字段,缺了之后数据对齐出错。 解决:在 csv 里补上 factor(复权因子)或 change(涨跌幅)字段,重新执行 dump_all 转换。如果只有不复权价格,至少补上 change 字段。

5.2 include_fields 逗号后带空格

现象:dump_all 执行到一半报字段解析错误,转换中断。 原因:--include_fields 参数里逗号后面带了空格,比如 open, high, low,Qlib 把空格也当成字段名的一部分。 解决:写成 open,high,low,close,volume,money,factor,vwap,change,逗号后不留空格。

5.3 pip 安装后 get_data 报 ModuleNotFoundError

现象:pip install pyqlib 之后,运行 python scripts/get_data.py 报 No module named 'qlib.tests'。 原因:pip 包为了减小体积,没有打包 tests 目录,而 get_data.py 依赖 tests 里的部分模块。 解决:从 GitHub 下载 zip 源码包,把 tests 文件夹拷贝到 Qlib 安装路径下,或者直接用 setup.py 方式安装。

5.4 预处理拟合区间泄露未来信息

现象:回测 IC 高得离谱,超过 0.1,但实盘表现差很多。 原因:fit_start_time 和 fit_end_time 设置在了 test 区间之后,预处理器用到了未来数据做标准化。 解决:fit_end_time 必须早于 valid 和 test 的起始时间。通常 fit 区间和 train 区间一致,或者略短于 train 区间。

5.5 港股涨跌停参数设置错误

现象:回测换手率异常低,很多交易被拒绝。 原因:exchange_kwargs 里 limit_threshold 设成了 0.095,这是 A 股的涨跌停限制,港股没有涨跌停,导致大量交易被误判为涨跌停无法成交。 解决:港股回测时把 limit_threshold 设为 1 或直接去掉该参数。

6. 二次开发:自定义因子、标签和模型的改造思路

6.1 自定义因子的两种方式

最简单的自定义因子方式是直接改参数。Alpha158 的因子列表在源码里定义,找到 qlib.contrib.data.handler.Alpha158 的 get_feature_config 方法,里面有一个 fields 列表,每个元素是一个因子表达式。比如想加一个 5 日动量因子,在列表里追加:

"ROC5": ("Ref($close, -5)/$close - 1", False),

第一个元素是表达式,第二个元素是是否归一化。改完源码后重新生成数据集即可。

更灵活的方式是仿照 Alpha158 创建新的继承类:

from qlib.contrib.data.handler import Alpha158 class MyAlpha(Alpha158): def get_feature_config(self): fields, names = super().get_feature_config() # 追加自定义因子 fields.append("Ref($close, -10)/Ref($close, -5) - 1") names.append("ROC10") return fields, names

然后在 dataset_config 里把 handler 的 class 换成 MyAlpha,module_path 指向你的文件路径。这种方式不污染源码,升级 Qlib 时不会冲突。

6.2 自定义标签与预处理

标签通过 label 参数控制。默认标签是下期收益,改成周频收益:

handler = Alpha158( instruments="all", start_time="2010-01-01", end_time="2020-11-30", freq="day", label=["Ref($close, -5)/Ref($close, -1) - 1"], )

预处理方法在 qlib.data.dataset.processor 里定义,包括 DropnaLabel、CSZScoreNorm、CSRankNorm 等。替换预处理方式:

from qlib.data.dataset.processor import CSRankNorm handler = Alpha158( instruments="all", start_time="2010-01-01", end_time="2020-11-30", freq="day", label=["Ref($close, -2)/Ref($close, -1) - 1"], learn_processors=[CSRankNorm()], # 替换默认的 CSZScoreNorm )

CSRankNorm 做的是截面秩标准化,对异常值更稳健。如果因子分布偏态严重,用 CSRankNorm 比 CSZScoreNorm 更合适。

6.3 更换 AI 模型

Qlib 内置了 LightGBM、XGBoost、CatBoost、MLP、LSTM 等模型,都在 qlib.contrib.model 下。换模型最简单的方式是改 model_config:

model_config = { "class": "XGBModel", "module_path": "qlib.contrib.model.xgboost", "kwargs": { "max_depth": 6, "learning_rate": 0.05, "n_estimators": 500, }, }

如果要接入 Qlib 没有的模型,比如 SVR,创建一个继承自 qlib.model.base.Model 的子类:

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"]) self.model.fit(X_train.values, y_train.values.ravel()) def predict(self, dataset): X_test, _ = dataset.prepare("test", col_set=["feature", "label"]) return self.model.predict(X_test.values)

然后在 model_config 里把 class 换成 SVRModel,module_path 指向你的文件。这样就能把任意 sklearn 兼容的模型接进 Qlib 的 workflow。

6.4 验证改造效果的方法

改完因子、标签或模型后,别急着上实盘。先跑一遍回测,对比 IC、Rank IC 和净值曲线。IC 提升但净值没提升,说明因子有预测力但交易成本吃掉了收益,得调换手率或手续费参数。净值提升但最大回撤也大幅上升,说明模型在冒险,得加风控或降低仓位。我一般会固定 train 和 valid 区间,只改 test 区间做对比,这样能排除数据划分带来的干扰。

从那以后我每次改完因子或模型,都强制走一遍“IC 对比 → 净值对比 → 回撤对比”三步,少一步都不敢往实盘推。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询