Package: expr-codegen
expr_codegen 符号表达式代码生成器表达式转代码工具项目背景在本人新推出polars_ta这个库后，再回头反思expr_codegen是什么。expr_cdegen本质是DSL，领域特定语⾔(Domain Specific Language)。但它没有定义新的语法它解决了两个问题:polars_ta已经能很方便的写出特征计算表达式，但遇到混用时序与截面的表达式，利用expr_codegen能自动分组大大节省工作expr_codegen利用了Common Subexpression Elimination公共子表达式消除，大量减少重复计算，提高效率就算在量化领域，初级研究员局限于时序指标，仅用polars_ta即可，中高级研究员使用截面指标，推荐用expr_codegen虽然现在此项目与polars_ta依赖非常紧密，但也是支持翻译成其它库,如pandas / cudf.pandas，只是目前缺乏一个比较简易的库在线演示https://exprcodegen.streamlit.app初级用户可以直接访问此链接进行表达式转译，不需要另外安装软件。(此工具免费部署在国外，打开可能有些慢)更完整示例访问alpha_examples使用方法运行demo_cn.py生成output.py，将此文件复制到其它项目中直接import使用即可。一般生成的文件不需要再修改。目录结构│  requirements.txt # 通过`pip install -r requirements.txt`安装依赖
├─data
│      prepare_date.py # 准备数据
├─examples
│      alpha101.txt # WorldQuant Alpha101示例，可复制到`streamlit`应用
│      demo_cn.py # 中文注释示例。演示如何将表达式转换成代码
│      demo_exec_pl.py # 演示调用转换后代码并绘图
│      demo_transformer.py # 演示将第三方表达式转成内部表达式
│      output.py # 结果输出。可不修改代码，直接被其它项目导入
│      show_tree.py # 画表达式树形图。可用于分析对比优化结果
│      sympy_define.py # 符号定义，由于太多地方重复使用到，所以统一提取到此处
├─expr_codegen
│   │  expr.py # 表达式处理基本函数
│   │  tool.py # 核心工具代码。一般不需修改
│   ├─polars
│   │  │  code.py # 针对polars语法的代码生成功能
│   │  │  template.py.j2 # `Jinja2`模板。用于生成对应py文件，一般不需修改
│   │  │  printer.py # 继承于`Sympy`中的`StrPrinter`，添加新函数时可能需修改此文件工作原理本项目依赖于sympy项目。所用到的主要函数如下：simplify: 对复杂表达式进行化简cse:Common Subexpression Elimination公共子表达式消除StrPrinter: 根据不同的函数输出不同字符串。定制此代码可以支持其它语种或库因为groupby,sort都比较占用时间。如果提前将公式分类，不同的类别使用不同的groupby，可以减少计算时间。ts_xxx(ts_xxx): 可在同一groupby中进行计算cs_xxx(cs_xxx): 可在同一groupby中进行计算ts_xxx(cs_xxx): 需在不同groupby中进行计算cs_xxx(ts_xxx(cs_xxx)): 需三不同groupby中进行计算gp_xxx(aa, )+gp_xxx(bb, ): 因aa,bb不同，需在两不同groupby中进行计算所以需要有一个函数能获取当前表达式的类别(get_current)和子表达式的类别(get_children)如果当前类别与子类别不同就可以提取出短公式(extract)。不同层的同类别表达式有先后关系，不能放同一groupby利用cse的特点，将长表达式替换成前期提取出来的短表达式。然后输入到有向无环图(DAG)利用有向无环图的流转，进行分层。同一层的ts,cs,gp不区分先后同一层对ts,cs,gp分组，然后生成代码(codegen)即可隐含信息ts: sort(by=[ASSET, DATE]).groupby(by=[ASSET], maintain_order=True)cs: sort(by=[DATE]).groupby(by=[DATE], maintain_order=False)gp: sort(by=[DATE, GROUP]).groupby(by=[DATE, GROUP], maintain_order=False)即时序函数隐藏了两个字段ASSET, DATE，横截面函数了隐藏了一个字段DATE分组函数转入了一个字段GROUP，同时隐藏了一个字段DATE两种分类方法根据算子前缀分类(get_current_by_prefix)，限制算子必需以ts_、cs_、gp_开头根据算子全名分类(get_current_by_name), 不再限制算子名。比如cs_rank可以叫rank二次开发备份后编辑demo_cn.py,import需要引入的函数然后printer.py有可能需要添加对应函数的打印代码注意：需要留意是否要加括号()，不加时可能优先级混乱，可以每次都加括号，也可用提供的parenthesize简化处理贡献代码还有很多函数没有添加，需要大家提交代码一起完善目前表达式样式优先向WorldQuant 的 Alpha101 靠齐小技巧sympy不支持==，而是当成两个对象比较。例如：if_else(OPEN==CLOSE, HIGH, LOW), 一开始就变成了if_else(False, HIGH, LOW)可以用Eq来代替，if_else(Eq(OPEN, CLOSE), HIGH, LOW)。具体示例请参考Alpha101中的alpha_021sympy不支持bool转int。例如：(OPEN < CLOSE) * -1报错TypeError: unsupported operand type(s) for *: 'StrictLessThan' and 'int'可以用if_else代替。if_else(OPEN<CLOSE, 1, 0)*-1。具体示例请参考Alpha101中的alpha_064Python不支持?:三元表达式，只支持if else, 而在本项目中需要转成if_else以上三种问题本项目都使用ast进行了处理，可以简化使用示例片段需要转译的部分公式，详细代码请参考Demoexprs_src={"expr_1":-ts_corr(cs_rank(ts_mean(OPEN,10)),cs_rank(ts_mean(CLOSE,10)),10),"expr_2":cs_rank(ts_mean(OPEN,10))-abs_(log(ts_mean(CLOSE,10)))+gp_rank(sw_l1,CLOSE),"expr_3":ts_mean(cs_rank(ts_mean(OPEN,10)),10),"expr_4":cs_rank(ts_mean(cs_rank(OPEN),10)),"expr_5":-ts_corr(OPEN,CLOSE,10),}转译后的代码片段，详细代码请参考Polars版deffunc_0_ts__asset(df:pl.DataFrame)->pl.DataFrame:df=df.sort(by=[_DATE_])# ========================================df=df.with_columns(_x_0=1/ts_delay(OPEN,-1),LABEL_CC_1=(-CLOSE+ts_delay(CLOSE,-1))/CLOSE,)# ========================================df=df.with_columns(LABEL_OO_1=_x_0*ts_delay(OPEN,-2)-1,LABEL_OO_2=_x_0*ts_delay(OPEN,-3)-1,)returndf转译后的代码片段，详细代码请参考Pandas版deffunc_2_cs__date(df:pd.DataFrame)->pd.DataFrame:# expr_4 = cs_rank(x_7)df["expr_4"]=(df["x_7"]).rank(pct=True)returndfdeffunc_3_ts__asset__date(df:pd.DataFrame)->pd.DataFrame:# expr_5 = -ts_corr(OPEN, CLOSE, 10)df["expr_5"]=-(df["OPEN"]).rolling(10).corr(df["CLOSE"])# expr_6 = ts_delta(OPEN, 10)df["expr_6"]=df["OPEN"].diff(10)returndfdf=df.sort_values(by=["asset","date"]).groupby(by=["asset"],group_keys=False).apply(func_0_ts__asset__date)df=df.groupby(by=["date"],group_keys=False).apply(func_0_cs__date)df=func_0_cl(df)本地部署交互网页只需运行streamlit run streamlit_app.py