py-fastplus 0.2.1__tar.gz → 0.3.0__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.
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/.github/workflows/Publish.yml +7 -2
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/Cargo.lock +1 -1
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/Cargo.toml +1 -1
- py_fastplus-0.3.0/PKG-INFO +286 -0
- py_fastplus-0.3.0/README.md +274 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/python/fastplus/core.pyi +2 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/python/tests/test_all.py +11 -0
- py_fastplus-0.3.0/src/ast.rs +279 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/src/operator.rs +1 -1
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/src/parser.rs +40 -6
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/src/python.rs +10 -1
- py_fastplus-0.2.1/PKG-INFO +0 -32
- py_fastplus-0.2.1/README.md +0 -20
- py_fastplus-0.2.1/src/ast.rs +0 -129
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/.githooks/pre-commit +0 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/.github/workflows/Commit.yml +0 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/.gitignore +0 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/LICENSE +0 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/examples/parse_demo.rs +0 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/pyproject.toml +0 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/python/fastplus/__about__.py +0 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/python/fastplus/__init__.py +0 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/python/fastplus/parser.py +0 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/src/fastplus.pest +0 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/src/field.rs +0 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/src/lib.rs +0 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/src/python_macro.rs +0 -0
- {py_fastplus-0.2.1 → py_fastplus-0.3.0}/uv.lock +0 -0
|
@@ -187,12 +187,17 @@ jobs:
|
|
|
187
187
|
uses: actions/attest@v4
|
|
188
188
|
with:
|
|
189
189
|
subject-path: 'wheels-*/*'
|
|
190
|
+
- name: Recreate latest release
|
|
191
|
+
if: ${{ steps.release-tag.outputs.tag == 'dev-latest' || steps.release-tag.outputs.tag == 'main-latest' }}
|
|
192
|
+
env:
|
|
193
|
+
GH_TOKEN: ${{ github.token }}
|
|
194
|
+
RELEASE_TAG: ${{ steps.release-tag.outputs.tag }}
|
|
195
|
+
run: gh release delete "$RELEASE_TAG" --yes || true
|
|
190
196
|
- name: Create or update GitHub Release
|
|
191
197
|
uses: softprops/action-gh-release@v3
|
|
192
198
|
with:
|
|
193
199
|
tag_name: ${{ steps.release-tag.outputs.tag }}
|
|
194
|
-
|
|
195
|
-
generate_release_notes: false
|
|
200
|
+
generate_release_notes: true
|
|
196
201
|
append_body: false
|
|
197
202
|
overwrite_files: true
|
|
198
203
|
files: wheels-*/*
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-fastplus
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Classifier: Programming Language :: Rust
|
|
5
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Summary: Add your description here
|
|
8
|
+
Author-email: AshSwing <ashswing@email.cn>
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
11
|
+
|
|
12
|
+
# FastPlus
|
|
13
|
+
|
|
14
|
+
FastPlus 是一个基于 Rust 和 PyO3 实现的 WorldQuant Fast Expression 解析器。它把 Alpha 表达式解析为可访问的 Python 对象,并在解析已知操作符时进行参数数量、参数类型和部分操作符专属规则检查。
|
|
15
|
+
|
|
16
|
+
当前实现面向 RegularAlpha,收录了 Expert 权限范围内的 108 个操作符。
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
FastPlus 需要 Python 3.12 及以上版本。
|
|
21
|
+
|
|
22
|
+
从 PyPI 安装:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install py-fastplus
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
如果需要从源码开发:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv sync
|
|
32
|
+
source .venv/bin/activate
|
|
33
|
+
maturin develop --skip-install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
安装后可以在 Python 中导入:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import fastplus
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Python 用法
|
|
43
|
+
|
|
44
|
+
使用 `fastplus.parse` 解析一个 Alpha 表达式:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import fastplus
|
|
48
|
+
|
|
49
|
+
alpha = fastplus.parse("a=ts_delay(close, 5); group_rank(a, industry)")
|
|
50
|
+
|
|
51
|
+
print(alpha.fields)
|
|
52
|
+
print(alpha.operators)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
输出:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
{'matrix': ['close'], 'vector': [], 'group': ['industry']}
|
|
59
|
+
['ts_delay', 'group_rank']
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`Alpha` 提供以下属性:
|
|
63
|
+
|
|
64
|
+
- `assignments`:赋值语句列表,每个元素包含 `variable` 和 `value`。
|
|
65
|
+
- `signal`:最终信号表达式。
|
|
66
|
+
- `fields`:按 `matrix`、`vector`、`group` 分类的数据字段名称。
|
|
67
|
+
- `operators`:表达式中使用的操作符名称列表。
|
|
68
|
+
|
|
69
|
+
解析失败会抛出 `ValueError`。例如:
|
|
70
|
+
|
|
71
|
+
最终 `signal` 不能是 `Group` 或 `Vector`:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
fastplus.parse("bucket(rank(x), range='0, 1, 0.1')")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
--> 1:1
|
|
79
|
+
|
|
|
80
|
+
1 | bucket(rank(x), range='0, 1, 0.1')
|
|
81
|
+
| ^--------------------------------^
|
|
82
|
+
|
|
|
83
|
+
= signal must be Matrix-compatible, but got Group
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`bucket` 不能同时提供有效的 `range` 和 `buckets`:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
fastplus.parse("bucket(rank(x), range='0, 1, 0.1', buckets='0, 0.5, 1')")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
--> 1:1
|
|
94
|
+
|
|
|
95
|
+
1 | bucket(rank(x), range='0, 1, 0.1', buckets='0, 0.5, 1')
|
|
96
|
+
| ^-----------------------------------------------------^
|
|
97
|
+
|
|
|
98
|
+
= operator `bucket` check failed: InvalidExpression("bucket requires exactly one of range and buckets to be valid")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`vec_*` 操作符要求 `Vector`,不能把其他返回 `Matrix` 的操作嵌进去:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
fastplus.parse("vec_avg(rank(x))")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
--> 1:1
|
|
109
|
+
|
|
|
110
|
+
1 | vec_avg(rank(x))
|
|
111
|
+
| ^--------------^
|
|
112
|
+
|
|
|
113
|
+
= operator `vec_avg` check failed: InvalidArgumentType { expected: Vector, actual: Matrix }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
异常信息会同时指出错误位置、操作符名称、错误类型、期望类型和实际类型,便于研究员和大模型定位问题并自动修复表达式。
|
|
117
|
+
|
|
118
|
+
## FastPlus 语法
|
|
119
|
+
|
|
120
|
+
### Alpha 和赋值
|
|
121
|
+
|
|
122
|
+
一个 Alpha 由零个或多个赋值语句和一个最终信号组成:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
[variable = expression;] expression [;]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
例如:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
adv = ts_mean(volume, 20);
|
|
132
|
+
signal = rank(adv);
|
|
133
|
+
signal
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
赋值左侧只能是普通变量,不能使用占位符。最后一个分号可选。
|
|
137
|
+
|
|
138
|
+
### 变量、字段和占位符
|
|
139
|
+
|
|
140
|
+
普通变量由字母开头,只能包含 ASCII 字母、数字和下划线:
|
|
141
|
+
|
|
142
|
+
```text
|
|
143
|
+
close
|
|
144
|
+
industry
|
|
145
|
+
my_signal_1
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
占位符必须使用完整形式:
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
<close_field/>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
目前不支持常数占位符,例如 `<1/>` 或 `<0.5/>`。占位符会统一作为数据字段处理,并在 `Alpha.fields` 中保留完整的 `<`、`/>` 标记。
|
|
155
|
+
|
|
156
|
+
已知操作符会根据参数签名把变量和占位符分类为 `matrix`、`vector` 或 `group`。未知变量在无法确定类型时作为通配类型处理;赋值左侧定义的变量不会被计入 `fields`。
|
|
157
|
+
|
|
158
|
+
### 操作符调用
|
|
159
|
+
|
|
160
|
+
操作符调用格式为:
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
operator(positional_arg, ..., keyword=value, ...)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
位置参数必须写在关键字参数之前,关键字参数只能使用常量:
|
|
167
|
+
|
|
168
|
+
```text
|
|
169
|
+
add(close, volume, filter=true)
|
|
170
|
+
clamp(x, lower=-1, upper=1, inverse=false, mask='mean')
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
已知操作符会检查:
|
|
174
|
+
|
|
175
|
+
- 位置参数数量;
|
|
176
|
+
- 位置参数类型;
|
|
177
|
+
- 关键字名称和类型;
|
|
178
|
+
- 必填关键字参数;
|
|
179
|
+
- 部分操作符专属约束,例如 `k <= d`、`clamp` 的上下界关系,以及 `bucket` 的参数互斥和数组顺序。
|
|
180
|
+
|
|
181
|
+
未知操作符会保留在表达式中,但跳过操作符检查。
|
|
182
|
+
|
|
183
|
+
当前实现中的几个平台差异:
|
|
184
|
+
|
|
185
|
+
- `subtract` 目前只支持两个位置参数。
|
|
186
|
+
- `add`、`multiply`、`max`、`min` 等可变参数操作符至少需要两个位置参数。
|
|
187
|
+
- `jump_decay` 的位置参数是 `x`、`d`,关键字参数是 `stddev`、`sensitivity`、`force`。
|
|
188
|
+
- `ts_rank_gmean_amean_diff` 使用必填的 `lookback` 关键字参数。
|
|
189
|
+
- 其他包含 lookback 语义的时间序列操作符通常把 lookback 作为最后一个位置参数,例如 `ts_backfill(x, d)` 和 `ts_rank(x, d)`。
|
|
190
|
+
- 关键字参数只能使用常量,不能传入变量、占位符或复杂表达式。
|
|
191
|
+
- 操作符至少需要一个位置参数;位置参数和关键字参数不能交错。
|
|
192
|
+
|
|
193
|
+
### 表达式和运算符
|
|
194
|
+
|
|
195
|
+
支持括号、前缀运算、二元运算和三元表达式:
|
|
196
|
+
|
|
197
|
+
```text
|
|
198
|
+
-(x + 1)
|
|
199
|
+
x > 0 && y < 1
|
|
200
|
+
x ? y : z
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
支持的二元运算符包括:
|
|
204
|
+
|
|
205
|
+
```text
|
|
206
|
+
|| && != == >= <= > < + - * /
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
支持的前缀运算符包括:
|
|
210
|
+
|
|
211
|
+
```text
|
|
212
|
+
! + -
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
三元表达式的三个分支都必须是完整表达式。
|
|
216
|
+
|
|
217
|
+
### 常量和关键字参数
|
|
218
|
+
|
|
219
|
+
支持整数、浮点数、布尔值和字符串常量。数值常量会根据值归类为整数、正整数、比例、浮点数等类型;数值标量可以按平台规则广播为 `Matrix`。
|
|
220
|
+
|
|
221
|
+
字符串关键字会经过统一解析,例如:
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
driver='gaussian'
|
|
225
|
+
mask='nearest_bound'
|
|
226
|
+
mask='mean'
|
|
227
|
+
ignore='NaN'
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
还支持范围、数组和集合形式,例如:
|
|
231
|
+
|
|
232
|
+
```text
|
|
233
|
+
range='0, 1, 0.1'
|
|
234
|
+
buckets='0, 0.2, 0.5, 1'
|
|
235
|
+
ignore='NaN 0 1'
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 注释和空白
|
|
239
|
+
|
|
240
|
+
空格、制表符和换行会被忽略。支持以下注释:
|
|
241
|
+
|
|
242
|
+
```text
|
|
243
|
+
// 行注释
|
|
244
|
+
# 行注释
|
|
245
|
+
/* 块注释 */
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## 当前限制和待开发功能
|
|
249
|
+
|
|
250
|
+
### 操作符权限范围
|
|
251
|
+
|
|
252
|
+
当前只有 Expert 权限,因此只能完整检查 Expert 范围内的操作符。未知操作符会跳过检查,以便后续扩展;欢迎提交 Pull Request 补充操作符签名和规则。
|
|
253
|
+
|
|
254
|
+
### 当前不会检查的规则
|
|
255
|
+
|
|
256
|
+
以下表达式按平台规则应该失败,但当前还没有检测:
|
|
257
|
+
|
|
258
|
+
- 定义了但没有使用的变量;
|
|
259
|
+
- 用户定义变量与数据字段重名。
|
|
260
|
+
- 字段分类的交叉校验:如果同一个字段同时出现在 `matrix` 和 `vector`(或其他类别)中,说明至少有一个位置的类型推断错误,目前尚未检测。
|
|
261
|
+
|
|
262
|
+
### 待开发功能
|
|
263
|
+
|
|
264
|
+
- 模板展开:根据占位符自动生成具体表达式。
|
|
265
|
+
- 参数遍历:遍历参数空间并生成参数组合。
|
|
266
|
+
- AST 结构化输出:提供 `.format()` 方法。
|
|
267
|
+
- 表达式编译:提供 `.compile()` 方法,自动合并同类项并优化因子表达式结构。
|
|
268
|
+
- 补充更多权限级别的操作符和操作符专属校验。
|
|
269
|
+
|
|
270
|
+
## 开发验证
|
|
271
|
+
|
|
272
|
+
Rust 测试和检查:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
cargo fmt -- --check
|
|
276
|
+
cargo test --lib
|
|
277
|
+
cargo clippy --all-targets --all-features -- -D warnings
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Python 测试和类型检查:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
uv run pytest
|
|
284
|
+
uv run ty check
|
|
285
|
+
```
|
|
286
|
+
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# FastPlus
|
|
2
|
+
|
|
3
|
+
FastPlus 是一个基于 Rust 和 PyO3 实现的 WorldQuant Fast Expression 解析器。它把 Alpha 表达式解析为可访问的 Python 对象,并在解析已知操作符时进行参数数量、参数类型和部分操作符专属规则检查。
|
|
4
|
+
|
|
5
|
+
当前实现面向 RegularAlpha,收录了 Expert 权限范围内的 108 个操作符。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
FastPlus 需要 Python 3.12 及以上版本。
|
|
10
|
+
|
|
11
|
+
从 PyPI 安装:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install py-fastplus
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
如果需要从源码开发:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv sync
|
|
21
|
+
source .venv/bin/activate
|
|
22
|
+
maturin develop --skip-install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
安装后可以在 Python 中导入:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import fastplus
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Python 用法
|
|
32
|
+
|
|
33
|
+
使用 `fastplus.parse` 解析一个 Alpha 表达式:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import fastplus
|
|
37
|
+
|
|
38
|
+
alpha = fastplus.parse("a=ts_delay(close, 5); group_rank(a, industry)")
|
|
39
|
+
|
|
40
|
+
print(alpha.fields)
|
|
41
|
+
print(alpha.operators)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
输出:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
{'matrix': ['close'], 'vector': [], 'group': ['industry']}
|
|
48
|
+
['ts_delay', 'group_rank']
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`Alpha` 提供以下属性:
|
|
52
|
+
|
|
53
|
+
- `assignments`:赋值语句列表,每个元素包含 `variable` 和 `value`。
|
|
54
|
+
- `signal`:最终信号表达式。
|
|
55
|
+
- `fields`:按 `matrix`、`vector`、`group` 分类的数据字段名称。
|
|
56
|
+
- `operators`:表达式中使用的操作符名称列表。
|
|
57
|
+
|
|
58
|
+
解析失败会抛出 `ValueError`。例如:
|
|
59
|
+
|
|
60
|
+
最终 `signal` 不能是 `Group` 或 `Vector`:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
fastplus.parse("bucket(rank(x), range='0, 1, 0.1')")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
--> 1:1
|
|
68
|
+
|
|
|
69
|
+
1 | bucket(rank(x), range='0, 1, 0.1')
|
|
70
|
+
| ^--------------------------------^
|
|
71
|
+
|
|
|
72
|
+
= signal must be Matrix-compatible, but got Group
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`bucket` 不能同时提供有效的 `range` 和 `buckets`:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
fastplus.parse("bucket(rank(x), range='0, 1, 0.1', buckets='0, 0.5, 1')")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
--> 1:1
|
|
83
|
+
|
|
|
84
|
+
1 | bucket(rank(x), range='0, 1, 0.1', buckets='0, 0.5, 1')
|
|
85
|
+
| ^-----------------------------------------------------^
|
|
86
|
+
|
|
|
87
|
+
= operator `bucket` check failed: InvalidExpression("bucket requires exactly one of range and buckets to be valid")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`vec_*` 操作符要求 `Vector`,不能把其他返回 `Matrix` 的操作嵌进去:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
fastplus.parse("vec_avg(rank(x))")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
--> 1:1
|
|
98
|
+
|
|
|
99
|
+
1 | vec_avg(rank(x))
|
|
100
|
+
| ^--------------^
|
|
101
|
+
|
|
|
102
|
+
= operator `vec_avg` check failed: InvalidArgumentType { expected: Vector, actual: Matrix }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
异常信息会同时指出错误位置、操作符名称、错误类型、期望类型和实际类型,便于研究员和大模型定位问题并自动修复表达式。
|
|
106
|
+
|
|
107
|
+
## FastPlus 语法
|
|
108
|
+
|
|
109
|
+
### Alpha 和赋值
|
|
110
|
+
|
|
111
|
+
一个 Alpha 由零个或多个赋值语句和一个最终信号组成:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
[variable = expression;] expression [;]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
例如:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
adv = ts_mean(volume, 20);
|
|
121
|
+
signal = rank(adv);
|
|
122
|
+
signal
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
赋值左侧只能是普通变量,不能使用占位符。最后一个分号可选。
|
|
126
|
+
|
|
127
|
+
### 变量、字段和占位符
|
|
128
|
+
|
|
129
|
+
普通变量由字母开头,只能包含 ASCII 字母、数字和下划线:
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
close
|
|
133
|
+
industry
|
|
134
|
+
my_signal_1
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
占位符必须使用完整形式:
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
<close_field/>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
目前不支持常数占位符,例如 `<1/>` 或 `<0.5/>`。占位符会统一作为数据字段处理,并在 `Alpha.fields` 中保留完整的 `<`、`/>` 标记。
|
|
144
|
+
|
|
145
|
+
已知操作符会根据参数签名把变量和占位符分类为 `matrix`、`vector` 或 `group`。未知变量在无法确定类型时作为通配类型处理;赋值左侧定义的变量不会被计入 `fields`。
|
|
146
|
+
|
|
147
|
+
### 操作符调用
|
|
148
|
+
|
|
149
|
+
操作符调用格式为:
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
operator(positional_arg, ..., keyword=value, ...)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
位置参数必须写在关键字参数之前,关键字参数只能使用常量:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
add(close, volume, filter=true)
|
|
159
|
+
clamp(x, lower=-1, upper=1, inverse=false, mask='mean')
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
已知操作符会检查:
|
|
163
|
+
|
|
164
|
+
- 位置参数数量;
|
|
165
|
+
- 位置参数类型;
|
|
166
|
+
- 关键字名称和类型;
|
|
167
|
+
- 必填关键字参数;
|
|
168
|
+
- 部分操作符专属约束,例如 `k <= d`、`clamp` 的上下界关系,以及 `bucket` 的参数互斥和数组顺序。
|
|
169
|
+
|
|
170
|
+
未知操作符会保留在表达式中,但跳过操作符检查。
|
|
171
|
+
|
|
172
|
+
当前实现中的几个平台差异:
|
|
173
|
+
|
|
174
|
+
- `subtract` 目前只支持两个位置参数。
|
|
175
|
+
- `add`、`multiply`、`max`、`min` 等可变参数操作符至少需要两个位置参数。
|
|
176
|
+
- `jump_decay` 的位置参数是 `x`、`d`,关键字参数是 `stddev`、`sensitivity`、`force`。
|
|
177
|
+
- `ts_rank_gmean_amean_diff` 使用必填的 `lookback` 关键字参数。
|
|
178
|
+
- 其他包含 lookback 语义的时间序列操作符通常把 lookback 作为最后一个位置参数,例如 `ts_backfill(x, d)` 和 `ts_rank(x, d)`。
|
|
179
|
+
- 关键字参数只能使用常量,不能传入变量、占位符或复杂表达式。
|
|
180
|
+
- 操作符至少需要一个位置参数;位置参数和关键字参数不能交错。
|
|
181
|
+
|
|
182
|
+
### 表达式和运算符
|
|
183
|
+
|
|
184
|
+
支持括号、前缀运算、二元运算和三元表达式:
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
-(x + 1)
|
|
188
|
+
x > 0 && y < 1
|
|
189
|
+
x ? y : z
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
支持的二元运算符包括:
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
|| && != == >= <= > < + - * /
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
支持的前缀运算符包括:
|
|
199
|
+
|
|
200
|
+
```text
|
|
201
|
+
! + -
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
三元表达式的三个分支都必须是完整表达式。
|
|
205
|
+
|
|
206
|
+
### 常量和关键字参数
|
|
207
|
+
|
|
208
|
+
支持整数、浮点数、布尔值和字符串常量。数值常量会根据值归类为整数、正整数、比例、浮点数等类型;数值标量可以按平台规则广播为 `Matrix`。
|
|
209
|
+
|
|
210
|
+
字符串关键字会经过统一解析,例如:
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
driver='gaussian'
|
|
214
|
+
mask='nearest_bound'
|
|
215
|
+
mask='mean'
|
|
216
|
+
ignore='NaN'
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
还支持范围、数组和集合形式,例如:
|
|
220
|
+
|
|
221
|
+
```text
|
|
222
|
+
range='0, 1, 0.1'
|
|
223
|
+
buckets='0, 0.2, 0.5, 1'
|
|
224
|
+
ignore='NaN 0 1'
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 注释和空白
|
|
228
|
+
|
|
229
|
+
空格、制表符和换行会被忽略。支持以下注释:
|
|
230
|
+
|
|
231
|
+
```text
|
|
232
|
+
// 行注释
|
|
233
|
+
# 行注释
|
|
234
|
+
/* 块注释 */
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## 当前限制和待开发功能
|
|
238
|
+
|
|
239
|
+
### 操作符权限范围
|
|
240
|
+
|
|
241
|
+
当前只有 Expert 权限,因此只能完整检查 Expert 范围内的操作符。未知操作符会跳过检查,以便后续扩展;欢迎提交 Pull Request 补充操作符签名和规则。
|
|
242
|
+
|
|
243
|
+
### 当前不会检查的规则
|
|
244
|
+
|
|
245
|
+
以下表达式按平台规则应该失败,但当前还没有检测:
|
|
246
|
+
|
|
247
|
+
- 定义了但没有使用的变量;
|
|
248
|
+
- 用户定义变量与数据字段重名。
|
|
249
|
+
- 字段分类的交叉校验:如果同一个字段同时出现在 `matrix` 和 `vector`(或其他类别)中,说明至少有一个位置的类型推断错误,目前尚未检测。
|
|
250
|
+
|
|
251
|
+
### 待开发功能
|
|
252
|
+
|
|
253
|
+
- 模板展开:根据占位符自动生成具体表达式。
|
|
254
|
+
- 参数遍历:遍历参数空间并生成参数组合。
|
|
255
|
+
- AST 结构化输出:提供 `.format()` 方法。
|
|
256
|
+
- 表达式编译:提供 `.compile()` 方法,自动合并同类项并优化因子表达式结构。
|
|
257
|
+
- 补充更多权限级别的操作符和操作符专属校验。
|
|
258
|
+
|
|
259
|
+
## 开发验证
|
|
260
|
+
|
|
261
|
+
Rust 测试和检查:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
cargo fmt -- --check
|
|
265
|
+
cargo test --lib
|
|
266
|
+
cargo clippy --all-targets --all-features -- -D warnings
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Python 测试和类型检查:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
uv run pytest
|
|
273
|
+
uv run ty check
|
|
274
|
+
```
|
|
@@ -73,3 +73,14 @@ def test_parse_tree_exposes_expression_and_constant_types():
|
|
|
73
73
|
def test_python_parse_reports_operator_type_errors():
|
|
74
74
|
with pytest.raises(ValueError, match="divide"):
|
|
75
75
|
fastplus.parse("divide('not a matrix', 2)")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_alpha_reports_typed_fields_and_operators():
|
|
79
|
+
alpha = fastplus.parse("a=ts_delay(close, 5);group_rank(a, industry)")
|
|
80
|
+
|
|
81
|
+
assert alpha.fields == {
|
|
82
|
+
"matrix": ["close"],
|
|
83
|
+
"vector": [],
|
|
84
|
+
"group": ["industry"],
|
|
85
|
+
}
|
|
86
|
+
assert alpha.operators == ["ts_delay", "group_rank"]
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
//! AST
|
|
2
|
+
//! 唯一结果对象: Alpha
|
|
3
|
+
//! |-> [Assignment;] 赋值语句
|
|
4
|
+
//! |-> [Assignment;] 赋值语句
|
|
5
|
+
//! |-> [...;] ...
|
|
6
|
+
//! |-> signal(Expression) 信号表达式
|
|
7
|
+
//! Alpha 由 N 个赋值语句和 1 个信号表达式组成
|
|
8
|
+
//!
|
|
9
|
+
//! Assignment 赋值语句
|
|
10
|
+
//! |-> variable(String) = value(Expression);
|
|
11
|
+
//!
|
|
12
|
+
//! 核心结构: Expression 表达式
|
|
13
|
+
//! |-> Group(Expression) / Ternary / Binary / Unary 递归结构
|
|
14
|
+
//! |-> Variable / Placeholder 占位结构
|
|
15
|
+
//! |-> Operation* 操作符*
|
|
16
|
+
//! |-> Constant* 常数项*
|
|
17
|
+
//!
|
|
18
|
+
//! 特殊规则:
|
|
19
|
+
//! - 不可以给常数赋值 ❌ -> 刚试了下又可以了(2026-07-28)
|
|
20
|
+
//! - 不可复用数据字段名 -> 非左值 Variable 都是数据字段
|
|
21
|
+
//! - 参数支持字符串/字面值 -> 自动转为字符串解析
|
|
22
|
+
//! - 支持 NaN, 不支持 Inf
|
|
23
|
+
|
|
24
|
+
use std::collections::HashMap;
|
|
25
|
+
use std::collections::HashSet;
|
|
26
|
+
|
|
27
|
+
use crate::field::Constant;
|
|
28
|
+
use crate::field::Field;
|
|
29
|
+
use crate::operator::get_operator;
|
|
30
|
+
|
|
31
|
+
use pest_derive::Parser;
|
|
32
|
+
|
|
33
|
+
#[derive(Parser)]
|
|
34
|
+
#[grammar = "fastplus.pest"]
|
|
35
|
+
pub struct FastPlusParser;
|
|
36
|
+
|
|
37
|
+
#[derive(Debug, Clone)]
|
|
38
|
+
pub struct Alpha {
|
|
39
|
+
assignments: Vec<Assignment>,
|
|
40
|
+
signal: Expression,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
44
|
+
pub struct AlphaFields {
|
|
45
|
+
pub matrix: Vec<String>,
|
|
46
|
+
pub vector: Vec<String>,
|
|
47
|
+
pub group: Vec<String>,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
impl Alpha {
|
|
51
|
+
pub fn new(assignments: Vec<Assignment>, signal: Expression) -> Self {
|
|
52
|
+
Alpha {
|
|
53
|
+
assignments,
|
|
54
|
+
signal,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
pub fn signal(&self) -> &Expression {
|
|
59
|
+
&self.signal
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
pub fn assignments(&self) -> &[Assignment] {
|
|
63
|
+
&self.assignments
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub fn fields(&self) -> AlphaFields {
|
|
67
|
+
let defined: HashSet<_> = self
|
|
68
|
+
.assignments
|
|
69
|
+
.iter()
|
|
70
|
+
.map(|assignment| assignment.variable.clone())
|
|
71
|
+
.collect();
|
|
72
|
+
let mut fields = AlphaFields::default();
|
|
73
|
+
|
|
74
|
+
for assignment in &self.assignments {
|
|
75
|
+
collect_fields(
|
|
76
|
+
&assignment.value,
|
|
77
|
+
&Field::Matrix,
|
|
78
|
+
&defined,
|
|
79
|
+
&mut fields,
|
|
80
|
+
&mut Vec::new(),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
collect_fields(
|
|
84
|
+
&self.signal,
|
|
85
|
+
&Field::Matrix,
|
|
86
|
+
&defined,
|
|
87
|
+
&mut fields,
|
|
88
|
+
&mut Vec::new(),
|
|
89
|
+
);
|
|
90
|
+
fields
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pub fn operators(&self) -> Vec<String> {
|
|
94
|
+
let mut operators = Vec::new();
|
|
95
|
+
for assignment in &self.assignments {
|
|
96
|
+
collect_operators(&assignment.value, &mut operators);
|
|
97
|
+
}
|
|
98
|
+
collect_operators(&self.signal, &mut operators);
|
|
99
|
+
operators
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fn collect_fields(
|
|
104
|
+
expression: &Expression,
|
|
105
|
+
expected: &Field,
|
|
106
|
+
defined: &HashSet<String>,
|
|
107
|
+
fields: &mut AlphaFields,
|
|
108
|
+
operators: &mut Vec<String>,
|
|
109
|
+
) {
|
|
110
|
+
match expression {
|
|
111
|
+
Expression::Variable(name) if !defined.contains(name) => {
|
|
112
|
+
add_field(fields, expected, name.clone());
|
|
113
|
+
}
|
|
114
|
+
Expression::Variable(_) => {}
|
|
115
|
+
Expression::Placeholder(name) => add_field(fields, expected, name.clone()),
|
|
116
|
+
Expression::Group(expression) => {
|
|
117
|
+
collect_fields(expression, expected, defined, fields, operators)
|
|
118
|
+
}
|
|
119
|
+
Expression::Operation {
|
|
120
|
+
op,
|
|
121
|
+
pos_args,
|
|
122
|
+
kw_args: _,
|
|
123
|
+
} => {
|
|
124
|
+
add_operator(operators, op);
|
|
125
|
+
if let Some(operator) = get_operator(op) {
|
|
126
|
+
for (index, argument) in pos_args.iter().enumerate() {
|
|
127
|
+
if let Some(argument_type) = if operator.nary == -1 {
|
|
128
|
+
operator.pos_args.first()
|
|
129
|
+
} else {
|
|
130
|
+
operator.pos_args.get(index)
|
|
131
|
+
} {
|
|
132
|
+
collect_fields(argument, argument_type, defined, fields, operators);
|
|
133
|
+
} else {
|
|
134
|
+
collect_fields(argument, &Field::Matrix, defined, fields, operators);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
for argument in pos_args {
|
|
139
|
+
collect_fields(argument, &Field::Matrix, defined, fields, operators);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
Expression::Ternary {
|
|
144
|
+
cond,
|
|
145
|
+
then_branch,
|
|
146
|
+
else_branch,
|
|
147
|
+
} => {
|
|
148
|
+
collect_fields(cond, &Field::Matrix, defined, fields, operators);
|
|
149
|
+
collect_fields(then_branch, &Field::Matrix, defined, fields, operators);
|
|
150
|
+
collect_fields(else_branch, &Field::Matrix, defined, fields, operators);
|
|
151
|
+
}
|
|
152
|
+
Expression::Binary { left, right, .. } => {
|
|
153
|
+
collect_fields(left, &Field::Matrix, defined, fields, operators);
|
|
154
|
+
collect_fields(right, &Field::Matrix, defined, fields, operators);
|
|
155
|
+
}
|
|
156
|
+
Expression::Unary { expr, .. } => {
|
|
157
|
+
collect_fields(expr, &Field::Matrix, defined, fields, operators)
|
|
158
|
+
}
|
|
159
|
+
Expression::Constant(_) => {}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fn collect_operators(expression: &Expression, operators: &mut Vec<String>) {
|
|
164
|
+
match expression {
|
|
165
|
+
Expression::Operation { op, pos_args, .. } => {
|
|
166
|
+
add_operator(operators, op);
|
|
167
|
+
for argument in pos_args {
|
|
168
|
+
collect_operators(argument, operators);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
Expression::Group(expression) => collect_operators(expression, operators),
|
|
172
|
+
Expression::Ternary {
|
|
173
|
+
cond,
|
|
174
|
+
then_branch,
|
|
175
|
+
else_branch,
|
|
176
|
+
} => {
|
|
177
|
+
collect_operators(cond, operators);
|
|
178
|
+
collect_operators(then_branch, operators);
|
|
179
|
+
collect_operators(else_branch, operators);
|
|
180
|
+
}
|
|
181
|
+
Expression::Binary { left, right, .. } => {
|
|
182
|
+
collect_operators(left, operators);
|
|
183
|
+
collect_operators(right, operators);
|
|
184
|
+
}
|
|
185
|
+
Expression::Unary { expr, .. } => collect_operators(expr, operators),
|
|
186
|
+
Expression::Constant(_) | Expression::Placeholder(_) | Expression::Variable(_) => {}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fn add_field(fields: &mut AlphaFields, expected: &Field, name: String) {
|
|
191
|
+
let target = match expected {
|
|
192
|
+
Field::Vector => &mut fields.vector,
|
|
193
|
+
Field::Group => &mut fields.group,
|
|
194
|
+
_ => &mut fields.matrix,
|
|
195
|
+
};
|
|
196
|
+
if !target.contains(&name) {
|
|
197
|
+
target.push(name);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fn add_operator(operators: &mut Vec<String>, name: &str) {
|
|
202
|
+
if !operators.iter().any(|operator| operator == name) {
|
|
203
|
+
operators.push(name.to_string());
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#[derive(Debug, Clone)]
|
|
208
|
+
pub struct Assignment {
|
|
209
|
+
variable: String,
|
|
210
|
+
value: Expression,
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
impl Assignment {
|
|
214
|
+
pub fn new(variable: String, value: Expression) -> Self {
|
|
215
|
+
Assignment { variable, value }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
pub fn variable(&self) -> &str {
|
|
219
|
+
&self.variable
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
pub fn value(&self) -> &Expression {
|
|
223
|
+
&self.value
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#[derive(Debug, Clone)]
|
|
228
|
+
pub enum BinaryOp {
|
|
229
|
+
NotEqual, // !=
|
|
230
|
+
Equal, // ==
|
|
231
|
+
GreaterEqual, // >=
|
|
232
|
+
LessEqual, // <=
|
|
233
|
+
Greater, // >
|
|
234
|
+
Less, // <
|
|
235
|
+
Add, // +
|
|
236
|
+
Subtract, // -
|
|
237
|
+
Multiply, // *
|
|
238
|
+
Divide, // /
|
|
239
|
+
Or, // ||
|
|
240
|
+
And, // &&
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
#[derive(Debug, Clone)]
|
|
244
|
+
pub enum UnaryOp {
|
|
245
|
+
Positive, // +
|
|
246
|
+
Negative, // -
|
|
247
|
+
Not, // !
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#[derive(Debug, Clone)]
|
|
251
|
+
pub enum Expression {
|
|
252
|
+
Ternary {
|
|
253
|
+
// cond ? true_val : false_val
|
|
254
|
+
cond: Box<Expression>,
|
|
255
|
+
then_branch: Box<Expression>,
|
|
256
|
+
else_branch: Box<Expression>,
|
|
257
|
+
},
|
|
258
|
+
Binary {
|
|
259
|
+
// left op right
|
|
260
|
+
left: Box<Expression>,
|
|
261
|
+
op: BinaryOp,
|
|
262
|
+
right: Box<Expression>,
|
|
263
|
+
},
|
|
264
|
+
Unary {
|
|
265
|
+
// op expr
|
|
266
|
+
op: UnaryOp,
|
|
267
|
+
expr: Box<Expression>,
|
|
268
|
+
},
|
|
269
|
+
Operation {
|
|
270
|
+
// op(args)
|
|
271
|
+
op: String,
|
|
272
|
+
pos_args: Vec<Expression>,
|
|
273
|
+
kw_args: HashMap<String, Constant>,
|
|
274
|
+
},
|
|
275
|
+
Constant(Constant),
|
|
276
|
+
Placeholder(String),
|
|
277
|
+
Variable(String),
|
|
278
|
+
Group(Box<Expression>),
|
|
279
|
+
}
|
|
@@ -1220,7 +1220,7 @@ define_operator_registry! {
|
|
|
1220
1220
|
{
|
|
1221
1221
|
"lower" => (Number, None),
|
|
1222
1222
|
"upper" => (Number, None),
|
|
1223
|
-
"inverse" => (Boolean,
|
|
1223
|
+
"inverse" => (Boolean, Some(Constant::Boolean(false))),
|
|
1224
1224
|
"mask" => (Mask, Some(Constant::NaN)),
|
|
1225
1225
|
},
|
|
1226
1226
|
1,
|
|
@@ -109,15 +109,25 @@ fn build_alpha(pair: Pair<Rule>) -> Result<Alpha, PestError<Rule>> {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
let signal = signal.ok_or(PestError::new_from_span(
|
|
113
|
+
CustomError {
|
|
114
|
+
message: "因子只有赋值语句, 缺少信号表达式".to_string(),
|
|
115
|
+
},
|
|
116
|
+
span,
|
|
117
|
+
))?;
|
|
118
|
+
if let Some(signal_type) = infer_field(&signal)
|
|
119
|
+
&& !Field::Matrix.fit(&signal_type)
|
|
120
|
+
{
|
|
121
|
+
return Err(PestError::new_from_span(
|
|
115
122
|
CustomError {
|
|
116
|
-
message:
|
|
123
|
+
message: format!(
|
|
124
|
+
"signal must be Matrix-compatible, but got {signal_type:?}"
|
|
125
|
+
),
|
|
117
126
|
},
|
|
118
127
|
span,
|
|
119
|
-
))
|
|
120
|
-
|
|
128
|
+
));
|
|
129
|
+
}
|
|
130
|
+
Ok(Alpha::new(assignments, signal))
|
|
121
131
|
}
|
|
122
132
|
other => Err(PestError::new_from_span(
|
|
123
133
|
CustomError {
|
|
@@ -436,6 +446,15 @@ mod tests {
|
|
|
436
446
|
}
|
|
437
447
|
}
|
|
438
448
|
|
|
449
|
+
#[test]
|
|
450
|
+
fn signal_must_be_matrix_compatible() {
|
|
451
|
+
assert!(parse("group_rank(x, industry)").is_ok());
|
|
452
|
+
assert!(parse("vec_avg(<prices/>)").is_ok());
|
|
453
|
+
assert!(parse("42").is_ok());
|
|
454
|
+
assert!(parse("bucket(rank(x), range='0, 1, 0.1')").is_err());
|
|
455
|
+
assert!(parse("bucket(rank(x), buckets='0, 1')").is_err());
|
|
456
|
+
}
|
|
457
|
+
|
|
439
458
|
#[test]
|
|
440
459
|
fn parse_failure() {
|
|
441
460
|
let inputs = [
|
|
@@ -464,6 +483,21 @@ mod tests {
|
|
|
464
483
|
assert!(parse("divide(x)").is_err());
|
|
465
484
|
}
|
|
466
485
|
|
|
486
|
+
#[test]
|
|
487
|
+
fn collects_typed_fields_and_operator_names() {
|
|
488
|
+
let alpha = parse("a=ts_delay(close, 5);group_rank(a, industry)").unwrap();
|
|
489
|
+
let fields = alpha.fields();
|
|
490
|
+
assert_eq!(fields.matrix, vec!["close"]);
|
|
491
|
+
assert_eq!(fields.group, vec!["industry"]);
|
|
492
|
+
assert!(fields.vector.is_empty());
|
|
493
|
+
assert_eq!(alpha.operators(), vec!["ts_delay", "group_rank"]);
|
|
494
|
+
|
|
495
|
+
let alpha = parse("vec_avg(<prices/>)").unwrap();
|
|
496
|
+
let fields = alpha.fields();
|
|
497
|
+
assert_eq!(fields.vector, vec!["<prices/>"]);
|
|
498
|
+
assert!(fields.matrix.is_empty());
|
|
499
|
+
}
|
|
500
|
+
|
|
467
501
|
#[test]
|
|
468
502
|
fn reports_missing_closing_parenthesis() {
|
|
469
503
|
let error = parse("multiply(rank(abs(zscore(<score/>)))").unwrap_err();
|
|
@@ -275,7 +275,16 @@ py_getters!(
|
|
|
275
275
|
},
|
|
276
276
|
signal: PyResult<Py<PyExpression>>, |self, py| {
|
|
277
277
|
build_pyexpression(py, self.0.signal().clone())
|
|
278
|
-
}
|
|
278
|
+
},
|
|
279
|
+
fields: PyResult<Py<PyDict>>, |self, py| {
|
|
280
|
+
let fields = self.0.fields();
|
|
281
|
+
let dict = PyDict::new(py);
|
|
282
|
+
dict.set_item("matrix", fields.matrix)?;
|
|
283
|
+
dict.set_item("vector", fields.vector)?;
|
|
284
|
+
dict.set_item("group", fields.group)?;
|
|
285
|
+
Ok(dict.unbind())
|
|
286
|
+
},
|
|
287
|
+
operators: Vec<String>, |self, _py| { self.0.operators() },
|
|
279
288
|
}
|
|
280
289
|
),
|
|
281
290
|
(
|
py_fastplus-0.2.1/PKG-INFO
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: py-fastplus
|
|
3
|
-
Version: 0.2.1
|
|
4
|
-
Classifier: Programming Language :: Rust
|
|
5
|
-
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Summary: Add your description here
|
|
8
|
-
Author-email: AshSwing <ashswing@email.cn>
|
|
9
|
-
Requires-Python: >=3.12
|
|
10
|
-
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
11
|
-
|
|
12
|
-
# FastPlus - Alpha Expression Language, based on WorldQuant's Fast Expression
|
|
13
|
-
|
|
14
|
-
基于 WorldQuant Fast Expression 开发的因子表达式语法解析器.
|
|
15
|
-
|
|
16
|
-
## 平台规则
|
|
17
|
-
|
|
18
|
-
- 关键字参数全部都可以使用字符串类型
|
|
19
|
-
- 数值标量会自动广播, 可以视作 MATRIX
|
|
20
|
-
|
|
21
|
-
## 当前操作符支持范围
|
|
22
|
-
|
|
23
|
-
当前实现面向 RegularAlpha,仅收录 RegularAlpha 可用的 108 个操作符。原始操作符资料中属于 SuperAlpha 的操作符暂不支持,包括 Combo、Reduce、Special 等类别中的专用操作符。
|
|
24
|
-
|
|
25
|
-
当前与平台行为保持一致的限制:
|
|
26
|
-
|
|
27
|
-
- `subtract` 目前只支持 `nary = 2`,即两个位置参数;平台的无限参数版本尚未实现。
|
|
28
|
-
- `add`、`multiply`、`max`、`min` 等 `nary = -1` 操作符只在签名中声明一个可变参数类型,实际使用时至少需要两个位置参数,由 op-check 负责校验。
|
|
29
|
-
- `jump_decay` 的位置参数为 `x` 和 `d`,关键字参数为 `stddev=false`、`sensitivity=0.5`、`force=0.1`。
|
|
30
|
-
- `ts_rank_gmean_amean_diff` 要求使用 `lookback` 关键字参数,以保持表达式语法的完整性;虽然平台允许将 `d` 作为位置参数传入,FastPlus 当前不采用这种写法。
|
|
31
|
-
- 除 `ts_rank_gmean_amean_diff` 外,包含 lookback 语义的时间序列操作符将 lookback 作为最后一个位置参数,例如 `ts_backfill(x, d)`、`ts_rank(x, d)`。
|
|
32
|
-
|
py_fastplus-0.2.1/README.md
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# FastPlus - Alpha Expression Language, based on WorldQuant's Fast Expression
|
|
2
|
-
|
|
3
|
-
基于 WorldQuant Fast Expression 开发的因子表达式语法解析器.
|
|
4
|
-
|
|
5
|
-
## 平台规则
|
|
6
|
-
|
|
7
|
-
- 关键字参数全部都可以使用字符串类型
|
|
8
|
-
- 数值标量会自动广播, 可以视作 MATRIX
|
|
9
|
-
|
|
10
|
-
## 当前操作符支持范围
|
|
11
|
-
|
|
12
|
-
当前实现面向 RegularAlpha,仅收录 RegularAlpha 可用的 108 个操作符。原始操作符资料中属于 SuperAlpha 的操作符暂不支持,包括 Combo、Reduce、Special 等类别中的专用操作符。
|
|
13
|
-
|
|
14
|
-
当前与平台行为保持一致的限制:
|
|
15
|
-
|
|
16
|
-
- `subtract` 目前只支持 `nary = 2`,即两个位置参数;平台的无限参数版本尚未实现。
|
|
17
|
-
- `add`、`multiply`、`max`、`min` 等 `nary = -1` 操作符只在签名中声明一个可变参数类型,实际使用时至少需要两个位置参数,由 op-check 负责校验。
|
|
18
|
-
- `jump_decay` 的位置参数为 `x` 和 `d`,关键字参数为 `stddev=false`、`sensitivity=0.5`、`force=0.1`。
|
|
19
|
-
- `ts_rank_gmean_amean_diff` 要求使用 `lookback` 关键字参数,以保持表达式语法的完整性;虽然平台允许将 `d` 作为位置参数传入,FastPlus 当前不采用这种写法。
|
|
20
|
-
- 除 `ts_rank_gmean_amean_diff` 外,包含 lookback 语义的时间序列操作符将 lookback 作为最后一个位置参数,例如 `ts_backfill(x, d)`、`ts_rank(x, d)`。
|
py_fastplus-0.2.1/src/ast.rs
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
//! AST
|
|
2
|
-
//! 唯一结果对象: Alpha
|
|
3
|
-
//! |-> [Assignment;] 赋值语句
|
|
4
|
-
//! |-> [Assignment;] 赋值语句
|
|
5
|
-
//! |-> [...;] ...
|
|
6
|
-
//! |-> signal(Expression) 信号表达式
|
|
7
|
-
//! Alpha 由 N 个赋值语句和 1 个信号表达式组成
|
|
8
|
-
//!
|
|
9
|
-
//! Assignment 赋值语句
|
|
10
|
-
//! |-> variable(String) = value(Expression);
|
|
11
|
-
//!
|
|
12
|
-
//! 核心结构: Expression 表达式
|
|
13
|
-
//! |-> Group(Expression) / Ternary / Binary / Unary 递归结构
|
|
14
|
-
//! |-> Variable / Placeholder 占位结构
|
|
15
|
-
//! |-> Operation* 操作符*
|
|
16
|
-
//! |-> Constant* 常数项*
|
|
17
|
-
//!
|
|
18
|
-
//! 特殊规则:
|
|
19
|
-
//! - 不可以给常数赋值 ❌ -> 刚试了下又可以了(2026-07-28)
|
|
20
|
-
//! - 不可复用数据字段名 -> 非左值 Variable 都是数据字段
|
|
21
|
-
//! - 参数支持字符串/字面值 -> 自动转为字符串解析
|
|
22
|
-
//! - 支持 NaN, 不支持 Inf
|
|
23
|
-
|
|
24
|
-
use std::collections::HashMap;
|
|
25
|
-
|
|
26
|
-
use crate::field::Constant;
|
|
27
|
-
|
|
28
|
-
use pest_derive::Parser;
|
|
29
|
-
|
|
30
|
-
#[derive(Parser)]
|
|
31
|
-
#[grammar = "fastplus.pest"]
|
|
32
|
-
pub struct FastPlusParser;
|
|
33
|
-
|
|
34
|
-
#[derive(Debug, Clone)]
|
|
35
|
-
pub struct Alpha {
|
|
36
|
-
assignments: Vec<Assignment>,
|
|
37
|
-
signal: Expression,
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
impl Alpha {
|
|
41
|
-
pub fn new(assignments: Vec<Assignment>, signal: Expression) -> Self {
|
|
42
|
-
Alpha {
|
|
43
|
-
assignments,
|
|
44
|
-
signal,
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
pub fn signal(&self) -> &Expression {
|
|
49
|
-
&self.signal
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
pub fn assignments(&self) -> &[Assignment] {
|
|
53
|
-
&self.assignments
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
#[derive(Debug, Clone)]
|
|
58
|
-
pub struct Assignment {
|
|
59
|
-
variable: String,
|
|
60
|
-
value: Expression,
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
impl Assignment {
|
|
64
|
-
pub fn new(variable: String, value: Expression) -> Self {
|
|
65
|
-
Assignment { variable, value }
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
pub fn variable(&self) -> &str {
|
|
69
|
-
&self.variable
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
pub fn value(&self) -> &Expression {
|
|
73
|
-
&self.value
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
#[derive(Debug, Clone)]
|
|
78
|
-
pub enum BinaryOp {
|
|
79
|
-
NotEqual, // !=
|
|
80
|
-
Equal, // ==
|
|
81
|
-
GreaterEqual, // >=
|
|
82
|
-
LessEqual, // <=
|
|
83
|
-
Greater, // >
|
|
84
|
-
Less, // <
|
|
85
|
-
Add, // +
|
|
86
|
-
Subtract, // -
|
|
87
|
-
Multiply, // *
|
|
88
|
-
Divide, // /
|
|
89
|
-
Or, // ||
|
|
90
|
-
And, // &&
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
#[derive(Debug, Clone)]
|
|
94
|
-
pub enum UnaryOp {
|
|
95
|
-
Positive, // +
|
|
96
|
-
Negative, // -
|
|
97
|
-
Not, // !
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
#[derive(Debug, Clone)]
|
|
101
|
-
pub enum Expression {
|
|
102
|
-
Ternary {
|
|
103
|
-
// cond ? true_val : false_val
|
|
104
|
-
cond: Box<Expression>,
|
|
105
|
-
then_branch: Box<Expression>,
|
|
106
|
-
else_branch: Box<Expression>,
|
|
107
|
-
},
|
|
108
|
-
Binary {
|
|
109
|
-
// left op right
|
|
110
|
-
left: Box<Expression>,
|
|
111
|
-
op: BinaryOp,
|
|
112
|
-
right: Box<Expression>,
|
|
113
|
-
},
|
|
114
|
-
Unary {
|
|
115
|
-
// op expr
|
|
116
|
-
op: UnaryOp,
|
|
117
|
-
expr: Box<Expression>,
|
|
118
|
-
},
|
|
119
|
-
Operation {
|
|
120
|
-
// op(args)
|
|
121
|
-
op: String,
|
|
122
|
-
pos_args: Vec<Expression>,
|
|
123
|
-
kw_args: HashMap<String, Constant>,
|
|
124
|
-
},
|
|
125
|
-
Constant(Constant),
|
|
126
|
-
Placeholder(String),
|
|
127
|
-
Variable(String),
|
|
128
|
-
Group(Box<Expression>),
|
|
129
|
-
}
|
|
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
|