Skip to content

expr_codegen简易教程

伍侃 edited this page Jan 11, 2025 · 5 revisions

expr_codegen+polars_ta使用教程

仿WorldQuant Alpha101表达式转译工具。简单!高效!开源!

背景

量化研究员一定很羡慕WorldQuant表达式在因子挖掘的便捷,特别是用pandas来实现时序与截面混合的因子。到Github上找了不少Alpha101的代码,但都要对表达式手工翻译,工作量大易出错。

有一次忽然灵光一闪,想到用装饰器可以实现时序与截面算子的提前分组。这就是ta_cn项目的来历。但使用了一段时间后,还是发现很大的不足。

  1. alpha101中有大量重复表达式,希望能化简避免重复计算
  2. 装饰器中有groupby,每个算子都分组一次,应当归集起来一起groupby

由于pandas+ta_cn速度还是不够理想,开始转向polars。以前的表达式翻译成polars又是巨大的工作量。

又一次偶然想到sympy有表达式化简功能,开始翻阅文档,发现还有cse功能,开始验证可行性,最终推出expr_codegen,完全解决了ta_cn的缺点。 并将其中的算子代码独立成了polars_ta

一开始算子名都按Alpha101,比如rank,但后来发现自定义因子比较麻烦,所以约定算子有前缀。

安装

官方源

pip install expr_codegen -i https://pypi.org/simple -U
pip install polars_ta -i https://pypi.org/simple -U

国内镜像

pip install expr_codegen -i https://mirrors.aliyun.com/pypi/simple -U
pip install polars_ta -i https://mirrors.aliyun.com/pypi/simple -U

免安装使用

https://exprcodegen.streamlit.app

使用

常用函数只有一个codegen_exec

  1. 代码生成(codegen)
    • output_file: 用于调试,或脱离codegen直接运行
      • None: 生成的代码只存在于内存中
      • sys.stdout: 打印到标准输出
      • path: 保存到文件
    • date/asset: 日期和资产字段名。用于分组和排序
    • style: 代码风格。
      • pandas: 使用pd.DataFrame.groupby
      • polars_group: 使用pl.DataFrame.group_by
      • polars_over(推荐): 使用Expr.over
    • codes: 需转译的代码块。支持传入多块代码
      • 函数块:可以利用IDE的智能补全,方便编写
      • 字符串:可利用循环拼接表达式。支持持无法过Python语法检查的三元表达式
    • extra_codes: 原样复制到目标代码中的字符串
  2. 执行生成的代码(exec)
    • df不为None就会exec执行生成的代码
    • df可以为pd.DataFrame/pl.DataFrame/pl.LazyFrame之一
      • date: 日期时间。对时间粒度无要求,可以日月年,也可以秒分时。在横截面计算时用于分组,在时序计算时用于排序
      • asset: 资产。在时序指标计算前用于分组。
      • other: 其他特征/因子
    • run_file: 可跳过转译,直接运行文件
      • True: 直接运行output_file文件
      • out.py: 直接运行out.py文件
      • out: 直接运行out模块

基础语法

  1. ts_xxx()时序算子: 先groupby(asset),然后sort(date),最后调用ts_xxx
  2. cs_xxx()截面算子: 先groupby(date),然后调用cs_xxx
  3. gp_xxx(group,)分组算子: 先groupby(date, group),然后调用cs_xxx
  4. xxx():不用groupby直接调用的算子
  5. _xxx临时特征: 提取公共子表达式时会自动生成,参与特殊的语法调用等功能,事后会被删除
  6. C?T:F支持C语言的三元表达式。注意:仅能通过字符串输入
  7. A,B,C=MACD()。支持多输出的算子
  8. CLOSE[1]。表示昨天收盘价,底层会转换成ts_delay(CLOSE,1)

示例

示例1

alpha003=-1 * ts_corr(cs_rank(open), cs_rank(volume), 10)

相当简单,直接复制到https://exprcodegen.streamlit.app,然后下载到本地,导入到项目中

from output import main

# ...
df = main(df)
#...

注意:open这是原始价?还是后复权价?

示例2

自己开发了一个算子库,如何使用?

  • 保证import到项目中后能被Python调用成功
  • 保证函数名符合ts_/cs_等前缀规则
  1. 通过代码区传入
def _code_block_1():
    from my_lib import my_func as ts_func

    A = ts_func(OPEN, CLOSE, 10)

df = codegen_exec(df, _code_block_1, output_file='output.py')

可以检查output.py中的代码是否正确。之后可直接调用省去转译过程。

  1. 通过修改模板,备份polars_over/template.py.j2,修改template2.py.j2,文件前插入from my_lib import my_func as ts_func
def _code_block_1():
    A = ts_func(OPEN, CLOSE, 10)

df = codegen_exec(df, _code_block_1, output_file='output.py',template_file='template2.py.j2')

所以,polars_ta本质只是一个符合前缀规则的算子库。用户可以开发自己的算子库

示例3

聚宽数据字段名与本工具默认设置不同

  1. 提前规范字段名
df = df.rename({'time': 'date', 'code': 'asset'})
  1. 转译时指定
df = codegen_exec(df, _code_block_1, date='time', asset='code')

示例4

除了生成因子,是否能生成机器学习的标签?

标签是未来数据,本质上只要平移即可,[-1]最简洁

收益率_第一天开盘入场_第二天收盘出场 = CLOSE[-2]/OPEN[-1]-1

示例5

Alpha191一条条翻译成指定前缀规则的算子工作量太大了

已经提供了ast翻译代码的示例供研究

https://github.com/wukan1986/alpha_examples/blob/main/transformer/alpha191_transformer.py

示例6

生成的代码如何下断点调试?分两步

# 生成文件
df = codegen_exec(df, _code_block_1, output_file="output1.py", run_file=False)

# 跳过转译,以模块方式调用,run_file后缀名非`.py`
df = codegen_exec(df, _code_block_1, output_file="output1.py", run_file="output1")

示例7

公式已经定型不用再改,如何最小改动跳过转译,长期使用?

# 生成文件
df = codegen_exec(df, _code_block_1, output_file="output1.py", run_file=False)

# 跳过转译,以文件方式运行
df = codegen_exec(df, _code_block_1, output_file="output1.py", run_file=True)

示例8

如何使用三元表达式?

def _code_block_1():
    A = if_else(OPEN>CLOSE,1,0)

codes="""
B=OPEN>CLOSE?1:0
"""

df = codegen_exec(df, _code_block_1, codes)

以上两种方法是等价的