因子定义与表达式引擎:从概念到实战
做量化交易这些年,我越来越觉得因子就是策略的灵魂。你想想看,没有好的因子,再牛的模型也是白搭。今天咱们就来聊聊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。这是引擎的约束,习惯了就好。
知识体系总览
为了让你对整个因子定义流程有个全局认识,我画了张图:
从这张图可以看得很清楚:原始数据经过表达式引擎的加工,变成可用的因子值。而引擎的核心就是基础函数、运算符和自定义扩展这三板斧。
写在最后
因子定义这件事,说难不难,说简单也不简单。关键是要理解表达式引擎的设计思想——它把时间序列计算抽象成了数学表达式,让你能专注于「算什么」而不是「怎么算」。
我个人建议你从MA和RSI这两个因子开始练手,写熟了再尝试组合和条件表达式。记住:好的因子不是一次写出来的,而是在反复回测和优化中打磨出来的。
实战建议:写因子时,先用小样本数据验证一下结果对不对。我习惯在代码里加个print看看前几行的因子值,确认没有NaN或者异常值再跑全量回测。这一步能省下不少排查时间。
好了,因子定义的基础就聊到这儿。表达式引擎还有很多高级玩法,比如跨股票运算、滚动窗口自定义等,后面咱们慢慢展开。
公众号:蓝海资料掘金营,微信deep3321