因子定义与表达式引擎:从概念到实战

做量化交易这些年,我越来越觉得因子就是策略的灵魂。你想想看,没有好的因子,再牛的模型也是白搭。今天咱们就来聊聊Qlib里因子怎么定义、怎么写,特别是那个表达式引擎——说实话,我第一次用的时候也觉得有点绕,但用顺手了是真香。

因子到底是什么?

因子,说白了就是能预测股票未来收益的特征。比如市盈率、动量、换手率,这些都是因子。但要注意,不是随便一个指标都能叫因子——它得对未来的收益有预测能力。

我在项目中遇到过不少新手,上来就堆了几百个因子,结果回测一跑,全是噪音。嗯,这里要记住:因子的质量远比数量重要。

因子的三个核心属性:

  • 预测性:因子值必须与未来收益存在统计相关性
  • 稳定性:在不同市场环境下表现相对一致
  • 可解释性:最好能说清楚为什么这个因子有效

Qlib Expression Engine 入门

Qlib的表达式引擎,你可以把它想象成一个因子计算器。它最大的好处是:你不需要写循环、不用管数据对齐,只要把数学表达式写出来,引擎自动帮你算好。

我个人习惯把表达式引擎理解成「懒人神器」。比如你要算5日均线,传统做法得写十几行pandas代码,但在Qlib里就一句话:

# 最简单的MA因子
ma5 = Mean('close', 5)

# 或者用操作符写法
ma5 = Ref('close', -4) + Ref('close', -3) + Ref('close', -2) + Ref('close', -1) + 'close' / 5

为什么会这么简洁?因为引擎底层帮你处理了时间序列的对齐、缺失值填充、滚动计算这些脏活累活。你只管写逻辑就好。

小技巧:刚开始用表达式引擎时,我建议你先从最简单的算术运算开始,慢慢再接触条件表达式和自定义函数。一口吃不成胖子。

基础因子编写实战

咱们直接上手写几个最常用的因子。这些因子在实盘中我几乎天天用。

1. 移动平均线(MA)

# 5日均线
ma5 = Mean('close', 5)

# 20日均线
ma20 = Mean('close', 20)

# 均线乖离率(价格偏离均线的程度)
bias = ('close' - ma20) / ma20

乖离率这个因子,我曾在一次回测中发现它对短期反转特别敏感。说白了就是:价格离均线太远了,大概率会往回拉一拉。

2. 相对强弱指标(RSI)

RSI的计算稍微复杂一点,但表达式引擎写起来依然很清爽:

# 计算价格变化
delta = 'close' - Ref('close', 1)

# 分别计算上涨和下跌的均值
gain = Max(delta, 0)
loss = -Min(delta, 0)

# RSI = 100 - 100/(1 + RS)
avg_gain = Mean(gain, 14)
avg_loss = Mean(loss, 14)
rs = avg_gain / avg_loss
rsi = 100 - 100 / (1 + rs)

注意看,这里用了Ref函数来取前一天的收盘价。Ref是表达式引擎里最常用的函数之一,它的作用就是「回头看」——取某个时间点之前的数据。

避坑指南:我曾经在写RSI时犯过一个低级错误——忘记处理分母为零的情况。当avg_loss为0时,rs会变成无穷大。虽然Qlib引擎会返回NaN,但后续计算会出问题。建议加上一个极小值保护:rs = avg_gain / (avg_loss + 1e-10)

因子表达式的高级用法

基础因子写熟了,咱们来点进阶的。表达式引擎真正厉害的地方在于组合和条件运算。

条件表达式

有时候我们需要根据某个条件来切换因子值。比如:

# 如果5日均线在20日均线之上,用动量因子;否则用反转因子
condition = ma5 > ma20
factor = If(condition, momentum, reversal)

这个If函数就是条件判断的核心。它接受三个参数:条件、为真时的值、为假时的值。

跨周期运算

有些因子需要用到不同周期的数据。比如计算「过去20天中,有多少天收盘价在5日均线之上」:

# 每天判断收盘价是否在5日均线之上
above_ma5 = 'close' > ma5

# 统计过去20天中满足条件的天数
count_above = Sum(above_ma5, 20)

你看,逻辑判断和统计运算可以无缝组合。这就是表达式引擎的魅力——像搭积木一样构建复杂因子。

自定义函数

如果内置函数不够用,Qlib还支持自定义。我一般把常用的复杂计算封装成函数:

# 自定义一个「波动率调整后的动量」因子
def vol_adjusted_momentum(close, window=20):
    ret = close / Ref(close, window) - 1
    vol = StdDev(ret, window)
    return ret / (vol + 1e-10)

# 在表达式中直接调用
factor = vol_adjusted_momentum('close', 20)

嗯,这里要注意:自定义函数的参数必须是字符串(列名)或数值,不能传DataFrame。这是引擎的约束,习惯了就好。

知识体系总览

为了让你对整个因子定义流程有个全局认识,我画了张图:

因子定义与表达式引擎知识体系 原始数据 价格、成交量、财务数据 表达式引擎 解析、计算、对齐 因子值 数值序列 表达式引擎核心组件 基础函数 Mean, StdDev, Max, Min Ref, Sum, If 运算符 +, -, *, / >, <, == 自定义扩展 用户自定义函数 复杂逻辑封装 典型因子:MA(均线) | RSI(相对强弱) | 乖离率 | 动量 | 波动率调整因子

从这张图可以看得很清楚:原始数据经过表达式引擎的加工,变成可用的因子值。而引擎的核心就是基础函数、运算符和自定义扩展这三板斧。

写在最后

因子定义这件事,说难不难,说简单也不简单。关键是要理解表达式引擎的设计思想——它把时间序列计算抽象成了数学表达式,让你能专注于「算什么」而不是「怎么算」。

我个人建议你从MA和RSI这两个因子开始练手,写熟了再尝试组合和条件表达式。记住:好的因子不是一次写出来的,而是在反复回测和优化中打磨出来的。

实战建议:写因子时,先用小样本数据验证一下结果对不对。我习惯在代码里加个print看看前几行的因子值,确认没有NaN或者异常值再跑全量回测。这一步能省下不少排查时间。

好了,因子定义的基础就聊到这儿。表达式引擎还有很多高级玩法,比如跨股票运算、滚动窗口自定义等,后面咱们慢慢展开。


公众号:蓝海资料掘金营,微信deep3321