
简介这是一份面向量化分析师、算法交易员及金融工程研究者的算子速查手册系统梳理了WorldQuant BRAIN平台在量化策略开发中常用的基础级与专家级算子。资源按算术运算、逻辑判断、时间序列分析、截面分析与向量变换等模块分类以函数表形式逐一说明每个算子的名称、所属级别、参数含义及典型量化应用场景例如通过ts_arg_max定位历史上极值出现的相对位置、借助kth_element按回望窗口填补缺失数据、利用协方差与延迟类算子研判资产联动与趋势延续性等可广泛用于因子构建、数据清理、特征工程、交易信号生成与回测验证。资源为1个PDF文档压缩包约94KB结构紧凑适合通读后快速建立完整算子框架也可在编写Alpha表达式时作为案头工具书随时翻阅检索。目前已有1908人学习下载对入门量化建模、优化策略稳定性与提升研究效率均有切实帮助。1. WorldQuant BRAIN 算子从因子表达式到可落地的量化研究工具WorldQuant BRAIN 平台的算子体系是很多做量化因子研究的人绕不开的一环。它本质上是一套在云端数据基础上封装好的函数库覆盖算术、逻辑、时间序列和向量运算四类操作配合 Python 公式环境可以快速把一条交易想法改写成可回测的因子表达式。用过的人大多知道BRAIN 的价值不在某一个算子有多强而在于它能用少量代码把「截面选股 时序信号」组合起来形成真正可提交的 Alpha 表达式。这篇文章将把四类算子逐个拆开讲清楚它们的输入输出、参数含义、以及实际研究中容易踩的坑。适合那些刚开始用 BRAIN 写因子、或者已经写过几十条表达式但总在回测结果上翻车的人。2. 先建立算子坐标系按维度与计算逻辑拆解 BRAIN 算子2.1 四类算子不是按难度划分而是按数据维度划分我一开始接触 BRAIN 时习惯把算子按「算术、逻辑、时序、向量」简单分类觉得时序算子最高级、算术算子最基础后来发现这个理解是错的。实际研究里BRAIN 的算子分类维度更重要——它决定你处理的是单股票的时间序列还是横截面上多只股票的截面数据或者是两者的组合。具体来说算术算子和逻辑算子对标量scalar或向量化后的逐元素数据做操作不跨股票也不跨时间。比如 add、subtract、greater_than 这些输入输出形状一致适合做因子融合或条件过滤。时间序列算子则在单只股票的时间轴上开窗口像 ts_mean、ts_delta、ts_rank 这类一定会涉及 lookback 窗口长度参数输出的是同一只股票在不同时间点上的统计值。向量算子作用于横截面也就是某个交易日全市场股票的数据比如 rank、winsorize、zscore它们把当日所有股票的取值做排序或标准化。这里有一个容易忽略的点BRAIN 里的向量算子窗口横截面处理时默认是在当前时点的截面上计算而不是在整个面板上混合计算。所以写表达式前先问自己一句「我这一步是在同一只股票的不同日期之间算还是在同一天的不同股票之间算」这个判断错了因子逻辑就全变了回测再漂亮也白搭。2.2 表达式最小单元一个算子输入什么输出什么任何一个 BRAIN 表达式本质上都是一棵以算子为节点的计算图。叶子节点通常是数据字段比如 close、open、volume、market_cap中间节点是各种算子根节点是最终输出的 alpha 值。BRAIN 的编译环境会对整棵树做类型检查如果某个算子的输入维度和数据类型不对直接报错不会给你模糊的提示。算子类别典型算子输入维度输出维度常见用途算术add, subtract, multiply, divide标量或同形状序列与输入一致因子合成、价量结合逻辑greater_than, less_than, and, or标量或布尔序列布尔或 0/1 数值条件过滤、信号开关时序ts_mean, ts_delta, ts_rank, ts_std_dev单股票时间序列 窗口参数同长度时间序列动量、均值回归向量rank, zscore, winsorize, signed_power横截面向量横截面向量截面排序、去极值、标准化拿一个最简单的表达式举例rank(ts_mean(close, 20))。这棵计算图里close是叶子节点ts_mean先在时序维度上做 20 日窗口的均线输出一个新的时间序列然后rank在横截面上对当日所有股票的 20 日均线做排序得到 0 到 1 之间的排名值。整个表达式的输出就是股票在当日截面上的相对位置数值越大代表该股票的短期均价在全市场越靠前。初学者最容易在这里搞混ts_mean的输出仍然是一个时间序列不能把它直接当作横截面值来用。如果你想在横截面上比较不同股票的 20 日均线必须把ts_mean的结果再包一层rank或zscore这个「先时序、再截面」的组合顺序正是 BRAIN 因子表达式最核心的套路。3. 算术与逻辑算子的实战用法因子合成与条件开关3.1 算术算子的典型组合模式算术算子看起来简单但在因子表达式里的作用是被严重低估的。最基础的用法自然是价量数据的直接加减乘除比如用divide(close, open)表示日内涨幅或者multiply(close, volume)构造成交额序列。但真正有价值的是把多个基础因子通过算术算子做合成形成复合信号。我常用的一种合成方式是「差值 归一化」两步走。先拿两个基础因子做差比如subtract(ts_mean(close, 5), ts_mean(close, 20))得到短期均线与长期均线的差值类似 MACD 的雏形但这个差值的量纲取决于股价绝对值不同股票的数值不可比所以后面要接rank或者zscore做横截面处理。完整的表达式长这样# 基于 5 日与 20 日均线差值的截面动量因子 gap ts_mean(close, 5) - ts_mean(close, 20) alpha rank(gap)ts_mean(close, 5)取的是近 5 个交易日的收盘价均值ts_mean(close, 20)取近 20 日均值两者相减得到短长均线乖离。rank(gap)将乖离值在当日截面上映射到 0 到 1 区间越接近 1 说明短期均线相对长期均线越高。这里要注意gap的计算用了两条不同窗口的均线窗口越长信号越平滑但滞后也越明显。除法在算术算子里的地位比较特殊。直接用divide(close, ts_mean(close, 20))可以构造价格相对其 20 日均线的比值这个比值天然就是无量纲的在不同股票之间可比但它不服从正态分布尾部极值特别多。所以我通常会把除法结果再接一个rank或者winsorize把极端值压一压再交给后续因子合成。3.2 逻辑算子布尔信号如何变成可用因子逻辑算子返回的是布尔值但 BRAIN 里的布尔值在数学运算中可以自动转换为 0 和 1这给因子设计带来了很大的灵活性。一个典型的应用是条件开关——当某个条件满足时因子值保留不满足时因子值清零。这种写法比用if语句更符合 BRAIN 的表达式语言风格。# 成交额放大且股价高于 20 日均线时的动量信号 cond greater_than(volume, ts_mean(volume, 20)) above_ma greater_than(close, ts_mean(close, 20)) alpha cond * above_ma * rank(ts_ret(close, 5))greater_than(volume, ts_mean(volume, 20))判断当日成交量是否大于 20 日均量返回布尔序列above_ma判断收盘价是否站上 20 日均线同样是布尔序列。两个布尔值相乘相当于逻辑与操作只有两个条件同时成立时才为 1。ts_ret(close, 5)是 BRAIN 里常用的算子表示 5 日收益率rank做截面排序后乘以条件值得到「放量且站上均线」时的相对收益强度。条件不成立时因子值为 0不会干扰排序。这种布尔开关的写法有一个隐患如果条件命中率很低大部分股票的因子值都是 0rank的结果会集中在 0.5 附近区分度变差。我一般会在条件过滤的基础上保留原始信号的连续变化比如用cond * rank(ts_ret(close, 5)) (1 - cond) * 0.5这样不满足条件的股票被赋予中性的 0.5而不是全部归零排名分布会更合理。逻辑算子里还有一个常用的and算子它和乘法在布尔逻辑上等价但语义更明确代码可读性更好。不过用and时要记得它的运算优先级和乘法不同复杂表达式里最好还是用乘法来代替避免运算符优先级带来的歧义。注意BRAIN 表达式里算make子函数的括号匹配很严格一个greater_than少写一个括号编译直接失败。写长表达式时我习惯每套一层算子就换一行缩进虽然平台不强制但排查错误时能省不少时间。4. 时间序列算子的窗口逻辑动量、均值回归与滞后效应4.1 ts_mean、ts_delta 与 ts_ret三个最常用的时序算子时间序列算子是 BRAIN 因子库的主力几乎任何一个带动量的 Alpha 表达式都离不开它们。三个最基础的算子是ts_mean、ts_delta和ts_ret。ts_mean计算窗口内均值ts_delta计算当前值与 n 期前的差值注意不是百分比变化ts_ret计算 n 期收益率公式是close / close[n] - 1n 是窗口长度参数。# 典型动量因子20 日收益率 5 日收益率差 momentum_20 ts_ret(close, 20) momentum_5 ts_ret(close, 5) alpha rank(momentum_20 - momentum_5)ts_ret(close, 20)计算过去 20 个交易日的累计收益率ts_ret(close, 5)是近 5 日的收益率。两者相减得到「长短期动量差」这个值的逻辑是如果 20 日涨幅明显高于 5 日涨幅说明涨势在过去 20 天里更持续近期反而放缓这有时候是回调信号反之说明短期加速上涨。rank做截面排序后数字越大的股票在排名上越靠前。窗口参数 n 的选择会直接影响因子表现20 日和 5 日的组合对应的是中短期动量逻辑如果你关注更长周期的趋势可以把参数改成 60 和 20。参数的敏感性测试在 BRAIN 里做起来很方便改两个数字重新提交回测就行。ts_delta和ts_ret的区别值得点一句ts_delta(close, 5)返回的是价格差值单位是元不同价位的股票不能直接比ts_ret返回的是收益率无量纲适合跨股票比较。在因子表达式里如果要做截面排序优先用ts_ret而不是ts_delta。有些新手写动量因子直接rank(ts_delta(close, 20))这个因子在低价股上天然占优因为同样涨 1 元低价股收益率远高于高价股——这就是一个隐蔽的偏差来源。4.2 ts_argmax、ts_rank 与窗口时序逻辑ts_rank和ts_argmax在因子研究里的地位其实被低估了。ts_rank(x, n)返回当前值在近 n 个周期里的分位排名输出范围是 0 到 1not n 的排名整数而是归一化后的位置它描述的是「当前位置在窗口中的相对高低」。ts_argmax(x, n)返回窗口内最大值出现的位置距当前周期的距离本质上是一个滞后期指标。# 用 ts_argmax 判断价格是否处于高位回落阶段 days_since_high ts_argmax(high, 20) alpha -rank(days_since_high)ts_argmax(high, 20)返回过去 20 个交易日里最高价距离今天是第几天0 表示今天就是最高价19 表示最高价在 20 天前。这个值越大说明价格距离最高点越久回落幅度可能越大。-rank(days_since_high)做负向排序让 days_since_high 大的股票排名靠前形成「已经回落很久」的做多信号。逻辑上这是一种均值回归思路——价格距离高点越久越有可能反转。要注意的是ts_argmax的输出是滞后天数不是价格本身所以它的量纲天然不同必须做截面排序后才能用。ts_rank的典型用法是判断当前收益率在历史窗口中的位置。比如ts_rank(ts_ret(close, 5), 60)表示当前 5 日收益率在过去 60 个周期里的分位位置。这个算子的优点在于它对异常值不敏感因为排名本身会拉平极端值的影响。但如果窗口长度太小ts_rank的输出会变成离散值比如窗口只有 5那输出只能是 0、0.2、0.4 这些五等分值会损失很多信息。所以我一般会把ts_rank的窗口设得大一些至少 20 起步。关于时序窗口有个容易被忽视的细节是 BRAIN 平台的数据对齐。ts_mean(close, 5)的窗口包含当前交易日吗答案是包含的。这个「包含当前」的设定意味着任何时序算子天然引入了当天的信息。如果你要写严格的「基于前一天收盘后的信号」需要把数据整体滞后一天常见做法是对因子整体做delay(alpha, 1)。这一步在回测里影响很大——不做滞后处理的因子回测表现往往虚高因为相当于用当天的结果预测当天的走势属于变相的未来函数。5. 向量算子的截面逻辑rank、zscore 与去极值的组合艺术5.1 rank 与 zscore截面标准化到底在做什么向量算子处理横截面数据核心目标是把不同量纲、不同分布的因子值拉到同一个坐标系里比较。rank把横截面上的数值映射为 0 到 1 的均匀分布排名zscore则把数值标准化为均值 0、标准差 1 的正态分布严格说是近似正态。两者用哪个取决于下游处理逻辑。# rank 与 zscore 对同一因子处理后的分布差异 raw_factor ts_ret(close, 20) ranked rank(raw_factor) standardized zscore(raw_factor)raw_factor是 20 日收益率本质上是一个有偏、厚尾的分布。rank处理后的ranked严格服从均匀分布无论原始数据怎么偏移排序后一定是均匀的这对后续按分位数分组回测非常友好。zscore处理后的standardized保留了原始因子的大致形状但把均值和方差调整到了标准正态的水平如果原始分布本身就特别偏zscore之后仍然会偏。所以 zscore 更适用于原始因子已经接近正态的场景而 rank 适用于任何分布。在 BRAIN 里大部分因子最终提交前用的都是rank因为它对极端值不敏感、对分布形态鲁棒。但 rank 也有一个盲区它完全丢掉了原始数值之间的距离信息。比如某一天全市场涨幅都集中在 1% 到 2% 之间另一天涨幅从 -5% 到 8% 都有rank 在这两天都会给出 0 到 1 的排序但第二天的排序明显蕴含着更强的信号强度。为了弥补这个信息损失我有时会用rank和zscore做组合先rank去除分布形态影响再对排名做zscore让排序结果的分布在 0 到 1 的基础上拉开差距。表达式是zscore(rank(raw_factor))这个组合在 BRAIN 里非常常见它的含义是「对排名再标准化」既保留了排序的鲁棒性又恢复了部分幅度信息。5.2 winsorize 与数据极端的三种处理手段横截面数据里总有极端值可能是停牌、新股或者数据错误。极端值对后续的线性回归或均值计算有显著影响因此需要专门的算子。BRAIN 里的winsorize是按分位数截断常用参数是min_percentile和max_percentile默认一般是 5% 和 95%即把低于 5% 分位的值拉到 5% 分位高于 95% 的拉到 95% 分位。# 去极值后再标准化避免极端值主导 zscore 结果 winsorized winsorize(ts_ret(close, 5), min_percentile0.05, max_percentile0.95) alpha zscore(winsorized)winsorize(ts_ret(close, 5), 0.05, 0.95)把 5 日收益率的极端值拉到 5% 和 95% 分位边界然后zscore再标准化。这里顺序很关键如果先zscore后winsorize极端值会先影响均值和标准差的计算导致标准化后的分布仍然有偏。我一直坚持先去极值、再标准化这是一个数据处理的纪律性问题。除了winsorize处理极端值还有两种手段。一种是直接剔除——在 BRAIN 里可以用where加条件过滤把超过某个阈值的因子值替换为固定的中性值 0.5。另一种是不处理——如果你后面接的是rank极端值会被自动平移到排名两端影响相对有限。但若后面是zscore或回归类算子不处理极端值就是灾难一个 100 倍于均值的数据点能直接把标准化结果拉歪。三种手段选哪个取决于你因子的下游用途。做机器学习模型的特征时winsorize更好因为它保留了大部分分布信息做简单排序 Alpha 时rank的天然抗极端值属性就够用不必额外处理。5.3 向量算子在组合表达式中的位置向量算子在表达式里的顺序决定了整个因子的行为逻辑。我总结过一个「先时序、再截面、最后过滤」的模板先用时序算子构造原始信号再用向量算子做横截面标准化最后用逻辑算子加条件过滤。这个模板覆盖了 BRAIN 上相当大一部分有效因子。# 一个结构完整的组合因子 raw ts_delta(close, 5) / ts_std_dev(close, 20) # 5日变动幅 / 20日波动 neutralized zscore(rank(raw)) # 截面标准化 alpha neutralized * greater_than(volume, ts_mean(volume, 20)) # 放量过滤raw用ts_delta除以ts_std_dev近似一个「价格变动 / 波动率」的标准化变动幅度类似于信息比率的思想。zscore(rank(raw))做两层截面处理先去极端值影响再恢复距离信息。最后乘以放量条件的布尔值把缩量交易日的信号清零。这个表达式的多层嵌套在 BRAIN 中执行时会有性能开销但单股票数量在几千到几万之间时运行时间一般在可接受范围内。不过要注意表达式的嵌套深度不能太夸张平台对算子数量有上限约束。向量算子还有一个容易混淆的地方rank和ts_rank差别非常大。rank(x)在当天横截面所有股票上排序输出的是股票之间的相对位置ts_rank(x, n)在单只股票的历史窗口内排序输出的是该股票自身当前值在历史中的相对位置。一个横截面视角、一个时序视角信号含义完全不同用混了就是逻辑完全错误。6. 避坑手册五个高频的 BRAIN 算子使用错误与排查思路6.1 未来函数ts_mean 含当前值 滞后处理缺失现象回测表现极佳月度收益稳定在 3% 以上但换到样本外或者实盘模拟立刻衰减过半。回测曲线在调参时非常「听话」参数一变曲线就跟着变仿佛因子在自适应。原因绝大多数时序算子默认纳入当前交易日数据。ts_mean(close, 5)包含了今天的收盘价ts_ret(close, 5)也包含了今天的价格变化。如果你的因子是用当日数据做信号、当日就产生收益那就等于拿结果预测结果。我在 6.2 节提过delay(alpha, 1)的滞后操作这一步在提交回测前是强制性的检查项。解决对任何输出直接作用于下期收益的因子统一加一层delay(alpha, 1)把信号整体滞后一个周期。注意是因子级别滞后不是对原始数据逐项滞后。检查方法比较简单——把回测周期缩短到一个月看首日的因子收益是否明显区别于后续日期如果第一天就产生大量收益且因子未做 delay大概率有未来函数。6.2 横截面与时间序列维度混淆现象表达式编译通过但结果里大量股票的因子值都是同一个数字或者排名集中在 0.5 附近区分度很低。更隐蔽的是同一个因子在某些年份有效、某些年份完全失效且失效年份看不出明显市场风格切换。原因把需要横截面处理的因子如收益率、价差直接用在了时序维度上或者反过来把时序统计量当成了截面值。比如rank(ts_ret(close, 5))和ts_rank(ts_ret(close, 5), 60)的差别前者看的是股票间相对强弱后者看的是该股票自身时序位置。一旦在组合表达式里把两个不同类型的算子混用信号的维度就乱了。解决写表达式前先在注释里标明每个中间变量的维度。# 变量维度时间序列 / 横截面这样的标注只需要几秒钟但能避免大量低级错误。如果结果不确定对中间结果做一次ts_rank和rank的对比回测看哪个的 IC 更稳定基本能定位问题。6.3 极端值主导不打磨直接进模型现象zscore 标准化后的因子分布明显左偏或右偏分组回测时第一组和最后一组收益极端中间组区分度不足。将因子输入机器学习模型时特征重要性集中在个别极端值上。原因极端值在计算均值、方差时具有极高权重。BRAIN 里某些算子在设计时已经做了内部去极值处理但不是所有算子都有这个机制。直接用ts_ret或ts_delta的原始输出进zscore极端值会主导标准化的结果。解决一律先winsorize再zscore或者直接用rank替代zscore二选一。我习惯在因子表达式模板里固定写zscore(winsorize(x, 0.05, 0.95))把去极值变成默认步骤而不是可选步骤。极值截断的分位数参数不要低于 1%否则去不掉尾部噪音。6.4 参数敏感性过强微调窗口就翻脸现象把ts_mean的窗口从 20 改到 21回测 IC 从 0.06 掉到 0.02从 21 改到 22又回升到 0.05。这种「参数一跳就变脸」的情况说明因子在过拟合噪音。原因窗口参数对时序算子的影响是全局性的窗口变短信号变灵敏但噪音增加变长则更平滑但滞后。敏感性强意味着因子表达式的核心逻辑对窗口过于依赖通常是信号本身太薄弱窗口稍微一动就压过了真实信号。BRAIN 平台上的样本内回测噪音水平比实盘低过拟合信号在回测里会显得特别有效。解决对关键窗口参数做网格搜索观察 IC 和收益率的分布。如果相邻参数的 IC 跳动幅度超过 50%这个因子大概率不稳定需要回到表达式本身找更稳健的信号结构。也可以试着一个参数固定、另一个参数小幅变化的二维网格画出的热力图如果呈现随机斑点而不是渐变趋势就说明因子逻辑不够稳定。6.5 平台语法差异把习惯函数当算子用现象写if语句、for循环或者print调试结果编译失败报错信息指向语法错误。另外一部分用户会用 pandas 的写法比如df[close].rolling(20).mean()这些在 BRAIN 里完全行不通。原因BRAIN 的表达式环境不是完全自由的 Python它是一套 DSL领域特定语言只接受算子和数据字段的嵌套组合。平台在编译时把表达式翻译为内部的优化计算图分支和循环逻辑没法直接映射到图上所以语法受限。有些简单逻辑可以用where或逻辑算子表达但循环依赖多个迭代步骤的复杂逻辑就需要拆成多个因子。解决先把逻辑拆成可嵌套的算子组合。如果需要条件分支用where(condition, value_if_true, value_if_false)这是 BRAIN 里最常见的条件算子。需要循环的地方大部分可以用ts_前缀的算子替代因为时序窗口本身就是一种隐式循环。如果确实无法在表达式内完成就在平台外先预处理数据生成新的数据字段再导入不要和 DSL 硬刚。从第 2 节到第 5 节走的是一条从算子分类到表达式组合的完整路径前四节是单算子的使用逻辑本节是把它们放在一起时最容易翻车的点。任何时候回测结果「好得不真实」先查这三件事有没有滞后、有没有维度混用、有没有极端值成主导。7. 进阶技巧用向量算子组合作中性化处理与性能检查以market_cap和rank的组合为例一个有效的风格中性化做法是把目标因子对市值和行业做横截面回归取残差作为新因子。BRAIN 平台支持通过算子嵌套实现简洁的表达式尽管 BRAIN 本身不直接提供线性回归算子但可以用zscore(rank(x))组合sector分组逻辑处理。一个实用的方案是做「市值分层 组内排序」用group_rank(x, market_cap)或先按市值分桶再在桶内做rank。这样处理后的因子不再偏向小市值股票。具体的表达式是group_rank(ts_ret(close, 20), bucket(market_cap, 10))其中bucket把市值切成 10 档然后按档位分别对个股排序结果消除了市值维度的影响。如果要进一步去除行业影响可以把表达式里的bucket(market_cap, 10)换成bucket(sector, 10)——这里sector需要提前定义成一个数据字段。做完中性化后老手的习惯是再验一遍「因子真的和市值无关吗」。做法是把新因子取出来对market_cap的排名做线性相关看相关系数绝对值是否低于 0.05。如果还残存明显相关性就把分组数调细从 10 档调成 20 档或者先对市值做rank再取整分档能获得更精细的中性化效果。这个验证步骤BRAIN 平台本身不直接展示相关系数但我一般会下载因子值在本地算一遍确保中性化干净了再提交。算子性能方面一个容易忽略的事实是表达式中嵌套超过 7 层编译和回测时间会成倍增加。BRAIN 平台的算子执行是逐层展开的每多一层就多一次中间计算且中间结果会占内存。如果因子涉及几千只股票的几十个交易日嵌套过深会导致回测跑一次等很久。我自己的习惯是把一个复杂因子拆成两到三个中间因子先单独回测中间因子的有效性再合成为最终因子。这样既方便排查问题也能减少单次回测的计算压力。回测结果出来以后还有一个重要的检查角度分组收益单调性。就是把因子值按十分位分组看完 top 组和 bottom 组的收益差以及中间组的单调性。如果中间组乱序、只有 top 和 bottom 差异大说明因子在中间区域的区分度不足策略容量和稳定性都有限。出现这种情况我会优先考虑换事件触发型算子如ts_argmax这类周期位置类算子而不是继续调窗口参数。多次实操下来算子选型对因子质量的影响远大于参数微调。再提一个不常被注意但很实用的技巧同一表达式中混合不同时间尺度的时序算子往往比单一时间尺度更稳健。比如ts_ret(close, 5)加ts_ret(close, 20)的组合比只用其中一个的 IC 波动更小。如果你要提交一批因子做组合我会先跑一遍我惯用的过滤流程——算子的算子粒度需完全按数据字段过滤三天不满足条件的信号就砍掉。从那以后我每次写完表达式都会强制走一遍「滞后 → 中性化 → 分组单调性」这三步流程再提交很多「玄学失效」的情况其实在提交前就能拦截住。希望这些梳理过的经验能帮到你少走一段弯路。本文还有配套的精品资源点击获取