Qlib 表达式引擎:一行表达式定义任意量化因子

发布时间:2026/11/5 7:54:56

Qlib 表达式引擎:一行表达式定义任意量化因子 Qlib 表达式引擎一行表达式定义任意量化因子【免费下载链接】qlibQlib is an AI-oriented Quant investment platform that aims to use AI tech to empower Quant Research, from exploring ideas to implementing productions. Qlib supports diverse ML modeling paradigms, including supervised learning, market dynamics modeling, and RL, and is now equipped with https://github.com/microsoft/RD-Agent to automate RD process.项目地址: https://gitcode.com/GitHub_Trending/qli/qlib从改一个参数重跑全链路说起你调了 MA 的窗口从 20 到 30结果发现因子计算写死在数据加载脚本里改完得重新跑整个 pipeline 才能看效果再想加一个收盘价高于 20 日均线时取成交量否则取 0的因子又得手写一遍数据读取和对齐代码。Qlib 表达式引擎Expression Engine源码在qlib/data/ops.py解决的就是这件事因子用一行字符串或几行 Python 代码定义解析、取数、缓存全部交给引擎。30 秒全景图Qlib 表达式引擎是 Qlib 数据层之上的因子计算层把一行表达式字符串解析成运算符对象树递归加载原始行情并完成计算输出pd.Series或pd.DataFrame。你写的是算什么它管怎么算、算了没算过。图中左侧数据层提供原始行情表达式引擎是中间的计算通道右侧模型层拿到的已经是算好的因子矩阵。parse_field eval负责把字符串变成对象load负责从存储层递归取数H[f]是全局内存缓存——这三个节点的行为是后面机制拆解的重点。机制拆解表达式字符串如何变成运算符对象树Qlib 没有自研词法/语法分析器而是复用 Python 的eval。qlib/data/data.py中ExpressionProvider.get_expression_instance的核心逻辑就一句把Mean($close, 20)转成合法 Python 代码后直接 eval得到的就是运算符对象。# qlib/data/data.py 中 get_expression_instance 的核心步骤 expression eval(parse_field(field)) # -- parse_field 把 $close 替换为 # Operators.Feature(close)Mean 等函数名在 eval 环境中映射到 Operators 注册表 self.expression_instance_cache[field] expression # 同一字符串只解析一次运算符类通过注册表Operatorsqlib/data/ops.py底部的OpsWrapper实例集中管理register_all_ops启动时把OpsList里约 50 个内置运算符注册进去__getattr__按名字取类。设计动机是零解析成本加可扩展eval 一行代码搞定用户还能通过C.custom_ops或Operators.register注册自定义运算符。反例如果像多数因子库那样自研 AST 解析器每加一个运算符都要改解析器字符串与实现的对应关系也更难维护。运算符树如何被递归执行所有运算符继承自qlib/data/base.py的Expression执行入口是模板方法load具体计算留给子类的_load_internal叶子节点Feature写法是$close字符串化后带$前缀直接读存储层Mean、Ref等窗口运算符先load自己的子表达式再套窗口计算。load的统一实现还干了两件子操作员感知不到的事全局内存缓存和错误日志后面生产实践部分细讲。窗口运算符Rolling._load_internal是整个引擎里最值得读的一段它用 8 行覆盖了三种窗口语义# qlib/data/ops.py 中 Rolling._load_internal 的核心分支 if isinstance(self.N, int) and self.N 0: series getattr(series.expanding(min_periods1), self.func)() # N0: 扩展窗口 elif isinstance(self.N, float) and 0 self.N 1: series series.ewm(alphaself.N, min_periods1).mean() # 0N1: 指数加权 else: series getattr(series.rolling(self.N, min_periods1), self.func)() # N 为正整数N 的取值行为对应 pandas 语义正整数如20固定 20 期滚动窗口rolling(20)0扩展窗口从序列起点算到当前expanding(0, 1)小数如0.1指数加权alpha0.1ewm(alpha0.1)把三种语义塞进同一个N参数好处是因子表达式里窗口类型可插拔Mean($close, 0)就是上市以来均值不需要单独发明运算符。反例如果仿照某些因子库把扩展窗口做成独立的ExpandingMean运算符数量会成倍膨胀且表达式文本无法通过改一个参数切换行为。注意min_periods1窗口不满 N 期时照常出值基于已有数据计算所以序列头部不会有一串 NaN。这一点和你直接用 pandas 手写结果会有差异调试时容易误判。运算符重载为什么你能用加号写因子Expression基类重写了__add__、__sub__、__truediv__、__gt__等全套运算符$close / $open - 1在 Python 里直接得到Sub(Div(Feature(close), Feature(open)), 1)的对象树str()就能印回表达式文本。这意味着两种写法字符串和 Python 对象产出同一棵树可以在 Python 里自由拼接、传参、组合。反例如果只有字符串入口像用同一个 20 日均线算两个因子这种复用就只能靠字符串拼接极易写错。动手实验前置准备安装 Qlib 并下载示例数据只需一次。git clone https://gitcode.com/GitHub_Trending/qli/qlib pip install -e qlib python qlib/scripts/get_data.py qlib_data cn_data实验一最小可运行的因子目标验证表达式字符串能直接算出单只股票的因子序列。import qlib qlib.init(provider_uri~/.qlib/qlib_data/cn_data, regioncn) # 指定数据目录 from qlib.data import D s D.feature(SH600519, ($close-$open)/$open, start_time2020-01-01, end_time2020-01-31) print(s) print(s.count())预期输出具体数值取决于行情数据2020-01-02 -0.0062 2020-01-03 0.0124 ... Freq: D, Name: ($close-$open)/$open, dtype: float32 22值得注意index 是交易日历非自然日1 月只有 22 个交易日series 的 name 就是表达式本身后续批量取数时会成为列名。变体把表达式换成Mean($close, 5)前 5 个交易日就能出值min_periods1的效应且前 5 个值是基于 1~5 期的均值而非 pandas 默认的 NaN。实验二批量取多因子并组合滚动与条件目标上一步只取了单因子这一步用dataset一次取多列并叠加滚动窗口和If条件分支。import qlib qlib.init(provider_uri~/.qlib/qlib_data/cn_data, regioncn) from qlib.data import D fields [ Mean($close, 20), # 20 日均价 Std($return, 10), # 10 日收益波动率 If($close Mean($close, 20), $volume, 0), # 站上均线才计成交量 ] df D.dataset(SH600519, fields, start_time2020-01-01, end_time2020-06-30) print(df.columns.tolist()) print(df.tail(3)) print(df[If($close Mean($close, 20), $volume, 0)].eq(0).mean())预期输出示例数值[Mean($close, 20), Std($return, 10), If($close Mean($close, 20), $volume, 0)] Mean($close, 20) Std($return, 10) If(...) 2020-06-29 731.25 0.021 1.82e07 2020-06-30 735.80 0.019 0.00e00 0.41值得注意列名就是表达式原文最后一行约 0.41 是收盘价跌破 20 日均线的天数占比说明If的 false 分支0真实生效了。变体把Mean($close, 20)改成Mean($close, 0)得到的是从该股上市首日算起的均值——窗口参数切换行为表达式结构不变。实验三Python 对象式组合与跨资产引用目标展示运算符重载和ChangeInstrument叠加用 Python 代码拼因子树并引用指数数据计算相对强度。import qlib qlib.init(provider_uri~/.qlib/qlib_data/cn_data, regioncn) from qlib.data import D from qlib.data.ops import Operators close Operators.Feature(close) ret close / Operators.Ref(close, 1) - 1 # 重载直接构树 idx Operators.ChangeInstrument(SH000300, close) # 换到沪深300指数 alpha Operators.Mean(ret, 20) - Operators.Mean(idx / Operators.Ref(idx, 1) - 1, 20) print(alpha) # 运算符树文本化可回传给 D.feature s D.feature(SH600519, str(alpha), 2023-01-01, 2023-03-31) print(s.tail(3))预期输出树结构示意具体数值取决于行情Sub(Mean(Sub(Div($close,Ref($close,1)),1),20), Mean(Sub(Div(ChangeInstrument(SH000300,$close), Ref(ChangeInstrument(SH000300,$close),1)),1),20)) 2023-03-29 0.0312 2023-03-30 -0.0045 2023-03-31 0.0108值得注意print(alpha)输出的文本可以直接作为字符串表达式回传D.feature两种写法闭环互通ChangeInstrument内部会忽略当前股票参数、改用指定标的取数这是相对强度类因子的关键。生产级实践当子表达式被重复使用时引擎的性能关键在Expression.load的缓存设计——缓存逻辑集中在基类所有运算符免费共享# 摘自 qlib/data/base.py 的 Expression.load cache_key str(self), instrument, start_index, end_index, *args if cache_key in H[f]: return H[f][cache_key] # -- 同文本子表达式命中内存缓存零重算 series self._load_internal(instrument, start_index, end_index, *args) series.name str(self) H[f][cache_key] series # -- 写回全局缓存供兄弟节点复用错误写法是把因子拆成多次D.feature调用、每次传不同时间范围(close-MA)/MA里 MA 只会在同一 instrument 且同一 index 范围的请求间复用范围一变就全部重算。正确写法是像实验二那样把多个表达式放进一次dataset调用对批量场景D.dataset默认还有disk_cache1的磁盘缓存DiskDatasetCache源码在qlib/data/cache.py相同标的池、字段、时间窗的二次请求直接读盘。一句话缓存键是表达式文本 标的 索引范围三元组三者保持一致才能命中。当窗口参数 N 取 0 或负数时Mean($close, 0)扩展窗口能跑但get_extended_window_size里明确标注了FIXME扩展窗口的边界数据不会精确计算源码原话 The Rolling(ATTR, 0) will not be accurately calculated涉及它的表达式在边界日期附近可能有偏差。正确做法需要全历史统计时用扩展窗口但在回测起始前预留充分数据或对精度敏感的场景改用足够大的固定窗口。Ref($close, -1)是另一个危险项Ref支持负 N 取未来数据用于研究没问题进生产就是前视偏差。get_longest_back_rolling的文档也明说Ref(Ref($close, -1), 1)这类嵌套无法正确处理。生产流水线里建议直接把负 N 视为 bug。如何确认你的表达式真的算对了import qlib qlib.init(provider_uri~/.qlib/qlib_data/cn_data, regioncn) from qlib.data import D from qlib.data.utils import parse_field field Mean($close, 20) - Ref($close, 1) print(eval(parse_field(field))) # 校验解析出的运算符树是否符合意图 s D.feature(SH600519, field, 2023-01-01, 2023-06-30) close D.feature(SH600519, $close, 2022-11-01, 2023-06-30) expect close.rolling(20, min_periods1).mean() - close.shift(1) # pandas 手算 print((s - expect.loc[s.index]).abs().max()) # 应为 0 或浮点精度量级三步定位法先print解析结果确认树没解析错比如括号位置再用 pandas 对单只股票手算对照注意对齐min_periods1这个细节最后怀疑性能问题时开 DEBUG 日志——load出错时会通过get_module_logger(data)打出 instrument、表达式、索引范围和异常详情这是排查某个标的数据缺了的第一现场。表达式算出的因子序列最终会进入 IC/收益分析环节上图为 Qlib 报告模块的 IC 分析输出样式如果数值和手算对不上问题一定在表达式或数据而不是评估环节。边界与取舍只做单标的的时间序列运算表达式引擎的操作单位是一只标的一列时间序列。横截面运算全市场 zscore、横截面 rank不在此列Rank($close, 20)是滚动窗口内的排名不是横截面排名。横截面标准化请用DataHandlerProcessor源码qlib/contrib/data/handler.py的ZScoreNorm、RobustZScoreNorm等两层各司其职。自定义运算符有明确成本写一个运算符需要继承ExpressionOps实现_load_internal、get_longest_back_rolling、get_extended_window_size三个抽象方法再经C.custom_ops或Operators.register注册注意同名会覆盖内置运算符并打 warning。如果你只是想调一个现成 Python 函数如 scipy 的某统计量更省事的路线是在dataset外面用 pandas/pipeline 处理而不是塞进表达式。已知不精确的场景源码自己承认两处扩展窗口N0边界不精确、负 N 嵌套的get_longest_back_rolling计算错误见前文。如果你的场景依赖精确的上市以来统计或未来函数组合建议绕开表达式引擎直接用存储层数据 pandas 实现。延伸与下一步通读运算符实现qlib/data/ops.py重点看Rolling和OpsWrapper两个类数据层完整文档表达式、DataHandler、PIT 数据docs/component/data.rst进阶点时数据库PIT运算符P/PRef与$$前缀字段见qlib/data/pit.py自定义运算符踩坑可直接在项目 Issues 区讨论速查附录名称语法一句话说明Feature$close叶子节点读原始行情字段RefRef($close, 1)取 N 期前数据负 N 为未来数据Mean / Sum / StdMean($close, 20)滚动均值 / 求和 / 标准差EMA / WMAEMA($close, 0.1)指数加权 / 加权移动平均Max / Min / Med / Rank / QuantileMax($high, 5)滚动极值、中位数、排名、分位数Slope / Rsquare / ResiSlope($close, 20)滚动回归斜率、拟合度、残差Corr / CovCorr($return, $idxret, 20)双序列滚动相关 / 协方差IdxMax / IdxMinIdxMax($volume, 10)窗口内最大 / 最小值的位置DeltaDelta($close, 5)当期与 N 期前之差IfIf(cond, a, b)按条件逐行取 a 或 bChangeInstrumentChangeInstrument(SH000300, $close)换用指定标的计算子表达式Log / Abs / Sign / PowerLog($close)逐元素一元运算MaskMask($close, SH000300)只保留指定标的的数据加减乘除$close / $open - 1运算符重载直接构树你现在可以打开qlib/data/ops.py对照本文验证每个运算符的窗口语义并用实验二的模板替换表达式跑通自己的第一个因子。【免费下载链接】qlibQlib is an AI-oriented Quant investment platform that aims to use AI tech to empower Quant Research, from exploring ideas to implementing productions. Qlib supports diverse ML modeling paradigms, including supervised learning, market dynamics modeling, and RL, and is now equipped with https://github.com/microsoft/RD-Agent to automate RD process.项目地址: https://gitcode.com/GitHub_Trending/qli/qlib创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/11/2 5:54:12

基于OpenCV+QT的啤酒瓶口缺陷检测系统实现

简介:一套面向啤酒瓶口缺陷检测的C工程源码,采用OpenCV视觉库和Qt界面框架实现,适合有图像处理基础或希望入门工业检测的开发者学习使用。算法流程完整覆盖灰度化、高斯滤波、自适应阈值、数学形态学处理、连通区域查找、最大轮廓筛选&#x…

2026/10/29 13:41:57

STM32循迹避障小车完整实战:从硬件搭建到调参避坑

简介:基于STM32F103C8T6的循迹避障小车完整工程源码包,面向嵌入式入门学习者与电子设计竞赛备赛学生,能够帮助理解循迹、避障两种工作模式的代码组织与实时切换思路。压缩包共292个文件、约7.73MB,内部除.c/.h源码外,还…

2026/10/29 16:45:33

mpv 播放器快速上手:源码编译到脚本扩展的 6 步实操教程

mpv 播放器快速上手:源码编译到脚本扩展的 6 步实操教程 【免费下载链接】mpv 🎥 Command line media player 项目地址: https://gitcode.com/GitHub_Trending/mp/mpv 昨晚想在终端里直接播一个 .mkv,懒得开图形界面,顺手敲…

2026/11/5 11:34:12

Simulink S-function执行时序与状态管理原理详解

简介:本资源是面向MATLAB/Simulink初学者与控制系统建模学习者的S-Function入门实践包,聚焦自定义系统函数开发这一核心能力,解决标准库无法覆盖的特定数学运算与动态逻辑建模需求。压缩包共4个文件(2个M文件实现核心算法逻辑&…

2026/11/5 11:34:12

低空经济三维智能感知与实时调度技术解析

1. 低空经济三维空间智能感知与管控体系概述低空经济作为新兴经济形态,正在全球范围内快速发展。根据国际航空运输协会(IATA)预测,到2030年,全球城市空中交通市场规模将达到1.5万亿美元。在这一背景下,《低…

2026/11/5 11:34:12

链表数据结构与PTA解题技巧详解

1. 链表基础与数据结构课程实践 链表作为线性表的链式存储结构,是数据结构课程中承前启后的关键内容。在山东理工大学(SDUT)的数据结构与算法课程体系中,链表章节通常安排在顺序表之后、树结构之前,这种教学顺序设计体…

2026/11/5 11:34:12

Simulink S-Function核心机制与两个经典C示例解析

简介:本资源是面向MATLAB/Simulink初学者与控制系统建模学习者的S-Function入门实践包,聚焦自定义系统函数开发这一核心能力,解决标准库无法覆盖的特定数学运算与动态逻辑建模需求。压缩包共4个文件(2个M文件实现核心算法逻辑&…

2026/11/5 11:34:12

Mantle系统架构:云存储局部性与扩展性的突破

1. 项目概述:Mantle系统架构演进的技术突围在云存储技术高度成熟的今天,百度沧海存储团队完成的Mantle系统设计堪称一次教科书级别的技术突围。这个被SOSP25收录的存储系统创新案例,完美诠释了如何在看似饱和的技术领域实现突破——不是通过堆…

2026/11/5 11:29:11

STM32串口DMA发送RGB565图片:分包协议与CRC校验实践

简介:一份围绕STM32串口传输彩色图像的嵌入式实践资料包,面向学习摄像头采集、串口通信与上位机图像还原的开发者,也可用于课程设计或项目原型验证。压缩包共499个文件、约10.92MB,包含C/H源码、Keil工程与配置、hex/axf/map等编译…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑