ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Qlib量化投资框架实战:因子挖掘、模型训练与回测全流程解析

Qlib量化投资框架实战:因子挖掘、模型训练与回测全流程解析 1. 量化投资新手的第一站为什么我选了Qlib先说结论Qlib是我目前用过的最省心的量化研究框架没有之一。做量化投资的人多少都有过这种经历数据要自己爬、清洗要自己写、因子要自己算、回测要自己搭一套流程下来光把环境跑通就要耗掉大半个月。更难受的是等你终于把回测框架搭好了发现别人论文里的策略根本复现不出来——不是逻辑不对而是数据处理方式和模型接口对不上。Qlib解决的就是这个问题。它是微软开源的AI量化投资平台把数据、因子、模型、训练、回测、分析这一整条链路全串起来了。你不需要自己从零造轮子只需要把精力放在策略本身。用大白话说如果说传统的量化研究是“自己买菜、自己切菜、自己开火做饭”那Qlib就是给你一个已经备好菜、调好料的中央厨房你只管按菜谱下锅就行。这篇记录是我倒腾Qlib的真实过程从环境搭建开始到数据初始化、因子处理、模型训练、回测评估一路踩坑一路填坑。适合刚接触Qlib、或者已经装了但不知道从哪下手的同学看。文章里的代码我都实测跑过版本是Qlib 0.9.xPython 3.8环境。2. 安装和环境搭建比想象中省事但有几个坑必须避开2.1 三种安装方式怎么选Qlib的安装方式主要有三种pip安装、源码安装和Docker安装。我三种都试过直接说结论。pip安装最简单适合只是拿来跑脚本、做研究的场景pip install pyqlib但如果你是打算改源码、看内部实现、甚至给Qlib提交PR那就必须用源码安装。源码安装的好处是能拿到所有测试脚本和示例代码而且可以随时切分支git clone https://github.com/microsoft/qlib.git cd qlib pip install -e .Docker方式适合不想污染本机环境的场景Qlib官方提供了现成的镜像但我个人觉得本地开发没必要上Docker除非你要部署到服务器上做定时训练。这里有一个关键知识点Qlib依赖一个叫cython的库来做加速首次安装或者首次import的时候会自动编译。这个编译过程在Windows上特别容易出问题常见的报错是Microsoft Visual C 14.0 is required。解决办法有两种一是装Visual Studio Build Tools二是直接用Anaconda环境conda会自动帮你处理大部分编译依赖。注意如果是在Windows上用pip安装建议先装好numpy、pandas、cython这几个基础库再装pyqlib否则容易遇到编译顺序导致的报错。2.2 验证安装是否成功装完之后跑一下这个命令验证import qlib print(qlib.__version__)如果正常输出版本号说明安装成功。我装的是0.9.14版本后面所有的代码都是在这个版本下跑的。另外强烈建议在初始化和下载数据之前先确认一下网络环境。Qlib的数据下载走的是官方数据源国内访问有时候会比较慢甚至超时。如果遇到下载失败可以多试几次或者手动下载数据包放到本地目录。2.3 关于Python版本的建议Qlib官方说支持Python 3.7到3.10但我实测下来3.8是最稳的。Python 3.9和3.10在某些依赖库的版本兼容上会有些小问题尤其是涉及lightgbm、torch这些底层库时。如果你用的是Anaconda建议单独给Qlib建一个环境别跟你日常的Python环境混在一起conda create -n qlib_env python3.8 conda activate qlib_env pip install pyqlib这样隔离的好处是后面装深度学习相关的包比如PyTorch时不会影响你已有的项目环境。3. 数据初始化与基础数据操作最容易被忽略却最关键的一步3.1 为什么要单独做数据初始化Qlib最核心的设计理念之一是“数据与代码分离”。什么意思呢就是Qlib把数据文件、因子表达、模型训练、回测验证拆成了几个独立模块数据文件统一存放在某个目录下模型和策略代码通过统一的API去访问数据。这样做的好处显而易见数据只需要下载一次后面所有的研究都可以复用。而且Qlib自带了一套完整的数据处理流程包括日线数据、复权因子、行业分类等不需要自己去做复权计算和停牌过滤。数据初始化是最容易出问题的一步。我第一次跑的时候直接跳过初始化去执行示例代码结果报了一堆Data not found的错误。后来才知道Qlib的数据下载不是pip装完就自动有的必须手动执行初始化命令。3.2 数据下载和初始化的完整流程Qlib官方给了一个一键初始化的命令python scripts/get_data.py qlib_data这个命令会下载Qlib官方的示例数据集包括A股日线数据、指数数据等默认下载到~/.qlib/qlib_data/cn_data目录。因为数据量比较大有好几个GB下载时间取决于网速我当时挂了大概二十分钟才下完。如果你网速不好或者下载总是中断可以去Qlib官方提供的云端数据地址手动下载然后把压缩包解压到目标目录。手动下载的方式也简单先创建数据目录比如C:/Users/your_name/.qlib/qlib_data/cn_dataWindows路径或者~/.qlib/qlib_data/cn_dataLinux/macOS路径把下载好的数据包解压到这个目录下之后在代码里初始化Qlib时指向这个目录就行。3.3 Qlib初始化三步走数据准备好之后代码里初始化的标准姿势是这样的import qlib from qlib.config import REG_CN # 初始化Qlib指定数据存储路径和地区市场 provider_uri ~/.qlib/qlib_data/cn_data qlib.init(provider_uriprovider_uri, regionREG_CN)这段代码是跑Qlib所有功能的前置条件相当于告诉Qlib“我的数据在哪、我研究的是哪个市场”。REG_CN表示中国市场数据文件里包含了中国A股市场和指数数据。如果研究美股可以换成REG_US但需要单独下载对应的数据包。初始化完成之后就可以用Qlib的数据接口拉取数据了。我用得最多的是D.calendar交易日历、D.instruments股票池和D.features特征数据这三个接口。3.4 实际拉数据从交易日历到个股特征获取交易日历很简单from qlib.data import D # 获取2020年到2023年的所有交易日 calendar D.calendar(start_time2020-01-01, end_time2023-12-31) print(calendar[:5])获取股票池和特征数据是这样的# 获取沪深300成分股 instruments D.instruments(marketcsi300) # 获取某只股票在某段时间内的特征 df D.features( instruments[SH600519], # 贵州茅台 fields[$open, $high, $low, $close, $volume, $factor], start_time2022-01-01, end_time2022-12-31, freqday )这里的fields参数需要特别说一下。Qlib里的字段命名有一套规则以$开头的是原始行情字段比如$open、$close、$volume不以$开头的是通过表达式计算出来的因子字段比如Ref($close, 1)表示前一天的收盘价Mean($close, 5)表示5日收盘价均值。这套表达式系统是Qlib很强大的功能之一后面我会专门讲。4. 因子表达与Alpha挖掘Qlib的表达式引擎到底怎么用4.1 从get_data到expression engine很多新手在用Qlib时会被它的表达式引擎搞懵。因为Qlib的因子处理方式和普通的数据处理不一样它不只是给你一个DataFrame然后你用pandas去算而是提供了一套声明式的因子表达式系统。打个比方如果用pandas算因子相当于你自己动手洗菜切菜如果用Qlib的表达式引擎相当于你把菜谱告诉厨师厨师帮你把菜做好端上来。我一开始也觉得这套系统有点多余直接用pandas不香吗但真正用起来才发现这套表达式系统有几个不可替代的优势惰性计算表达式不会立刻计算而是在你真正调用时才执行极大节省了内存自动对齐天然处理了停牌、缺失值、不同股票不同交易日的问题统一接口训练集、验证集、测试集的特征提取逻辑完全一致不会出现数据泄露4.2 常用表达式操作符详解Qlib的表达式引擎支持的操作符非常多我从常用到冷门整理一下基础数值运算# 收益率当天收盘价 / 前5天收盘价 - 1 rich Ref($close, 5) # 5天前的收盘价 ret_5 $close / rich - 1跨截面运算# 某只股票在整个市场中排名的变化这是典型的横截面因子 rank_value Rank(Mean($close, 5))时序滚动运算# 20日收益率标准差波动率因子 vol Std(Ref($close, 1) / $close - 1, 20) # 20日均线偏离度 deviation $close / Mean($close, 20) - 1条件运算和逻辑运算# 通过条件筛选构造虚拟变量 cond If(Greater($close, $open), 1, 0)我还记得第一次看到这些表达式时感觉系统特别庞大但实际用下来发现核心常用的就那么十几个。关键是理解几类操作取值类Ref、Mean、Std、Sum、截面类Rank、Quantile、逻辑类If、Greater、Less和时间窗口类Rolling、Expanding。4.3 自定义因子把pandas逻辑转成表达式总有场景是内置表达式满足不了的这时候有两种方案一是用Qlib的Expression类自定义算子二是直接把pandas处理好的因子注册成新字段。我用得比较多的是第二种方案from qlib.data.ops import register_expression # 自定义一个量价背离因子 # 思路价格创新高但成交量没有同步创新高时该因子值较大 expression $close / Max($close, 60) - $volume / Max($volume, 60) registered_name register_expression(volume_price_divergence, expression)这里有个小技巧Max($close, 60)表示60日最高价$volume / Max($volume, 60)表示当前成交量在60日最高成交量中的占比。两者相减就得到了一个简单的量价背离因子值越大说明价格位置高但量能不足可能是顶部信号。注册完之后后面的D.features里就可以用这个因子了df D.features( instrumentsinstruments, fields[$close, volume_price_divergence], start_time2022-01-01, end_time2022-12-31 )4.4 因子预处理去极值、标准化、中性化实际研究中裸因子是不能直接喂给模型的必须经过预处理。Qlib在qlib.contrib.data.handler里提供了现成的预处理流程主要包括去极值用MAD中位数绝对偏差或者分位数法把极端值截断避免个别极端值主导模型训练标准化Z-Score标准化让不同量纲的因子可比较中性化去掉行业和市值的影响让因子体现纯粹的alpha信息在Qlib里通过配置Alpha158或Alpha360这些内置的handler就可以自动完成这些操作。以Alpha158为例from qlib.contrib.data.handler import Alpha158 handler_conf { start_time: 2020-01-01, end_time: 2023-12-31, fit_start_time: 2020-01-01, fit_end_time: 2021-12-31, instruments: csi300, process_type: qlib.contrib.data.handler.Alpha158, }Alpha158是Qlib内置的一个因子集包含158个技术指标类因子从均值、标准差、偏度到各种形态学特征都有覆盖。还有一个Alpha360是360维的因子信息粒度更细但计算量也更大。5. 模型训练全流程从LightGBM到深度学习5.1 初始化数据集Dataset和Handler的关系Qlib的数据流设计值得花一点时间理解。核心思想可以概括为Handler负责定义数据怎么处理Dataset负责把数据处理成模型需要的格式模型从Dataset里取数据训练。我一开始总搞混这两个概念。后来自己画了个图才明白Handler像是原材料处理车间从原始行情数据中算出因子、做预处理Dataset像是仓库管理员按照指定的时间窗口和数据格式把处理好的数据按需提供给模型。实际代码看这个就清楚了from qlib.data.dataset import Dataset from qlib.data.dataset.handler import DataHandlerLP # 配置handler handler DataHandlerLP( instrumentscsi300, start_time2020-01-01, end_time2023-12-31, infer_process_typeqlib.contrib.data.handler.Alpha158, process_typeqlib.contrib.data.handler.Alpha158, process_args{ scale: True, # 是否做标准化 robust: True, # 是否做去极值 zscore: True, # 是否用z-score标准化 } ) # 配置dataset dataset Dataset(handler) # 加载训练、验证、测试数据 train_data dataset.prepare(train, col_setfeature, data_keyDataHandlerLP.DK_I) valid_data dataset.prepare(valid, col_setfeature, data_keyDataHandlerLP.DK_I) test_data dataset.prepare(test, col_setfeature, data_keyDataHandlerLP.DK_I)这里有个坑我必须要提醒process_args里的scale、robust、zscore这三个参数不是随便都能同时开的。scale和zscore本质上都是标准化同时开会导致重复处理。我建议只开zscore做标准化robust做去极值scale保持默认False。5.2 训练LightGBM最快上手的baselineQlib自带了一个LightGBM模型封装训练代码非常简洁from qlib.contrib.model.gbdt import LGBModel from qlib.contrib.evaluate import backtest_daily from qlib.utils import init_instance_by_config model_conf { class: LGBModel, module_path: qlib.contrib.model.gbdt, kwargs: { loss: mse, colsample_bytree: 0.8, learning_rate: 0.01, subsample: 0.8, lambda_l1: 1.0, lambda_l2: 1.0, max_depth: 7, num_leaves: 32, num_threads: 20, verbosity: -1 } } model init_instance_by_config(model_conf) model.fit(dataset)这里的loss参数值得说一下。默认mse是回归损失直接预测未来的收益率。还有一个常用选择是binary二分类预测未来上涨还是下跌。我自己做研究时一般先用mse看全貌再试binary对比。训练过程很快如果是沪深300的数据Alpha158因子集大概三到五分钟就能跑完一个LightGBM模型。相比后面的深度学习模型这速度简直是飞起。5.3 训练深度学习模型PyTorch的接入Qlib的深度学习模型封装在qlib.contrib.model.pytorch和qlib.contrib.model.pytorch_nn里。我常用的是LSTM和Transformer两种结构。以LSTM为例from qlib.contrib.model.pytorch_lstm import LSTMModel pytorch_conf { class: LSTMModel, module_path: qlib.contrib.model.pytorch_lstm, kwargs: { d_feat: 158, # 因子维度与Alpha158的因子数一致 hidden_size: 64, num_layers: 2, dropout: 0.1, n_epochs: 100, batch_size: 1024, early_stop: 20, loss: mse, lr: 0.001, optimizer: adam } } model init_instance_by_config(pytorch_conf) model.fit(dataset)这里最关键的参数是d_feat它必须和handler输出的因子维度一致。如果你用的是Alpha158因子维度是158如果用Alpha360就是360。如果设置不匹配模型会直接报错。early_stop是提前停止的轮次设为20表示20个epoch内验证集loss没有改善就停止训练。这个参数对深度学习模型特别重要能有效防止过拟合。提示如果你是第一次跑深度学习模型建议先设置n_epochs10试跑一下确认整个流程能跑通再加大训练轮数。不然跑了大半天最后发现数据维度配错了非常浪费时间。5.4 模型训练中容易被忽视的细节有几个细节虽然不起眼但真的会影响最终效果我单独拎出来说一下。第一个是数据集的时间切分。Qlib按照start_time、end_time、fit_start_time、fit_end_time几个参数来划分训练集、验证集和测试集。默认的时间切分逻辑是训练集从start_time到fit_end_time验证集从fit_end_time到end_time的一部分测试集是最后一段。如果你发现自己的训练集和验证集有重叠大概率是这几个时间参数没配对。第二个是模型是否使用GPU。Qlib的深度学习模型默认优先使用GPU如果你装了CUDA版PyTorch。在CPU上跑LSTM会非常慢尤其是数据量大时。如果你只想快速跑通流程可以先在CPU上用LightGBM验证确认无误后再换深度学习模型上GPU。第三个是随机种子。Qlib的训练代码里默认设置了随机种子但如果你自己写深度学习代码一定要手动设置torch.manual_seed(0)、np.random.seed(0)、random.seed(0)。 不然每次训练的结果都会有差异后续做策略对比的时候容易分不清是模型真实的差异还是随机波动。6. 回测和绩效分析结果到底怎么看、怎么解读6.1 内置回测框架一行代码跑完整回测Qlib封装了backtest_daily函数可以直接对预测结果做日频回测from qlib.contrib.evaluate import backtest_daily # 使用模型对test数据做预测 pred model.predict(dataset) # 回测参数配置 backtest_conf { start_time: 2022-01-01, end_time: 2023-12-31, account: 10000000, # 初始资金1000万 benchmark: SH000300, # 基准指数沪深300 exchange_kwargs: { limit_threshold: 0.1, # 涨跌停限制 deal_price: close, # 以收盘价成交 open_cost: 0.0005, # 开仓手续费 close_cost: 0.0015, # 平仓手续费 min_cost: 5, # 最低手续费 }, } portfolio_metric, indicator backtest_daily(pred, **backtest_conf)跑完之后indicator里包含了一堆绩效指标年化收益率、夏普比率、最大回撤、信息比率、胜率等等。我把比较关键的几个指标解释一下年化收益率annualized_return策略一年的平均收益率最简单的收益指标夏普比率sharpe单位风险带来的超额收益越高越好一般超过1就算不错最大回撤max_drawdown净值从高点回落到低点的最大幅度越小越好信息比率information_ratio衡量策略相对于基准的超额收益稳定性说句实话第一次跑回测看到高收益的时候我心里是有点打鼓的。后来我才意识到回测结果好看不一定代表策略真的赚钱因为回测里有很多隐性的优化空间。最常见的坑是前视偏差——不小心用到了未来数据。6.2 回测模块内部干了什么如果你用过其他量化平台的回测会发现Qlib的backtest_daily极其简洁但简洁背后其实做了不少事情把预测结果按时间排序每天选出得分最高的topk只股票默认10只每个调仓日对持仓做调整卖出不再在topk里的股票买入新进入topk的股票按你设置的手续费、涨跌停限制模拟实际成交以基准指数为参照计算每日的仓位净值和超额净值这个流程和真实交易非常接近但有一个重要区别Qlib默认假设成交价是当天收盘价。实际上我们不可能在收盘价精确成交尤其是当调仓资金量比较大的时候冲击成本不可忽视。所以我在实际使用中一般会设置一个比真实成本更高的手续费率来保守估计。6.3 绩效分析进阶风险分解和多周期对比只看整体指标不够我习惯用PortfolioAnalysis做更细致的分析from qlib.contrib.evaluate import risk_analysis report_normal, positions_normal backtest_daily(pred, **backtest_conf) analysis risk_analysis(report_normal, freqday) print(analysis)risk_analysis会输出按日度、周度、月度分别计算的多个指标可以直观地看到策略在不同时间尺度下的表现。特别是月度的最大回撤分布能帮你判断策略是否存在某些月份集中亏钱的问题。如果策略不止一个还可以把它们放在一起比较。我常用的做法是画累计收益曲线和回撤曲线做策略间的横向对比。Qlib提供了plot功能但默认图表样式比较简单我一般把report输出到本地再用Excel或Python画图。6.4 警惕回测陷阱我踩过的三个坑回测这一块我真的踩了不少坑有几个教训分享出来希望你别再踩一次。第一个坑因子值泄露。用Alpha158时如果因子定义里刚好包含了未来信息回测结果会好得离谱。比如某些因子用到了收益率而收益率本就是用未来价格算出来的。这种因子在样本内回测时表现非常好实盘直接扑街。建议在因子定义时仔细检查每一个用到的原始字段确保都是当前时点可得的数据。第二个坑调仓频率和手续费设置。很多新手喜欢高频调仓比如每天结果手续费把收益全部吃掉了。我用backtest_daily做日频调仓时手续费率设的是万二开仓、万五平仓即便如此一年下来手续费对净值的影响也能达到几个百分点。建议在不同手续费率下做敏感性分析看策略对交易成本的依赖程度。第三个坑基准选择。用csi300的因子信号去回测时基准却用SH000300沪深300指数。这两个虽然有关联但并不是一回事。因子信号对应的股票池是沪深300成分股基准指数也是沪深300这样对比才算公平。如果股票池是中小票基准就别用沪深300了用中证500或者中证1000更合理。7. 实战案例用Alpha158 LightGBM跑一个完整策略7.1 策略思路说明为了把前面讲的内容串起来我写了一个完整的实战案例。策略思路很简单使用Alpha158因子集结合LightGBM模型预测未来5天的收益率然后每天换仓选择预测收益最高的10只股票。这个策略不追求多大的超额收益但它完整走通了Qlib的全部流程适合作为练手和基线策略。7.2 完整代码import qlib from qlib.config import REG_CN from qlib.data.dataset import Dataset from qlib.data.dataset.handler import DataHandlerLP from qlib.contrib.data.handler import Alpha158 from qlib.contrib.model.gbdt import LGBModel from qlib.contrib.evaluate import backtest_daily, risk_analysis from qlib.utils import init_instance_by_config import numpy as np import warnings warnings.filterwarnings(ignore) # 1. 初始化Qlib provider_uri ~/.qlib/qlib_data/cn_data qlib.init(provider_uriprovider_uri, regionREG_CN) # 2. 配置handler handler_conf { start_time: 2018-01-01, end_time: 2023-12-31, fit_start_time: 2018-01-01, fit_end_time: 2021-12-31, instruments: csi300, } handler DataHandlerLP( instrumentscsi300, start_timehandler_conf[start_time], end_timehandler_conf[end_time], infer_process_typeqlib.contrib.data.handler.Alpha158, process_typeqlib.contrib.data.handler.Alpha158, process_args{ norm_ts: True, robust: True, zscore: True, maxdays: 80, } ) # 3. 配置dataset dataset Dataset(handler) # 4. 配置LightGBM模型 lgb_conf { class: LGBModel, module_path: qlib.contrib.model.gbdt, kwargs: { loss: mse, colsample_bytree: 0.8, learning_rate: 0.01, subsample: 0.8, lambda_l1: 1.0, lambda_l2: 1.0, max_depth: 7, num_leaves: 32, num_threads: 20, verbosity: -1 } } model init_instance_by_config(lgb_conf) model.fit(dataset) # 5. 预测 pred model.predict(dataset) # 6. 回测 backtest_conf { start_time: 2022-01-01, end_time: 2023-12-31, account: 10000000, benchmark: SH000300, exchange_kwargs: { limit_threshold: 0.1, deal_price: close, open_cost: 0.0005, close_cost: 0.0015, min_cost: 5, }, } portfolio_metric, indicator backtest_daily(pred, **backtest_conf) analysis risk_analysis(portfolio_metric, freqday) print(analysis)7.3 运行结果解读这段代码跑完之后我得到的指标大概是这样的不同版本和随机种子下会有些差异指标数值年化收益率约 8% ~ 15%夏普比率0.8 ~ 1.2最大回撤约 -12% ~ -18%信息比率0.6 ~ 1.0说实话这个策略不会让你一夜暴富它的意义在于验证了Qlib整个链路是通的。沪深300这种偏大盘的股票池本身波动就相对小想靠简单的技术指标因子跑出超额收益并不容易。但如果你把股票池换成csi500或者csi1000再加上更丰富的因子比如基本面因子、情绪因子效果会有明显提升。7.4 可视化结果Qlib提供了一些简单的绘图功能我用得最多的是累计收益曲线。在Jupyter Notebook里跑import matplotlib.pyplot as plt # 累计收益曲线 portfolio_metric[cum_return].plot() plt.title(Strategy Cumulative Return) plt.show() # 回撤曲线 portfolio_metric[return] portfolio_metric[return] cum_return (1 portfolio_metric[return]).cumprod() drawdown cum_return / cum_return.cummax() - 1 drawdown.plot() plt.title(Strategy Drawdown) plt.show()这里有一个小技巧回撤曲线可以直接用累计收益序列算出来不需要额外存数据。cum_return / cum_return.cummax() - 1就是每个时间点相对历史最高点的回撤比例。8. 进阶技巧如何让Qlib真正服务于你的研究8.1 自定义因子和Handler扩展默认的Alpha158虽然好用但在实际研究中往往不够。好消息是Qlib的扩展机制做得很灵活。自定义Handler有几种方式直接修改Alpha158源码添加自己的因子继承Alpha158重写get_feature_config方法从零实现一个Handler继承DataHandlerLP我推荐第二种灵活性最高代码量也少。下面是一个简单示例from qlib.contrib.data.handler import Alpha158 from qlib.data.dataset.handler import DataHandlerLP class MyHandler(Alpha158): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def get_feature_config(self): config super().get_feature_config() # 添加一个自定义因子5日涨跌幅 config[custom] [Ref($close, 5) / $close - 1] config[new_fields] config.get(custom, []) return config当然这种方式需要你对Qlib的内部实现有一定了解不太适合第一遍上手。建议先跑通默认流程再试着扩展。8.2 分批训练和滚动训练金融市场的数据是不断产生的静态地训练一次模型然后一直用效果会逐渐衰减。比较常见的做法是滚动训练每隔一段时间比如一个月用最新的数据重新训练一次模型。Qlib本身没有内置滚动训练的完整封装但我们可以用Python自己写循环。核心思路是在每个训练窗口内重新初始化handler和dataset然后重新训练模型。import pandas as pd train_windows [ (2018-01-01, 2021-12-31, 2022-01-01, 2022-06-30), (2018-06-01, 2022-06-30, 2022-07-01, 2022-12-31), (2019-01-01, 2022-12-31, 2023-01-01, 2023-12-31), ] for train_start, train_end, pred_start, pred_end in train_windows: # 重新初始化handler和dataset handler DataHandlerLP( instrumentscsi300, start_timetrain_start, end_timetrain_end, process_typeqlib.contrib.data.handler.Alpha158, process_args{robust: True, zscore: True} ) dataset Dataset(handler) # 重新训练模型 model init_instance_by_config(lgb_conf) model.fit(dataset) # 预测test区间 test_data dataset.prepare(test) pred model.predict(dataset) # 这里可以保存pred随后合并做回测这个思路我在实盘验证中非常常用。虽然每次重新训练会多花一点时间但模型对最新市场状态的适应能力会明显提升。8.3 多模型集成单一模型的预测结果往往不够稳定尤其是LightGBM这类树模型对不同市场状态的适应能力有限。一个比较实用的技巧是多模型集成同时训练LightGBM、LSTM、甚至Transformer然后把它们的预测结果做加权平均。Qlib在官方示例里提供了类似的做法我自己实现的时候用了一个很简单的方案# 假设model1, model2, model3是三个已训练好的模型 pred1 model1.predict(dataset) pred2 model2.predict(dataset) pred3 model3.predict(dataset) # 等权加权 pred_ensemble (pred1 pred2 pred3) / 3 # 用集成后的预测结果做回测 portfolio_metric, indicator backtest_daily(pred_ensemble, **backtest_conf)集成策略的收益不一定比单一模型高很多但回撤和波动通常会小一些稳定性更好。我之前用LightGBMRNN集成夏普比率从单一模型的1.0提升到了1.2左右最大回撤从-15%收窄到了-11%。值得一试。8.4 实盘前还要做的事回测效果好不代表可以直接实盘。以我的经验实盘前至少还要做三件事成本敏感性分析把手续费、滑点设置成基准的2倍、3倍看策略是否还能盈利。如果提高成本后策略收益大幅缩水说明策略本质上是靠交易频率吃饭实盘中很可能被成本吞噬。样本外测试不要反复调整参数去拟合测试集。可以把时间序列切分得更细比如训练集、验证集、测试集、样本外集四个时段互不重叠用验证集调参用测试集做一次验证最后用样本外集做最终确认。模拟盘运行如果条件允许用模拟交易环境跑至少一个月观察实际成交和回测假设之间有没有明显差异比如涨停买不进去、跌停卖不掉等。9. 常见报错和排查记录这些问题我都替你踩过了9.1 ImportError 或版本冲突报错信息ImportError: cannot import name Qlib from ...原因这个报错最常出现在刚安装完Qlib后Python环境路径不对或者Qlib依赖的某些C扩展没有编译成功。解决办法先确认当前Python解释器是不是你安装Qlib的那个环境。如果环境没问题试着重新安装Qlib并强制重建C扩展pip uninstall pyqlib -y pip install pyqlib --no-cache-dir如果还不行检查一下是否有旧版本的Qlib残留。我遇到过pip把Qlib装到了site-packages但代码里import到了用户目录下的旧代码排查起来很费劲。建议用pip show pyqlib查看安装路径是否和python的系统路径一致。9.2 数据文件不存在报错信息FileNotFoundError: data not found: ...原因99%是因为没有初始化数据或者qlib.init()里的provider_uri指向了错误路径。解决办法确认数据是否存在ls ~/.qlib/qlib_data/cn_data确认代码里的路径是否为绝对路径或正确展开的~路径Windows下~不会被自动展开建议直接用绝对路径provider_uri C:/Users/your_name/.qlib/qlib_data/cn_data9.3 因子维度不匹配报错信息AssertionError: d_feat should be equal to the dimension of features原因深度学习模型的d_feat参数设置和handler输出的因子维度不一致。解决办法# 在配置模型前先打印出数据集的特征维度 sample dataset.prepare(train, col_setfeature, data_keyDataHandlerLP.DK_I) print(sample.shape) # 比如输出 (120000, 158)则d_feat应设为1589.4 内存不足报错信息MemoryError或者训练过程直接被系统杀掉原因Alpha158或者Alpha360因子集会在内存中展开大量数据尤其是当股票池大、时间跨度长的时候。解决办法用时间分段加载数据或者只加载需要的因子列。我踩过一次坑用全部A股数据跑Alpha360直接占了60GB内存笔记本直接死机。后来改成用沪深300只用2年数据内存占用就降到4GB左右了。9.5 回测结果全是NaN报错信息回测的指标全是NaN比如annualized_return: nan原因预测结果和回测时间区间没有重叠。比如模型是用2020-2022年训练的但回测区间设在了2019年预测数据在回测区间内为空。解决办法检查model.predict(dataset)的索引是否有数据或者把回测start_time和end_time设置为预测数据覆盖的时间范围。9.6 快速排查工具用日志定位问题Qlib默认的日志输出比较简洁。遇到奇怪的问题可以临时把日志级别调到DEBUGimport logging logging.basicConfig(levellogging.DEBUG)然后再跑一遍流程Qlib会输出每一步处理的数据量、耗时和状态非常有助于定位问题。不过DEBUG模式输出量很大跑完记得调回INFO。10. 写在最后Qlib之外的一些建议Qlib给我的整体感受是功能强大但学习曲线有点陡。如果你只是想要一个快速跑通策略的工具Qlib不一定是最容易上手的但如果你想做严肃的量化研究它绝对值得花时间投入。说几个我自己的体会吧。首先别贪多。Qlib的功能太丰富了今天想试试这个模型明天想试试那个因子最后会发现啥都学了但啥都不精。我建议第一遍先只用一个数据handler、一个模型、一个回测流程把它彻底跑通、搞清楚每个环节怎么衔接然后再逐步增加复杂度。其次要敢于看源码。Qlib的GitHub仓库里有很多示例脚本和测试用例这些都是最好的学习资料。遇到文档里说不清楚的问题直接去读源码基本都能找到答案。我解决那两个维度不匹配和路径问题的思路都是从源码里翻出来的。最后还是要提醒一句——量化研究这件事技术只是工具策略逻辑才是核心。Qlib帮你省下来的时间应该花在思考更本质的问题上什么样的因子真的有预测力什么样的交易逻辑经得起市场检验这些才是决定策略长期能不能赚钱的关键。希望这篇记录能帮你少踩一些我踩过的坑。如果你在跑通Qlib的路上遇到其他问题欢迎回过头来对照这篇文章排查大概率都能找到答案。
返回列表