expr-codegen 0.10.6__tar.gz → 0.10.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/PKG-INFO +22 -23
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/README.md +21 -22
- expr_codegen-0.10.8/expr_codegen/_version.py +1 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/codes.py +1 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/model.py +6 -2
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/tool.py +12 -13
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen.egg-info/PKG-INFO +22 -23
- expr_codegen-0.10.6/expr_codegen/_version.py +0 -1
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/LICENSE +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/__init__.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/dag.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/expr.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/latex/__init__.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/latex/printer.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/pandas/__init__.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/pandas/code.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/pandas/helper.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/pandas/printer.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/pandas/ta.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/pandas/template.py.j2 +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/polars_group/__init__.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/polars_group/code.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/polars_group/printer.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/polars_group/template.py.j2 +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/polars_over/__init__.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/polars_over/code.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/polars_over/printer.py +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen/polars_over/template.py.j2 +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen.egg-info/SOURCES.txt +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen.egg-info/dependency_links.txt +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen.egg-info/requires.txt +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/expr_codegen.egg-info/top_level.txt +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/pyproject.toml +0 -0
- {expr_codegen-0.10.6 → expr_codegen-0.10.8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: expr_codegen
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.8
|
|
4
4
|
Summary: symbol expression to polars expression tool
|
|
5
5
|
Author-email: wukan <wu-kan@163.com>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -48,9 +48,7 @@ Requires-Dist: streamlit; extra == "streamlit"
|
|
|
48
48
|
Requires-Dist: streamlit-ace; extra == "streamlit"
|
|
49
49
|
Requires-Dist: more_itertools; extra == "streamlit"
|
|
50
50
|
|
|
51
|
-
# expr_codegen
|
|
52
|
-
|
|
53
|
-
表达式转代码工具
|
|
51
|
+
# expr_codegen 表达式转译器
|
|
54
52
|
|
|
55
53
|
## 项目背景
|
|
56
54
|
|
|
@@ -79,14 +77,9 @@ https://exprcodegen.streamlit.app
|
|
|
79
77
|
|
|
80
78
|
```python
|
|
81
79
|
import sys
|
|
80
|
+
from io import StringIO
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
from polars_ta.prefix.cdl import * # noqa
|
|
85
|
-
from polars_ta.prefix.ta import * # noqa
|
|
86
|
-
from polars_ta.prefix.tdx import * # noqa
|
|
87
|
-
from polars_ta.prefix.wq import * # noqa
|
|
88
|
-
|
|
89
|
-
from expr_codegen.tool import codegen_exec
|
|
82
|
+
from expr_codegen import codegen_exec
|
|
90
83
|
|
|
91
84
|
|
|
92
85
|
def _code_block_1():
|
|
@@ -114,10 +107,15 @@ def _code_block_2():
|
|
|
114
107
|
CPV = cs_zscore(_corr) + cs_zscore(_beta)
|
|
115
108
|
|
|
116
109
|
|
|
110
|
+
code = StringIO()
|
|
111
|
+
|
|
117
112
|
df = None # 替换成真实的polars数据
|
|
118
113
|
df = codegen_exec(df, _code_block_1, _code_block_2, output_file=sys.stdout) # 打印代码
|
|
119
114
|
df = codegen_exec(df, _code_block_1, _code_block_2, output_file="output.py") # 保存到文件
|
|
120
115
|
df = codegen_exec(df, _code_block_1, _code_block_2) # 只执行,不保存代码
|
|
116
|
+
df = codegen_exec(df, _code_block_1, _code_block_2, output_file=code) # 保存到字符串
|
|
117
|
+
code.seek(0)
|
|
118
|
+
code.read() # 读取代码
|
|
121
119
|
|
|
122
120
|
df = codegen_exec(df.lazy(), _code_block_1, _code_block_2).collect() # Lazy CPU
|
|
123
121
|
df = codegen_exec(df.lazy(), _code_block_1, _code_block_2).collect(engine="gpu") # Lazy GPU
|
|
@@ -138,7 +136,7 @@ df = codegen_exec(df.lazy(), _code_block_1, _code_block_2).collect(engine="gpu")
|
|
|
138
136
|
│ sympy_define.py # 符号定义,由于太多地方重复使用到,所以统一提取到此处
|
|
139
137
|
├─expr_codegen
|
|
140
138
|
│ │ expr.py # 表达式处理基本函数
|
|
141
|
-
│ │ tool.py #
|
|
139
|
+
│ │ tool.py # 核心工具代码
|
|
142
140
|
│ ├─polars
|
|
143
141
|
│ │ │ code.py # 针对polars语法的代码生成功能
|
|
144
142
|
│ │ │ template.py.j2 # `Jinja2`模板。用于生成对应py文件,一般不需修改
|
|
@@ -185,13 +183,20 @@ df = codegen_exec(df.lazy(), _code_block_1, _code_block_2).collect(engine="gpu")
|
|
|
185
183
|
1. 根据算子前缀分类(`get_current_by_prefix`),限制算子必需以`ts_`、`cs_`、`gp_`开头
|
|
186
184
|
2. 根据算子全名分类(`get_current_by_name`), 不再限制算子名。比如`cs_rank`可以叫`rank`
|
|
187
185
|
|
|
188
|
-
## Null
|
|
186
|
+
## Null处理
|
|
187
|
+
|
|
188
|
+
`null`是如何产生的?
|
|
189
|
+
|
|
190
|
+
1. 停牌导致。在计算前就直接过滤掉了,不会对后续计算产生影响。
|
|
191
|
+
2. 不同品种交易时段不同
|
|
192
|
+
3. 计算产生。`null`在数列两端不影响后续时序算子结果,但中间出现`null`会影响。例如: `if_else(close<2, None, close)`
|
|
189
193
|
|
|
190
194
|
https://github.com/pola-rs/polars/issues/12925#issuecomment-2552764629
|
|
195
|
+
|
|
191
196
|
非常棒的点子,总结下来有两种实现方式:
|
|
192
197
|
|
|
193
|
-
1. 将`null`分成一组,`not_null
|
|
194
|
-
2. 仅一组,但复合排序,将`null`排在前面,`not_null
|
|
198
|
+
1. 将`null`分成一组,`not_null`分成另一组。要调用两次
|
|
199
|
+
2. 仅一组,但复合排序,将`null`排在前面,`not_null`排后面。只调用一次,略快一些
|
|
195
200
|
|
|
196
201
|
```python
|
|
197
202
|
X1 = (ts_returns(CLOSE, 3)).over(CLOSE.is_not_null(), _ASSET_, order_by=_DATE_),
|
|
@@ -199,17 +204,11 @@ X2 = (ts_returns(CLOSE, 3)).over(_ASSET_, order_by=[CLOSE.is_not_null(), _DATE_]
|
|
|
199
204
|
X3 = (ts_returns(CLOSE, 3)).over(_ASSET_, order_by=_DATE_),
|
|
200
205
|
```
|
|
201
206
|
|
|
202
|
-
第2种开头的`null
|
|
207
|
+
第2种开头的`null`区域,是否影响结果由算子所决定,特别时是多列输入时`null`区域可能有数据
|
|
203
208
|
|
|
204
209
|
1. `over_null='partition_by'`。分到两个区域
|
|
205
210
|
2. `over_null='order_by'`。分到一个区域,`null`排在前面
|
|
206
|
-
3. `over_null=None
|
|
207
|
-
|
|
208
|
-
## 二次开发
|
|
209
|
-
|
|
210
|
-
1. 备份后编辑`demo_express.py`, `import`需要引入的函数
|
|
211
|
-
2. 然后`printer.py`有可能需要添加对应函数的打印代码
|
|
212
|
-
- 注意:需要留意是否要加括号`()`,不加时可能优先级混乱,可以每次都加括号,也可用提供的`parenthesize`简化处理
|
|
211
|
+
3. `over_null=None`。不处理,直接调用,速度更快。如果确信不会中段产生`null`建议使用此参数
|
|
213
212
|
|
|
214
213
|
## `expr_codegen`局限性
|
|
215
214
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
# expr_codegen
|
|
2
|
-
|
|
3
|
-
表达式转代码工具
|
|
1
|
+
# expr_codegen 表达式转译器
|
|
4
2
|
|
|
5
3
|
## 项目背景
|
|
6
4
|
|
|
@@ -29,14 +27,9 @@ https://exprcodegen.streamlit.app
|
|
|
29
27
|
|
|
30
28
|
```python
|
|
31
29
|
import sys
|
|
30
|
+
from io import StringIO
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
from polars_ta.prefix.cdl import * # noqa
|
|
35
|
-
from polars_ta.prefix.ta import * # noqa
|
|
36
|
-
from polars_ta.prefix.tdx import * # noqa
|
|
37
|
-
from polars_ta.prefix.wq import * # noqa
|
|
38
|
-
|
|
39
|
-
from expr_codegen.tool import codegen_exec
|
|
32
|
+
from expr_codegen import codegen_exec
|
|
40
33
|
|
|
41
34
|
|
|
42
35
|
def _code_block_1():
|
|
@@ -64,10 +57,15 @@ def _code_block_2():
|
|
|
64
57
|
CPV = cs_zscore(_corr) + cs_zscore(_beta)
|
|
65
58
|
|
|
66
59
|
|
|
60
|
+
code = StringIO()
|
|
61
|
+
|
|
67
62
|
df = None # 替换成真实的polars数据
|
|
68
63
|
df = codegen_exec(df, _code_block_1, _code_block_2, output_file=sys.stdout) # 打印代码
|
|
69
64
|
df = codegen_exec(df, _code_block_1, _code_block_2, output_file="output.py") # 保存到文件
|
|
70
65
|
df = codegen_exec(df, _code_block_1, _code_block_2) # 只执行,不保存代码
|
|
66
|
+
df = codegen_exec(df, _code_block_1, _code_block_2, output_file=code) # 保存到字符串
|
|
67
|
+
code.seek(0)
|
|
68
|
+
code.read() # 读取代码
|
|
71
69
|
|
|
72
70
|
df = codegen_exec(df.lazy(), _code_block_1, _code_block_2).collect() # Lazy CPU
|
|
73
71
|
df = codegen_exec(df.lazy(), _code_block_1, _code_block_2).collect(engine="gpu") # Lazy GPU
|
|
@@ -88,7 +86,7 @@ df = codegen_exec(df.lazy(), _code_block_1, _code_block_2).collect(engine="gpu")
|
|
|
88
86
|
│ sympy_define.py # 符号定义,由于太多地方重复使用到,所以统一提取到此处
|
|
89
87
|
├─expr_codegen
|
|
90
88
|
│ │ expr.py # 表达式处理基本函数
|
|
91
|
-
│ │ tool.py #
|
|
89
|
+
│ │ tool.py # 核心工具代码
|
|
92
90
|
│ ├─polars
|
|
93
91
|
│ │ │ code.py # 针对polars语法的代码生成功能
|
|
94
92
|
│ │ │ template.py.j2 # `Jinja2`模板。用于生成对应py文件,一般不需修改
|
|
@@ -135,13 +133,20 @@ df = codegen_exec(df.lazy(), _code_block_1, _code_block_2).collect(engine="gpu")
|
|
|
135
133
|
1. 根据算子前缀分类(`get_current_by_prefix`),限制算子必需以`ts_`、`cs_`、`gp_`开头
|
|
136
134
|
2. 根据算子全名分类(`get_current_by_name`), 不再限制算子名。比如`cs_rank`可以叫`rank`
|
|
137
135
|
|
|
138
|
-
## Null
|
|
136
|
+
## Null处理
|
|
137
|
+
|
|
138
|
+
`null`是如何产生的?
|
|
139
|
+
|
|
140
|
+
1. 停牌导致。在计算前就直接过滤掉了,不会对后续计算产生影响。
|
|
141
|
+
2. 不同品种交易时段不同
|
|
142
|
+
3. 计算产生。`null`在数列两端不影响后续时序算子结果,但中间出现`null`会影响。例如: `if_else(close<2, None, close)`
|
|
139
143
|
|
|
140
144
|
https://github.com/pola-rs/polars/issues/12925#issuecomment-2552764629
|
|
145
|
+
|
|
141
146
|
非常棒的点子,总结下来有两种实现方式:
|
|
142
147
|
|
|
143
|
-
1. 将`null`分成一组,`not_null
|
|
144
|
-
2. 仅一组,但复合排序,将`null`排在前面,`not_null
|
|
148
|
+
1. 将`null`分成一组,`not_null`分成另一组。要调用两次
|
|
149
|
+
2. 仅一组,但复合排序,将`null`排在前面,`not_null`排后面。只调用一次,略快一些
|
|
145
150
|
|
|
146
151
|
```python
|
|
147
152
|
X1 = (ts_returns(CLOSE, 3)).over(CLOSE.is_not_null(), _ASSET_, order_by=_DATE_),
|
|
@@ -149,17 +154,11 @@ X2 = (ts_returns(CLOSE, 3)).over(_ASSET_, order_by=[CLOSE.is_not_null(), _DATE_]
|
|
|
149
154
|
X3 = (ts_returns(CLOSE, 3)).over(_ASSET_, order_by=_DATE_),
|
|
150
155
|
```
|
|
151
156
|
|
|
152
|
-
第2种开头的`null
|
|
157
|
+
第2种开头的`null`区域,是否影响结果由算子所决定,特别时是多列输入时`null`区域可能有数据
|
|
153
158
|
|
|
154
159
|
1. `over_null='partition_by'`。分到两个区域
|
|
155
160
|
2. `over_null='order_by'`。分到一个区域,`null`排在前面
|
|
156
|
-
3. `over_null=None
|
|
157
|
-
|
|
158
|
-
## 二次开发
|
|
159
|
-
|
|
160
|
-
1. 备份后编辑`demo_express.py`, `import`需要引入的函数
|
|
161
|
-
2. 然后`printer.py`有可能需要添加对应函数的打印代码
|
|
162
|
-
- 注意:需要留意是否要加括号`()`,不加时可能优先级混乱,可以每次都加括号,也可用提供的`parenthesize`简化处理
|
|
161
|
+
3. `over_null=None`。不处理,直接调用,速度更快。如果确信不会中段产生`null`建议使用此参数
|
|
163
162
|
|
|
164
163
|
## `expr_codegen`局限性
|
|
165
164
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.10.8"
|
|
@@ -125,6 +125,7 @@ class RenameTransformer(ast.NodeTransformer):
|
|
|
125
125
|
def __init__(self, funcs_map, targets_map, args_map=None):
|
|
126
126
|
|
|
127
127
|
if args_map is None:
|
|
128
|
+
# 保留字
|
|
128
129
|
args_map = {'True': "_TRUE_", 'False': "_FALSE_", 'None': "_NONE_"}
|
|
129
130
|
self.funcs_old = set()
|
|
130
131
|
self.args_old = set()
|
|
@@ -7,6 +7,8 @@ from sympy import symbols
|
|
|
7
7
|
from expr_codegen.dag import zero_indegree, hierarchy_pos, remove_paths_by_zero_outdegree
|
|
8
8
|
from expr_codegen.expr import CL, get_symbols, get_children, get_key, is_simple_expr
|
|
9
9
|
|
|
10
|
+
_RESERVED_WORD_ = {'_NONE_', '_TRUE_', '_FALSE_'}
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
class ListDictList:
|
|
12
14
|
"""嵌套列表
|
|
@@ -109,8 +111,7 @@ class ListDictList:
|
|
|
109
111
|
l2 = [set()]
|
|
110
112
|
s = set()
|
|
111
113
|
for i in reversed(l1):
|
|
112
|
-
#
|
|
113
|
-
s = s | i - {'_NONE_', '_TRUE_', '_FALSE_'}
|
|
114
|
+
s = s | i # - {'_NONE_', '_TRUE_', '_FALSE_'}
|
|
114
115
|
l2.append(s)
|
|
115
116
|
l2 = list(reversed(l2))
|
|
116
117
|
|
|
@@ -396,6 +397,9 @@ def dag_end(G):
|
|
|
396
397
|
key = G.nodes[node]['key']
|
|
397
398
|
expr = G.nodes[node]['expr']
|
|
398
399
|
symbols = G.nodes[node]['symbols']
|
|
400
|
+
# 这几个特殊的不算成字段名
|
|
401
|
+
symbols = list(set(symbols) - _RESERVED_WORD_)
|
|
402
|
+
|
|
399
403
|
exprs_ldl.append(key, (node, expr, symbols))
|
|
400
404
|
|
|
401
405
|
exprs_ldl._list = exprs_ldl.values()[1:]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import pathlib
|
|
3
3
|
from functools import lru_cache
|
|
4
|
-
from io import
|
|
5
|
-
from typing import Sequence, Dict, Union,
|
|
4
|
+
from io import TextIOBase
|
|
5
|
+
from typing import Sequence, Dict, Union, TypeVar, Optional, Literal
|
|
6
6
|
|
|
7
7
|
from black import Mode, format_str
|
|
8
8
|
from loguru import logger
|
|
@@ -12,7 +12,7 @@ from sympy.logic import boolalg
|
|
|
12
12
|
|
|
13
13
|
from expr_codegen.codes import sources_to_exprs
|
|
14
14
|
from expr_codegen.expr import get_current_by_prefix, get_children, replace_exprs
|
|
15
|
-
from expr_codegen.model import dag_start, dag_end, dag_middle
|
|
15
|
+
from expr_codegen.model import dag_start, dag_end, dag_middle, _RESERVED_WORD_
|
|
16
16
|
|
|
17
17
|
try:
|
|
18
18
|
from pandas import DataFrame as _pd_DataFrame
|
|
@@ -232,6 +232,7 @@ class ExprTool:
|
|
|
232
232
|
|
|
233
233
|
# 子表达式在前,原表式在最后
|
|
234
234
|
exprs_dst, syms_dst = self.merge(date, asset, **exprs_src)
|
|
235
|
+
syms_dst = list(set(syms_dst) - _RESERVED_WORD_)
|
|
235
236
|
|
|
236
237
|
# 提取公共表达式
|
|
237
238
|
self.cse(exprs_dst, symbols_repl=numbered_symbols('_x_'), symbols_redu=exprs_src.keys())
|
|
@@ -286,7 +287,7 @@ class ExprTool:
|
|
|
286
287
|
**kwargs)
|
|
287
288
|
|
|
288
289
|
# 移回到cache,防止多次调用多次保存
|
|
289
|
-
if isinstance(output_file,
|
|
290
|
+
if isinstance(output_file, TextIOBase):
|
|
290
291
|
# 输出到控制台
|
|
291
292
|
output_file.write(code)
|
|
292
293
|
elif output_file is not None:
|
|
@@ -305,6 +306,8 @@ def _exec_code(code: str, df_input):
|
|
|
305
306
|
|
|
306
307
|
|
|
307
308
|
def _exec_file(file, df_input):
|
|
309
|
+
file = pathlib.Path(file)
|
|
310
|
+
logger.info(f'run file "{file.absolute()}"')
|
|
308
311
|
with open(file, 'r', encoding='utf-8') as f:
|
|
309
312
|
code = f.read()
|
|
310
313
|
return _exec_code(code, df_input)
|
|
@@ -313,6 +316,7 @@ def _exec_file(file, df_input):
|
|
|
313
316
|
def _exec_module(module: str, df_input):
|
|
314
317
|
""""可下断点调试"""
|
|
315
318
|
m = __import__(module, fromlist=['*'])
|
|
319
|
+
logger.info(f'run module {m}')
|
|
316
320
|
return m.main(df_input)
|
|
317
321
|
|
|
318
322
|
|
|
@@ -322,13 +326,13 @@ _TOOL_ = ExprTool()
|
|
|
322
326
|
def codegen_exec(df: Optional[DataFrame],
|
|
323
327
|
*codes,
|
|
324
328
|
extra_codes: str = r'CS_SW_L1 = r"^sw_l1_\d+$"',
|
|
325
|
-
output_file: Union[str,
|
|
329
|
+
output_file: Union[str, TextIOBase, None] = None,
|
|
326
330
|
run_file: Union[bool, str] = False,
|
|
327
331
|
convert_xor: bool = False,
|
|
328
332
|
style: Literal['pandas', 'polars_group', 'polars_over'] = 'polars_over',
|
|
329
333
|
template_file: str = 'template.py.j2',
|
|
330
334
|
date: str = 'date', asset: str = 'asset',
|
|
331
|
-
over_null: Literal['
|
|
335
|
+
over_null: Literal['partition_by', 'order_by', None] = 'partition_by',
|
|
332
336
|
**kwargs) -> Optional[DataFrame]:
|
|
333
337
|
"""快速转换源代码并执行
|
|
334
338
|
|
|
@@ -340,7 +344,7 @@ def codegen_exec(df: Optional[DataFrame],
|
|
|
340
344
|
函数体。此部分中的表达式会被翻译成目标代码
|
|
341
345
|
extra_codes: str
|
|
342
346
|
额外代码。不做处理,会被直接复制到目标代码中
|
|
343
|
-
output_file: str
|
|
347
|
+
output_file: str| TextIOBase
|
|
344
348
|
保存生成的目标代码到文件中
|
|
345
349
|
run_file: bool or str
|
|
346
350
|
是否不生成脚本,直接运行代码。
|
|
@@ -362,8 +366,8 @@ def codegen_exec(df: Optional[DataFrame],
|
|
|
362
366
|
资产字段
|
|
363
367
|
over_null: str
|
|
364
368
|
时序中遇到null时的处理方式
|
|
365
|
-
- order_by: 空值排同一分区的前排
|
|
366
369
|
- partition_by: 空值划分到不同分区
|
|
370
|
+
- order_by: 空值排同一分区的前排
|
|
367
371
|
- None: 不做处理
|
|
368
372
|
|
|
369
373
|
Returns
|
|
@@ -374,17 +378,12 @@ def codegen_exec(df: Optional[DataFrame],
|
|
|
374
378
|
if df is not None:
|
|
375
379
|
if run_file is True:
|
|
376
380
|
assert output_file is not None, 'output_file is required'
|
|
377
|
-
output_file = pathlib.Path(output_file)
|
|
378
|
-
logger.info(f'run file "{output_file.absolute()}"')
|
|
379
381
|
return _exec_file(output_file, df)
|
|
380
382
|
if run_file is not False:
|
|
381
383
|
run_file = str(run_file)
|
|
382
384
|
if run_file.endswith('.py'):
|
|
383
|
-
run_file = pathlib.Path(run_file)
|
|
384
|
-
logger.info(f'run file "{run_file.absolute()}"')
|
|
385
385
|
return _exec_file(run_file, df)
|
|
386
386
|
else:
|
|
387
|
-
logger.info(f'run module "{run_file}"')
|
|
388
387
|
return _exec_module(run_file, df) # 可断点调试
|
|
389
388
|
|
|
390
389
|
# 此代码来自于sympy.var
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: expr_codegen
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.8
|
|
4
4
|
Summary: symbol expression to polars expression tool
|
|
5
5
|
Author-email: wukan <wu-kan@163.com>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -48,9 +48,7 @@ Requires-Dist: streamlit; extra == "streamlit"
|
|
|
48
48
|
Requires-Dist: streamlit-ace; extra == "streamlit"
|
|
49
49
|
Requires-Dist: more_itertools; extra == "streamlit"
|
|
50
50
|
|
|
51
|
-
# expr_codegen
|
|
52
|
-
|
|
53
|
-
表达式转代码工具
|
|
51
|
+
# expr_codegen 表达式转译器
|
|
54
52
|
|
|
55
53
|
## 项目背景
|
|
56
54
|
|
|
@@ -79,14 +77,9 @@ https://exprcodegen.streamlit.app
|
|
|
79
77
|
|
|
80
78
|
```python
|
|
81
79
|
import sys
|
|
80
|
+
from io import StringIO
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
from polars_ta.prefix.cdl import * # noqa
|
|
85
|
-
from polars_ta.prefix.ta import * # noqa
|
|
86
|
-
from polars_ta.prefix.tdx import * # noqa
|
|
87
|
-
from polars_ta.prefix.wq import * # noqa
|
|
88
|
-
|
|
89
|
-
from expr_codegen.tool import codegen_exec
|
|
82
|
+
from expr_codegen import codegen_exec
|
|
90
83
|
|
|
91
84
|
|
|
92
85
|
def _code_block_1():
|
|
@@ -114,10 +107,15 @@ def _code_block_2():
|
|
|
114
107
|
CPV = cs_zscore(_corr) + cs_zscore(_beta)
|
|
115
108
|
|
|
116
109
|
|
|
110
|
+
code = StringIO()
|
|
111
|
+
|
|
117
112
|
df = None # 替换成真实的polars数据
|
|
118
113
|
df = codegen_exec(df, _code_block_1, _code_block_2, output_file=sys.stdout) # 打印代码
|
|
119
114
|
df = codegen_exec(df, _code_block_1, _code_block_2, output_file="output.py") # 保存到文件
|
|
120
115
|
df = codegen_exec(df, _code_block_1, _code_block_2) # 只执行,不保存代码
|
|
116
|
+
df = codegen_exec(df, _code_block_1, _code_block_2, output_file=code) # 保存到字符串
|
|
117
|
+
code.seek(0)
|
|
118
|
+
code.read() # 读取代码
|
|
121
119
|
|
|
122
120
|
df = codegen_exec(df.lazy(), _code_block_1, _code_block_2).collect() # Lazy CPU
|
|
123
121
|
df = codegen_exec(df.lazy(), _code_block_1, _code_block_2).collect(engine="gpu") # Lazy GPU
|
|
@@ -138,7 +136,7 @@ df = codegen_exec(df.lazy(), _code_block_1, _code_block_2).collect(engine="gpu")
|
|
|
138
136
|
│ sympy_define.py # 符号定义,由于太多地方重复使用到,所以统一提取到此处
|
|
139
137
|
├─expr_codegen
|
|
140
138
|
│ │ expr.py # 表达式处理基本函数
|
|
141
|
-
│ │ tool.py #
|
|
139
|
+
│ │ tool.py # 核心工具代码
|
|
142
140
|
│ ├─polars
|
|
143
141
|
│ │ │ code.py # 针对polars语法的代码生成功能
|
|
144
142
|
│ │ │ template.py.j2 # `Jinja2`模板。用于生成对应py文件,一般不需修改
|
|
@@ -185,13 +183,20 @@ df = codegen_exec(df.lazy(), _code_block_1, _code_block_2).collect(engine="gpu")
|
|
|
185
183
|
1. 根据算子前缀分类(`get_current_by_prefix`),限制算子必需以`ts_`、`cs_`、`gp_`开头
|
|
186
184
|
2. 根据算子全名分类(`get_current_by_name`), 不再限制算子名。比如`cs_rank`可以叫`rank`
|
|
187
185
|
|
|
188
|
-
## Null
|
|
186
|
+
## Null处理
|
|
187
|
+
|
|
188
|
+
`null`是如何产生的?
|
|
189
|
+
|
|
190
|
+
1. 停牌导致。在计算前就直接过滤掉了,不会对后续计算产生影响。
|
|
191
|
+
2. 不同品种交易时段不同
|
|
192
|
+
3. 计算产生。`null`在数列两端不影响后续时序算子结果,但中间出现`null`会影响。例如: `if_else(close<2, None, close)`
|
|
189
193
|
|
|
190
194
|
https://github.com/pola-rs/polars/issues/12925#issuecomment-2552764629
|
|
195
|
+
|
|
191
196
|
非常棒的点子,总结下来有两种实现方式:
|
|
192
197
|
|
|
193
|
-
1. 将`null`分成一组,`not_null
|
|
194
|
-
2. 仅一组,但复合排序,将`null`排在前面,`not_null
|
|
198
|
+
1. 将`null`分成一组,`not_null`分成另一组。要调用两次
|
|
199
|
+
2. 仅一组,但复合排序,将`null`排在前面,`not_null`排后面。只调用一次,略快一些
|
|
195
200
|
|
|
196
201
|
```python
|
|
197
202
|
X1 = (ts_returns(CLOSE, 3)).over(CLOSE.is_not_null(), _ASSET_, order_by=_DATE_),
|
|
@@ -199,17 +204,11 @@ X2 = (ts_returns(CLOSE, 3)).over(_ASSET_, order_by=[CLOSE.is_not_null(), _DATE_]
|
|
|
199
204
|
X3 = (ts_returns(CLOSE, 3)).over(_ASSET_, order_by=_DATE_),
|
|
200
205
|
```
|
|
201
206
|
|
|
202
|
-
第2种开头的`null
|
|
207
|
+
第2种开头的`null`区域,是否影响结果由算子所决定,特别时是多列输入时`null`区域可能有数据
|
|
203
208
|
|
|
204
209
|
1. `over_null='partition_by'`。分到两个区域
|
|
205
210
|
2. `over_null='order_by'`。分到一个区域,`null`排在前面
|
|
206
|
-
3. `over_null=None
|
|
207
|
-
|
|
208
|
-
## 二次开发
|
|
209
|
-
|
|
210
|
-
1. 备份后编辑`demo_express.py`, `import`需要引入的函数
|
|
211
|
-
2. 然后`printer.py`有可能需要添加对应函数的打印代码
|
|
212
|
-
- 注意:需要留意是否要加括号`()`,不加时可能优先级混乱,可以每次都加括号,也可用提供的`parenthesize`简化处理
|
|
211
|
+
3. `over_null=None`。不处理,直接调用,速度更快。如果确信不会中段产生`null`建议使用此参数
|
|
213
212
|
|
|
214
213
|
## `expr_codegen`局限性
|
|
215
214
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.10.6"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|