
用一行字符串定义量化因子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下午三点风控那边递来一句话把策略里那个 5 日动量窗口改成 15 日。听起来是个小改动可如果窗口值硬编码在 Python 逻辑里你要改代码、跑回归、重新走一遍上线流程。要是因子定义本身就是一段字符串、放在配置文件里这个改动就只是把 5 换成 15。Qlib 表达式引擎干的就是这件事让因子的定义形式脱离代码文件变成可直接读写、可批量生成的文本。它的定位很克制——不替你决定用什么因子只负责把你写下来的表达式忠实、快速地算出来。本文按源码实际行为梳理动手时可对照 qlib/data/ops.py 与 qlib/data/base.py每个算子在文件里都能找到同名类。一条因子表达式从输入到结果的完整生命周期本节先回答字符串凭什么能算数。结论先行表达式字符串本身不可执行引擎靠parse_field加 Python 运算符重载把它组装成一棵算子树再由数据层按标的和时间区间递归求值缓存层保证同一子表达式不重复计算。整个链路可以拆成五步阶段发生什么落点1. 字段解析$close这类写法被parse_field替换成Feature(close)qlib/utils/init.py2. 树构建eval求值时中缀运算符被重载成算子对象字段变成叶子节点得到一棵树qlib/data/base.py3. 区间扩展求值前引擎先问树最多要往回看多少期把查询起点前移滚动计算才不产生边界空洞get_extended_window_size4. 递归求值树自底向上每个节点执行load先用表达式字符串 标的 起止下标查内存缓存命中直接返回MemCache5. 落库取数表达式写进 Handler / Dataset 的fields训练与回测时自动走上面四步qlib/data/data.py几个容易混淆的点在这里说清。第一表达式里直接写 Python 运算符就行 - * / **对应加减乘除和幂比较符对应 Gt、Lt、Ge、Le、Eq、Ne逻辑运算请用、|、~写成 and / or / not 在 Python 语义下不会落到算子树上。第二常量可以直接参与运算比如Mean($volume, 20) - 1二元算子会自己区分表达式操作数和数值操作数。第三求值不是先算完再裁剪第 3 步的前移是防前视空洞的关键你只取后段数据但引擎已经帮你把前段历史垫好了。理解了这条链路后面挑算子就只是挑树上挂哪类节点的问题。算子体系先分四类再选具体函数本节给一张选型表。所有算子都挂在ExpressionOps基类下叶子节点是Feature对应 $ 前缀字段其余按输入个数 × 是否带窗口分成四类另有两件跨界的特殊算子。选算子时按输入形态定位类别再在下表里找具体函数即可。类别基类典型成员什么时候用一元无窗ElemOperatorLog、Abs、Sign对序列做逐点数值变换二元无窗PairOperatorAdd、Sub、Mul、Div、Power、比较与逻辑运算两个序列或常量混合运算单输入带窗RollingMean、Std、Rank、Delta、Ref 等时间序列统计Ref 名义上归此类行为是平移双输入带窗PairRollingCorr、Cov两序列的窗口相关性、协方差跨界件独立实现ChangeInstrument、If换标的取数、条件分支窗口算子里最常打的几个算子写法示例一句话用途MeanMean($close, 10)简单移动平均趋势基准线StdStd($ret, 20)波动率度量RankRank($close, 60)当前值在近 60 期里的百分位排名QuantileQuantile($close, 60, 0.8)注意有三个参数第三个才是分位点IdxMax / IdxMinIdxMax($volume, 10)窗口内极值出现的位置相对期数做量能拐点Slope / Rsquare / ResiSlope($close, 20)滚动回归三件套斜率、拟合度、残差WMA / EMAEMA($close, 0.1)加权移动平均EMA 的窗口参数是小数 alpha另外Ref虽然继承自Rolling但它是唯一的平移算子Ref($close, 3)就是三日前收盘价等价于 pandas 的shift(3)。跨资产场景用ChangeInstrument(SH000300, $close)它会忽略当前标的、直接去指定代码取数这是做相对强弱、Beta 类因子的入口条件分支用If(条件, 真值, 假值)三个参数都允许是标量内部走np.where。知道每个节点干什么之后剩下的悬念集中在窗口参数这一个数字上。窗口参数的三种写法与 Ref 的方向约定本节回答窗口 N 到底能填什么。答案同一个参数位承载了三种时间语义填错类型等于换了一种统计口径。窗口取值语义内部实现注意点整数 N ≥ 1固定窗口回看 N 期pandas rolling前 N−1 期样本不足但 min_periods1 照样给值0扩展窗口从序列起点算到当前pandas expanding回看深度为无穷引擎会打告警缓存区间不精确0 N 1指数加权N 即 alphapandas ewm衰减期按 (1−N)^L≈1e−6 估算取值越大越看重近期这里要当心两个细节。其一所有滚动算子统一min_periods1窗口没攒满也出数所以因子序列开头一段的数值天然偏小做截面比较时建议自行截掉头部。其二Ref的方向和很多人直觉相反N 0是回看过去N 0是取未来数据N 0取整段序列的起点值。回测里出现负 N 基本就是前视偏差务必全局搜一遍。参数语义清楚了接下来把这些知识变成一张哪里会踩坑的对照表。常见坑与调试三板斧本节把日常最贵的几类错误收敛成一张对照表再给出三个调试动作。坑后果规避方式误用扩展窗口 0回看无穷、缓存不精确、日志告警无特殊需求一律用整数窗口Ref 填了负 N前视偏差回测虚高全局检查回测只允许 N ≥ 0除法不加保护分母接近 0 时爆出极端值分母加平滑项如 1e-6Corr 遇到零方差窗口该处直接置 NaN属预期行为下游做缺失处理想写全局 min / max大小写冲突Min是小写语义的滚动最小值全局极值请出表达式后用 pandas 处理自定义算子与内置重名覆盖内置实现只有一条告警日志自定义算子起不重名的类名改了表达式还拿到旧结果缓存键含表达式字符串键变才重算但以为改了其实没改最易中招核对实际写入配置的那串字符调试三板斧按顺序做一是打印表达式树的字符串形式str(expr)输出与源码一致的写法用来核对嵌套层级和参数位置有没有对调二是查窗口边界调用get_extended_window_size()和get_longest_back_rolling()看回看深度是否符合预期三是确认缓存行为同一字符串、同标的、同区间只真算一次改数之前先确认自己不是在跟旧缓存对话。排错能力就位后就可以按同一套规则批量造因子了。从单因子到因子库一组可复用的表达式示例本节给一套改个数字就能用的因子写法覆盖动量、反转、波动、量价、条件分支、跨资产六类。因子表达式逻辑短期动量Mean($ret, 10)近 10 日平均日收益延续性中期反转-Mean($ret, 40)拉长到 40 日取反向均值回归波动压缩Std($ret, 5) / (Std($ret, 20) 1e-6)短窗波动对长窗波动的比值量价背离($close / $open - 1) / ($volume / Mean($volume, 20) - 1 1e-6)当日涨幅除以相对均量变化趋势三态If($close Mean($close, 50), 1, If($close Mean($close, 20), -1, 0))站上 50 日均线上看跌破 20 日均线下看其余中性相对强度Mean($ret, 40) / (Mean(ChangeInstrument(SH000300, $ret), 40) 1e-6) - 1个股 40 日收益相对大盘的超额组合建议相关性分块后各取一个代表这里给一套偏保守的配法——0.25 * Mean($ret, 10) - 0.25 * Mean($ret, 40) 0.3 * Std($ret, 5) / (Std($ret, 20) 1e-6) 0.2 * (相对强度)。合成之后跑一遍因子间相关矩阵任一对绝对值超过 0.6 就回去做正交化或换代表别让权重里藏两份同一个信息。落库方式只有一条路把表达式字符串写进 Handler 或 Dataset 的fields配置取数层会自动完成区间扩展与递归求值表达式层和训练流程就此打通。上线前的八项检查清单本节收个尾把散落在前文的风险点压成一张出门前用的清单。全部通过再进回测省下的不是几行代码是返工的时间。检查项通过标准窗口参数不超过训练集长度的 1/10且全部为正整数除非明确要扩展窗口Ref 方向全库无负 NN0 只在确实要取起点值时出现除法保护每个分母都带平滑项树形核对打印表达式树字符串与写下来的意图逐层比对边界告警日志中无扩展窗口、重名算子的告警头部截断截面比较前裁掉窗口预热段约 N−1 期缓存确认修改表达式后确认字符串确实变了再谈结果变化耗时基线首次计算耗时在预期量级二次同参请求走缓存八项都打勾之后因子库的日常迭代就只剩两个动作改字符串、跑回归。至于想再加内置之外的运算路子也留好了——继承ExpressionOps实现load与两个窗口方法再经OpsWrapper.register注册表达式里就能按类名直接调用。【免费下载链接】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),仅供参考