ygo 1.0.11__py3-none-any.whl → 1.1.0__py3-none-any.whl

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.

Potentially problematic release.


This version of ygo might be problematic. Click here for more details.

ycat/qdf/expr.py DELETED
@@ -1,308 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- ---------------------------------------------
4
- Created on 2025/3/3 19:52
5
- @author: ZhangYundi
6
- @email: yundi.xxii@outlook.com
7
- ---------------------------------------------
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import re
13
- import warnings
14
- from dataclasses import dataclass
15
-
16
- from lark import Lark, Transformer, v_args
17
-
18
- from .errors import ParseError
19
-
20
-
21
- # 基类
22
- class Token:
23
- pass
24
-
25
-
26
- @dataclass
27
- class OperatorToken(Token):
28
- """算子类型token"""
29
- value: str
30
-
31
-
32
- @dataclass
33
- class OperandToken(Token):
34
- """运算对象token"""
35
- value: str | float | int
36
-
37
-
38
- with warnings.catch_warnings():
39
- warnings.simplefilter("ignore")
40
- grammar = """
41
- start: expr
42
- ?expr: ternary_expr
43
- ?ternary_expr: or_expr
44
- | or_expr "?" or_expr ":" ternary_expr -> ternary
45
- ?or_expr: and_expr
46
- | or_expr "|" and_expr -> or_
47
- ?and_expr: comp_expr
48
- | and_expr "&" comp_expr -> and_
49
- ?comp_expr: eq_expr
50
- | comp_expr "<" eq_expr -> lt
51
- | comp_expr ">" eq_expr -> gt
52
- | comp_expr "<=" eq_expr -> le
53
- | comp_expr ">=" eq_expr -> ge
54
- ?eq_expr: arith_expr
55
- | eq_expr "==" arith_expr -> eq
56
- | eq_expr "!=" arith_expr -> neq
57
- ?arith_expr: term
58
- | arith_expr "+" term -> add
59
- | arith_expr "-" term -> sub
60
- ?term: pow_expr
61
- | term "*" pow_expr -> mul
62
- | term "/" pow_expr -> div
63
- | term "//" pow_expr -> floordiv // 取整
64
- | term "%" pow_expr -> mod // 求余
65
- ?pow_expr: factor
66
- | factor "**" pow_expr -> pow
67
- ?factor: atom
68
- | "-" factor -> neg
69
- | "!" factor -> not_
70
- | "~" factor -> not_
71
- ?atom: function
72
- | NAME
73
- | NUMBER
74
- | FLOAT
75
- | "(" expr ")"
76
- | implicit_mul // 隐式乘法
77
- | attribute_access // 新增:属性访问
78
- implicit_mul: (NUMBER | FLOAT) NAME -> implicit_mul // 隐式乘法
79
- attribute_access: atom "." NAME -> attribute_access // 新增:属性访问
80
- function: NAME "(" expr_list ")" -> function
81
- // expr_list: expr ("," expr)*
82
- keyword_arg: NAME "=" expr -> keyword_arg // 关键字参数
83
- expr_list: (expr | keyword_arg) ("," (expr | keyword_arg))* // 支持关键字参数
84
- NAME: /[a-zA-Z_$,][a-zA-Z0-9_$]*/
85
- NUMBER: /\d+/ // regex for numbers
86
- FLOAT: /\d+\.\d+([eE][+-]?\d+)?/ | /\d+[eE][+-]?\d+/ // 支持科学计数法
87
- %import common.WS
88
- %ignore WS
89
- """
90
-
91
-
92
- class ExprParser(Transformer):
93
- @v_args(inline=True)
94
- def ternary(self, a, b, c):
95
- return Expr.new("if_", [a, b, c])
96
-
97
- def attribute_access(self, items):
98
- return ".".join(items)
99
-
100
- def keyword_arg(self, item):
101
- k, v = item
102
- return {k: v}
103
-
104
- def NAME(self, name):
105
- return str(name)
106
-
107
- def NUMBER(self, number): # new transformer for numbers
108
- return int(number)
109
-
110
- def FLOAT(self, number):
111
- return float(number)
112
-
113
- def add(self, items):
114
- return Expr.new("add", items)
115
-
116
- def sub(self, items):
117
- return Expr.new("sub", items)
118
-
119
- def mul(self, items):
120
- return Expr.new("mul", items)
121
-
122
- def div(self, items):
123
- return Expr.new("div", items)
124
-
125
- def floordiv(self, items):
126
- return Expr.new("floordiv", items)
127
-
128
- def mod(self, items):
129
- return Expr.new("mod", items)
130
-
131
- def pow(self, items):
132
- return Expr.new("pow", items)
133
-
134
- def neg(self, items):
135
- item = items[0]
136
- if isinstance(item, (int, float)):
137
- return -item
138
- return Expr.new("neg", items)
139
-
140
- def not_(self, item):
141
- return Expr.new("not_", item)
142
-
143
- def and_(self, items):
144
- return Expr.new("and_", items)
145
-
146
- def or_(self, items):
147
- return Expr.new("or_", items)
148
-
149
- def eq(self, items):
150
- return Expr.new("eq", items)
151
-
152
- def neq(self, items):
153
- return Expr.new("neq", items)
154
-
155
- def lt(self, items):
156
- return Expr.new("lt", items)
157
-
158
- def gt(self, items):
159
- return Expr.new("gt", items)
160
-
161
- def le(self, items):
162
- return Expr.new("le", items)
163
-
164
- def ge(self, items):
165
- return Expr.new("ge", items)
166
-
167
- def function(self, items):
168
- name = items.pop(0)
169
- return Expr.new(name, items[0])
170
-
171
- def implicit_mul(self, items):
172
- return Expr.new("mul", items)
173
-
174
- def expr_list(self, items):
175
- return items
176
-
177
-
178
- parser = Lark(grammar, parser='lalr', transformer=ExprParser())
179
-
180
-
181
- def parse_expr(expression: str) -> Expr:
182
- return parser.parse(expression).children[0]
183
-
184
- class Expr:
185
-
186
- def __init__(self, expr: str | None = None):
187
-
188
- self.fn_name: str | None = ""
189
- self.args: list | None = None
190
- self.alias: str | None = None
191
- if expr:
192
- try:
193
- self._parse(expr)
194
- except Exception as e:
195
- raise ParseError(f"{expr}\n{e}")
196
-
197
- @classmethod
198
- def new(cls, fn_name: str | None, args: list | None, alias: str | None = None):
199
- expr = cls()
200
- expr.fn_name = fn_name
201
- expr.args = args
202
- expr.alias = alias if alias is not None else str(expr)
203
- return expr
204
-
205
- def __hash__(self):
206
- return hash(str(self).strip())
207
-
208
- def __eq__(self, other):
209
- return str(self).strip() == str(other).strip()
210
-
211
-
212
- def to_rpn(self) -> list[Token]:
213
- """生成逆波兰表达式: (后缀表达式: 运算符在后)"""
214
- rpn = list()
215
-
216
- # 递归遍历子表达式
217
- def _traverse(node: Expr):
218
-
219
- if node.args is not None:
220
- for child in node.args:
221
- if isinstance(child, Expr):
222
- _traverse(child)
223
- else:
224
- rpn.append(OperandToken(child))
225
- rpn.append(OperatorToken(node.fn_name))
226
-
227
- _traverse(self)
228
-
229
- return rpn
230
-
231
- def __str__(self):
232
- unary_map = {"neg": "-", "not_": "!"}
233
- binary_map = {"add": "+",
234
- "mul": "*",
235
- "div": "/",
236
- "sub": "-",
237
- "floordiv": "//",
238
- "mod": "%",
239
- "pow": "**",
240
- "and_": "&",
241
- "or_": "|",
242
- "gt": ">",
243
- "gte": ">=",
244
- "lt": "<",
245
- "lte": "<=",
246
- "eq": "==",
247
- "neq": "!=",
248
- }
249
- if self.fn_name is None:
250
- return str(self.args[0])
251
- if self.fn_name == "if_":
252
- cond, body, orelse = self.args
253
- return f"{cond}?{body}:{orelse}"
254
- elif self.fn_name in ("neg", "not_"):
255
- return f"{unary_map.get(self.fn_name)}{self.args[0]}"
256
- elif self.fn_name in binary_map:
257
- return f"({binary_map.get(self.fn_name).join([str(arg) for arg in self.args])})"
258
- else:
259
- return f"{self.fn_name}({', '.join([str(arg) for arg in self.args])})"
260
-
261
- def __repr__(self):
262
- return self.__str__()
263
-
264
- def _parse(self, expr):
265
- """
266
- 解析表达式
267
- """
268
- convertor = {
269
- 'if(': 'if_(',
270
- 'not(': 'not_(',
271
- 'and(': 'and_(',
272
- 'or(': 'or_(',
273
- '$': '',
274
- "\n": '',
275
- "!": "~",
276
- ",": ", ",
277
- }
278
- for old, new in convertor.items():
279
- expr = expr.replace(old, new)
280
- new_expr = expr
281
- match = re.search(r'(?i)(.+?)\s+AS\s+(\w+)', new_expr)
282
- alias = None
283
- if match:
284
- new_expr = match.group(1).strip()
285
- alias = match.group(2).strip()
286
-
287
- expr_ = parse_expr(new_expr)
288
- self.alias = alias if alias is not None else str(expr_)
289
- if not isinstance(expr_, Expr):
290
- self.args = [expr_]
291
- else:
292
- self.fn_name, self.args = expr_.fn_name, expr_.args
293
-
294
-
295
- @property
296
- def n_args(self) -> int:
297
- """返回表达式的参数个数"""
298
- return len(self.args)
299
-
300
- @property
301
- def depth(self) -> int:
302
- """返回表达式的嵌套深度"""
303
- _depth = 1
304
- _depths = [0]
305
- for arg in self.args:
306
- if isinstance(arg, Expr):
307
- _depths.append(arg.depth)
308
- return _depth + max(_depths)
ycat/qdf/qdf.py DELETED
@@ -1,180 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- ---------------------------------------------
4
- Created on 2025/3/5 21:40
5
- @author: ZhangYundi
6
- @email: yundi.xxii@outlook.com
7
- ---------------------------------------------
8
- """
9
- from __future__ import annotations
10
-
11
- import importlib.util
12
- import sys
13
- from functools import lru_cache
14
- from pathlib import Path
15
-
16
- import polars as pl
17
- from toolz import partial
18
-
19
- import ygo
20
- import ylog
21
- from .errors import CalculateError, CompileError, PolarsError, FailError
22
- from .expr import Expr
23
-
24
- # 动态加载模块
25
- module_name = "udf"
26
- module_path = Path(__file__).parent / "udf" / "__init__.py"
27
- spec = importlib.util.spec_from_file_location(module_name, module_path)
28
- module = importlib.util.module_from_spec(spec)
29
- sys.modules[module_name] = module
30
- spec.loader.exec_module(module)
31
-
32
-
33
- @lru_cache(maxsize=512)
34
- def parse_expr(expr: str) -> Expr:
35
- return Expr(expr)
36
-
37
-
38
- class QDF:
39
-
40
- def __init__(self,
41
- data: pl.LazyFrame | pl.DataFrame,
42
- index: tuple[str] = ("date", "time", "asset"),
43
- align: bool = True, ):
44
- assert isinstance(data, (pl.LazyFrame, pl.DataFrame)), "data must be a polars DataFrame or LazyFrame"
45
- self.data = data.with_columns(pl.col(pl.Decimal).cast(pl.Float32).round(5))
46
- if isinstance(self.data, pl.LazyFrame):
47
- self.data = self.data.collect()
48
- self.index = index
49
- self.dims = [self.data[name].drop_nulls().n_unique() for name in index]
50
- if align:
51
- lev_vals: list[pl.DataFrame] = [self.data.select(name).drop_nulls().unique() for name in index]
52
- full_index = lev_vals[0]
53
- for lev_val in lev_vals[1:]:
54
- full_index = full_index.join(lev_val, how="cross")
55
- self.data = full_index.join(self.data, on=index, how='left').sort(index)
56
- self.failed = list()
57
- self._expr_cache = dict() # type: dict[Expr, str]
58
- self._cur_expr_cache = dict()
59
- self._data_: pl.LazyFrame = None
60
-
61
- def __str__(self):
62
- return self.data.__str__()
63
-
64
- def __repr__(self):
65
- return self.data.__str__()
66
-
67
- def register_udf(self, func: callable, name: str = None):
68
- name = name if name is not None else func.__name__
69
- setattr(module, name, func)
70
-
71
- def _compile_expr(self, expr: str, cover: bool):
72
- """str表达式 -> polars 表达式"""
73
- try:
74
- expr_parsed = Expr(expr)
75
- alias = expr_parsed.alias # if expr_parsed.alias is not None else str(expr_parsed)
76
- current_cols = set(self.data.columns)
77
- # columns = self.data.columns
78
- if alias in current_cols and not cover:
79
- return pl.col(alias), alias
80
- # 如果该表达式已有对应列,直接复用
81
- if expr_parsed in self._expr_cache and not cover:
82
- expr_pl: pl.Expr = pl.col(self._expr_cache[expr_parsed]).alias(alias)
83
- return expr_pl, alias
84
- elif expr_parsed in self._cur_expr_cache and not cover:
85
- expr_pl: pl.Expr = pl.col(self._cur_expr_cache[expr_parsed]).alias(alias)
86
- return expr_pl, alias
87
-
88
- def recur_compile(expr_: Expr):
89
- """递归编译"""
90
- alias_ = expr_.alias
91
- if alias_ in current_cols and not cover:
92
- # 已存在:直接select数据源
93
- return pl.col(alias_)
94
- if expr_ in self._expr_cache:
95
- return pl.col(self._expr_cache[expr_]).alias(alias_)
96
- elif expr_ in self._cur_expr_cache:
97
- return pl.col(self._cur_expr_cache[expr_]).alias(alias_)
98
- func = getattr(module, expr_.fn_name)
99
- _params = ygo.fn_signature_params(func)
100
- if "dims" in _params:
101
- func = partial(func, dims=self.dims)
102
- args = list()
103
- kwargs = dict()
104
- for arg in expr_.args:
105
- if isinstance(arg, Expr):
106
- args.append(recur_compile(arg))
107
- elif isinstance(arg, dict):
108
- kwargs.update(arg)
109
- elif isinstance(arg, str):
110
- args.append(pl.col(arg))
111
- else:
112
- args.append(arg) # or args.append(pl.lit(arg))
113
- try:
114
- expr_pl: pl.Expr = func(*args, **kwargs)
115
- self._data_ = self._data_.with_columns(expr_pl.alias(alias_))
116
- self._cur_expr_cache[expr_] = alias_
117
- return pl.col(alias_)
118
- except Exception as e:
119
- raise CompileError(message=f"{expr_.fn_name}({', '.join([str(arg) for arg in args])})\n{e}") from e
120
-
121
- return recur_compile(expr_parsed), alias
122
- except (CalculateError, CompileError, PolarsError) as e:
123
- raise e
124
- except Exception as e:
125
- # 所有未处理的错误统一抛出为 CompileError
126
- raise CompileError(message=f"[编译器外层]\n{e}") from e
127
-
128
- def sql(self, *exprs: str, cover: bool = False, ) -> pl.LazyFrame:
129
- """
130
- 表达式查询
131
- Parameters
132
- ----------
133
- exprs: str
134
- 表达式,比如 "ts_mean(close, 5) as close_ma5"
135
- cover: bool
136
- 当遇到已经存在列名的时候,是否重新计算覆盖原来的列, 默认False,返回已经存在的列,跳过计算
137
- - True: 重新计算并且返回新的结果,覆盖掉原来的列
138
- - False, 返回已经存在的列,跳过计算
139
- Returns
140
- -------
141
- polars.DataFrame
142
- """
143
- self.failed = list()
144
- exprs_to_add = list()
145
- exprs_select = list()
146
- self._cur_expr_cache = {}
147
- self._data_ = self.data.lazy()
148
-
149
- for expr in exprs:
150
- try:
151
- compiled, alias = self._compile_expr(expr, cover)
152
- if compiled is not None:
153
- exprs_to_add.append(compiled)
154
- exprs_select.append(alias)
155
- except Exception as e:
156
- self.failed.append(FailError(expr, e))
157
- if self.failed:
158
- ylog.warning(f"QDF.sql 失败:{len(self.failed)}/{len(exprs)}: \n {self.failed}")
159
- self._data_ = self._data_.with_columns(*exprs_to_add).fill_nan(None)
160
- new_expr_cache = dict()
161
- try:
162
- self.data = self._data_.collect()
163
- final_df = self.data.select(*self.index, *exprs_select)
164
- current_cols = set(self.data.columns)
165
- # 缓存整理:只保留当前表达式的缓存
166
- self._expr_cache.update(self._cur_expr_cache)
167
- for k, v in self._expr_cache.items():
168
- if v in current_cols:
169
- new_expr_cache[k] = v
170
- self._expr_cache = new_expr_cache
171
- return final_df
172
- except Exception as e:
173
- current_cols = set(self.data.columns)
174
- # 缓存整理:只保留当前表达式的缓存
175
- for k, v in self._expr_cache.items():
176
- if v in current_cols:
177
- new_expr_cache[k] = v
178
- self._expr_cache = new_expr_cache
179
- raise PolarsError(message=f"LazyFrame.collect() 阶段出错\n{e}") from e
180
-
ycat/qdf/udf/__init__.py DELETED
@@ -1,14 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- ---------------------------------------------
4
- Created on 2025/3/4 20:20
5
- @author: ZhangYundi
6
- @email: yundi.xxii@outlook.com
7
- ---------------------------------------------
8
- """
9
-
10
- from .base_udf import *
11
- from .cs_udf import *
12
- from .ts_udf import *
13
- from .d_udf import *
14
- from .ind_udf import *
ycat/qdf/udf/base_udf.py DELETED
@@ -1,145 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- ---------------------------------------------
4
- Created on 2025/3/4 20:28
5
- @author: ZhangYundi
6
- @email: yundi.xxii@outlook.com
7
- ---------------------------------------------
8
- """
9
-
10
- import polars as pl
11
- import math
12
-
13
- """
14
- 基本算子:一元算子、二元算子、三元算子 以及 polars 支持的表达式(剔除数据泄露的)
15
- """
16
- # ======================== 一元算子 ========================
17
-
18
- def not_(expr: pl.Expr): return ~expr
19
-
20
-
21
- def neg(expr: pl.Expr): return -expr
22
-
23
-
24
- def abs(expr: pl.Expr): return expr.abs()
25
-
26
-
27
- def log(expr: pl.Expr, base=math.e): return expr.log(base=base)
28
-
29
-
30
- def sqrt(expr: pl.Expr): return expr.sqrt()
31
-
32
-
33
- def square(expr: pl.Expr): return expr ** 2
34
-
35
-
36
- def cube(expr: pl.Expr): return expr ** 3
37
-
38
-
39
- def cbrt(expr: pl.Expr): return expr ** (1 / 3)
40
-
41
-
42
- def sin(expr: pl.Expr): return expr.sin()
43
-
44
- def sinh(expr: pl.Expr): return expr.sinh()
45
-
46
- def arcsin(expr: pl.Expr): return expr.arcsin()
47
-
48
- def arcsinh(expr: pl.Expr): return expr.arcsinh()
49
-
50
-
51
- def cos(expr: pl.Expr): return expr.cos()
52
-
53
- def cosh(expr: pl.Expr): return expr.cosh()
54
-
55
- def arccos(expr: pl.Expr): return expr.arccos()
56
-
57
- def arccosh(expr: pl.Expr): return expr.arccosh()
58
-
59
- def tan(expr: pl.Expr): return expr.tan()
60
-
61
- def tanh(expr: pl.Expr): return expr.tanh()
62
-
63
- def arctan(expr: pl.Expr): return expr.arctan()
64
-
65
- def arctanh(expr: pl.Expr): return expr.arctanh()
66
-
67
-
68
- def sign(expr: pl.Expr): return expr.sign()
69
-
70
-
71
- def sigmoid(expr: pl.Expr): return 1 / (1 + (-expr).exp())
72
-
73
-
74
- # def all(expr: pl.Expr, ignore_nulls: bool = True): return expr.all(ignore_nulls=ignore_nulls)
75
-
76
-
77
- # def any(expr: pl.Expr, ignore_nulls: bool = True): return expr.any(ignore_nulls=ignore_nulls)
78
-
79
- def cot(expr: pl.Expr): return expr.cot()
80
-
81
- def degrees(expr: pl.Expr): return expr.degrees()
82
-
83
- def exp(expr: pl.Expr): return expr.exp()
84
-
85
- def log1p(expr: pl.Expr): return expr.log1p()
86
-
87
- def clip(expr: pl.Expr, lower_bound, upper_bound): return expr.clip(lower_bound, upper_bound)
88
-
89
- # ======================== 二元算子 ========================
90
- def add(left: pl.Expr, right: pl.Expr): return left + right
91
-
92
-
93
- def sub(left: pl.Expr, right: pl.Expr): return left - right
94
-
95
-
96
- def mul(left: pl.Expr, right: pl.Expr): return left * right
97
-
98
-
99
- def div(left: pl.Expr, right: pl.Expr): return left / right
100
-
101
-
102
- def floordiv(left: pl.Expr, right: pl.Expr): return left // right
103
-
104
-
105
- def mod(left: pl.Expr, right: pl.Expr): return left % right
106
-
107
-
108
- def lt(left: pl.Expr, right: pl.Expr): return left < right
109
-
110
-
111
- def le(left: pl.Expr, right: pl.Expr): return left <= right
112
-
113
-
114
- def gt(left: pl.Expr, right: pl.Expr): return left > right
115
-
116
-
117
- def ge(left: pl.Expr, right: pl.Expr): return left >= right
118
-
119
-
120
- def eq(left: pl.Expr, right: pl.Expr): return left == right
121
-
122
-
123
- def neq(left: pl.Expr, right: pl.Expr): return left != right
124
-
125
- def and_(left: pl.Expr, right: pl.Expr): return left & right
126
-
127
- def or_(left: pl.Expr, right: pl.Expr): return left | right
128
-
129
- def max(*exprs: pl.Expr): return pl.max_horizontal(*exprs)
130
-
131
- def min(*exprs: pl.Expr): return pl.min_horizontal(*exprs)
132
-
133
- def sum(*exprs: pl.Expr): return pl.sum_horizontal(*exprs)
134
-
135
-
136
- # ======================== 三元 ========================
137
- def if_(cond: pl.Expr, body: pl.Expr, or_else: pl.Expr):
138
- return pl.when(cond).then(body).otherwise(or_else)
139
-
140
- def fib(high: pl.Expr, low: pl.Expr, ratio: float = 0.618):
141
- """
142
- 计算裴波那契回调比率
143
- ratio: 0.236 | 0.382 | 0.618 等黄金分割比例
144
- """
145
- return low + (high - low) * ratio