py-alpha-lib 0.2.0__tar.gz → 0.2.2__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_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/CHANGELOG.md +15 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/Cargo.lock +15 -42
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/Cargo.toml +3 -3
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/PKG-INFO +3 -2
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/README.md +2 -1
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/algo/algo_gen.py +24 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/algo.md +1 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/backfill.rs +4 -6
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/context.rs +4 -17
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/cross.rs +37 -26
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/ema.rs +3 -6
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/entropy.rs +2 -3
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/extremum.rs +4 -5
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/group.rs +0 -12
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/ma.rs +7 -9
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/misc.rs +7 -10
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/mod.rs +2 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/moments.rs +6 -8
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/neutralize.rs +2 -7
- py_alpha_lib-0.2.2/src/algo/quantile.rs +224 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/rank.rs +2 -11
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/returns.rs +3 -10
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/scan.rs +4 -8
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/series.rs +10 -14
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/slope.rs +2 -3
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/stats.rs +15 -24
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/stddev.rs +3 -4
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/sum.rs +10 -14
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/zscore.rs +3 -8
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/tests/usage.py +16 -17
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/.agent/skills/add_algo/SKILL.md +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/.github/workflows/CI.yml +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/.gitignore +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/.nwa-config.yaml +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/LICENSE +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/articles/001.md +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/articles/COMPARISON.md +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/benchmarks/benchmark_alpha101.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/benchmarks/benchmark_pandas.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/benchmarks/benchmark_polars_ta.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/build.rs +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/gtja191/al/__init__.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/gtja191/al/alpha191.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/gtja191/al/alpha191_context.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/gtja191/alpha191.txt +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/gtja191/main.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/al/__init__.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/al/alpha101.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/al/alpha101_context.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/alpha101.txt +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/main.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/pd_/__init__.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/pd_/alpha101_adjusted.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/pl_/__init__.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/pl_/alpha101_adjusted.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/result.md +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/pyproject.toml +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/__init__.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/algo/__init__.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/algo/algo.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/context.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/lang/__init__.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/lang/__main__.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/lang/alpha.lark +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/lang/parser.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/lang/to_python.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/conftest.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/tests/test_grammar.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/tests/test_rank.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/tests/test_talib.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/tests/test_to_python.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/rustfmt.toml +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/error.rs +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/skip_nan_window.rs +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/lib.rs +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/tests/rank.py +0 -0
- {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/tests/verify_sumif.py +0 -0
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# ChangeLog
|
|
2
2
|
|
|
3
|
+
## [0.2.2] - 2026-04-11
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- QUANTILE
|
|
8
|
+
|
|
9
|
+
## [0.2.1] - 2026-04-06
|
|
10
|
+
|
|
11
|
+
### Fix
|
|
12
|
+
|
|
13
|
+
- Fixed an internal logic bug in `Context::end` where `_end == 0` incorrectly returned `0` instead of yielding the expected `total` array dimension.
|
|
14
|
+
- Changed Context data truncation (`end` limit) to execute within the closure of each parallel grouped chunk (via `ctx.end()`) rather than globally shrinking arrays ahead of time (`align_end()`). This prevents group size corruption and misaligned cross-sectional indexing when `groups > 1`.
|
|
15
|
+
- Bound internal algorithm iterations and `SkipNanWindow` initializations dynamically to the `end` limit on a per-chunk basis while preserving absolute `start` index zero-alignments.
|
|
16
|
+
- Deprecated and removed legacy `align_end` and `align_end_mut` functions entirely to enforce correct boundary lifecycle patterns across algorithms.
|
|
17
|
+
|
|
3
18
|
|
|
4
19
|
## [0.2.0] - 2026-02-26
|
|
5
20
|
|
|
@@ -4,7 +4,7 @@ version = 4
|
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
6
6
|
name = "alpha"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"anyhow",
|
|
10
10
|
"log",
|
|
@@ -74,15 +74,6 @@ version = "0.5.0"
|
|
|
74
74
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
75
75
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
76
76
|
|
|
77
|
-
[[package]]
|
|
78
|
-
name = "indoc"
|
|
79
|
-
version = "2.0.7"
|
|
80
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
81
|
-
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
|
82
|
-
dependencies = [
|
|
83
|
-
"rustversion",
|
|
84
|
-
]
|
|
85
|
-
|
|
86
77
|
[[package]]
|
|
87
78
|
name = "libc"
|
|
88
79
|
version = "0.2.180"
|
|
@@ -105,15 +96,6 @@ dependencies = [
|
|
|
105
96
|
"rawpointer",
|
|
106
97
|
]
|
|
107
98
|
|
|
108
|
-
[[package]]
|
|
109
|
-
name = "memoffset"
|
|
110
|
-
version = "0.9.1"
|
|
111
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
112
|
-
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
|
113
|
-
dependencies = [
|
|
114
|
-
"autocfg",
|
|
115
|
-
]
|
|
116
|
-
|
|
117
99
|
[[package]]
|
|
118
100
|
name = "ndarray"
|
|
119
101
|
version = "0.17.2"
|
|
@@ -158,9 +140,9 @@ dependencies = [
|
|
|
158
140
|
|
|
159
141
|
[[package]]
|
|
160
142
|
name = "numpy"
|
|
161
|
-
version = "0.
|
|
143
|
+
version = "0.28.0"
|
|
162
144
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
163
|
-
checksum = "
|
|
145
|
+
checksum = "778da78c64ddc928ebf5ad9df5edf0789410ff3bdbf3619aed51cd789a6af1e2"
|
|
164
146
|
dependencies = [
|
|
165
147
|
"libc",
|
|
166
148
|
"ndarray",
|
|
@@ -204,35 +186,32 @@ dependencies = [
|
|
|
204
186
|
|
|
205
187
|
[[package]]
|
|
206
188
|
name = "pyo3"
|
|
207
|
-
version = "0.
|
|
189
|
+
version = "0.28.2"
|
|
208
190
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
209
|
-
checksum = "
|
|
191
|
+
checksum = "cf85e27e86080aafd5a22eae58a162e133a589551542b3e5cee4beb27e54f8e1"
|
|
210
192
|
dependencies = [
|
|
211
|
-
"indoc",
|
|
212
193
|
"libc",
|
|
213
|
-
"memoffset",
|
|
214
194
|
"once_cell",
|
|
215
195
|
"portable-atomic",
|
|
216
196
|
"pyo3-build-config",
|
|
217
197
|
"pyo3-ffi",
|
|
218
198
|
"pyo3-macros",
|
|
219
|
-
"unindent",
|
|
220
199
|
]
|
|
221
200
|
|
|
222
201
|
[[package]]
|
|
223
202
|
name = "pyo3-build-config"
|
|
224
|
-
version = "0.
|
|
203
|
+
version = "0.28.2"
|
|
225
204
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
226
|
-
checksum = "
|
|
205
|
+
checksum = "8bf94ee265674bf76c09fa430b0e99c26e319c945d96ca0d5a8215f31bf81cf7"
|
|
227
206
|
dependencies = [
|
|
228
207
|
"target-lexicon",
|
|
229
208
|
]
|
|
230
209
|
|
|
231
210
|
[[package]]
|
|
232
211
|
name = "pyo3-ffi"
|
|
233
|
-
version = "0.
|
|
212
|
+
version = "0.28.2"
|
|
234
213
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
235
|
-
checksum = "
|
|
214
|
+
checksum = "491aa5fc66d8059dd44a75f4580a2962c1862a1c2945359db36f6c2818b748dc"
|
|
236
215
|
dependencies = [
|
|
237
216
|
"libc",
|
|
238
217
|
"pyo3-build-config",
|
|
@@ -240,9 +219,9 @@ dependencies = [
|
|
|
240
219
|
|
|
241
220
|
[[package]]
|
|
242
221
|
name = "pyo3-log"
|
|
243
|
-
version = "0.13.
|
|
222
|
+
version = "0.13.3"
|
|
244
223
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
245
|
-
checksum = "
|
|
224
|
+
checksum = "26c2ec80932c5c3b2d4fbc578c9b56b2d4502098587edb8bef5b6bfcad43682e"
|
|
246
225
|
dependencies = [
|
|
247
226
|
"arc-swap",
|
|
248
227
|
"log",
|
|
@@ -251,9 +230,9 @@ dependencies = [
|
|
|
251
230
|
|
|
252
231
|
[[package]]
|
|
253
232
|
name = "pyo3-macros"
|
|
254
|
-
version = "0.
|
|
233
|
+
version = "0.28.2"
|
|
255
234
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
256
|
-
checksum = "
|
|
235
|
+
checksum = "f5d671734e9d7a43449f8480f8b38115df67bef8d21f76837fa75ee7aaa5e52e"
|
|
257
236
|
dependencies = [
|
|
258
237
|
"proc-macro2",
|
|
259
238
|
"pyo3-macros-backend",
|
|
@@ -263,9 +242,9 @@ dependencies = [
|
|
|
263
242
|
|
|
264
243
|
[[package]]
|
|
265
244
|
name = "pyo3-macros-backend"
|
|
266
|
-
version = "0.
|
|
245
|
+
version = "0.28.2"
|
|
267
246
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
268
|
-
checksum = "
|
|
247
|
+
checksum = "22faaa1ce6c430a1f71658760497291065e6450d7b5dc2bcf254d49f66ee700a"
|
|
269
248
|
dependencies = [
|
|
270
249
|
"heck",
|
|
271
250
|
"proc-macro2",
|
|
@@ -363,9 +342,3 @@ name = "unicode-ident"
|
|
|
363
342
|
version = "1.0.22"
|
|
364
343
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
365
344
|
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
|
366
|
-
|
|
367
|
-
[[package]]
|
|
368
|
-
name = "unindent"
|
|
369
|
-
version = "0.2.4"
|
|
370
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
371
|
-
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "alpha"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
authors = ["ElseJJ"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -11,10 +11,10 @@ name = "alpha"
|
|
|
11
11
|
crate-type = ["cdylib", "rlib"]
|
|
12
12
|
|
|
13
13
|
[dependencies]
|
|
14
|
-
pyo3 = { version = "0.
|
|
14
|
+
pyo3 = { version = "0.28", features = ["abi3"] }
|
|
15
15
|
pyo3-log = "0.13"
|
|
16
16
|
thiserror = "2"
|
|
17
|
-
numpy = "0.
|
|
17
|
+
numpy = "0.28"
|
|
18
18
|
num-traits = "0.2"
|
|
19
19
|
rayon = "1.11"
|
|
20
20
|
log = "0.4"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: py-alpha-lib
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
@@ -71,7 +71,7 @@ Control computation behavior via `alpha.set_ctx()`:
|
|
|
71
71
|
alpha.set_ctx(flags=alpha.FLAG_SKIP_NAN)
|
|
72
72
|
data_nan = np.array([1, 2, np.nan, 4, 5, 6, 7, 8, 9, 10], dtype=np.float64)
|
|
73
73
|
result = alpha.MA(data_nan, 3)
|
|
74
|
-
#
|
|
74
|
+
#[1. 1.5 nan 2.333 3.667 5. 6. 7. 8. 9. ]
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
|
|
@@ -203,6 +203,7 @@ Naming Rules:
|
|
|
203
203
|
| MOMENT | Calculate rolling k-th central moment over a moving window |
|
|
204
204
|
| NEUTRALIZE | Neutralize the effect of a categorical variable on a numeric variable |
|
|
205
205
|
| PRODUCT | Calculate product of values in preceding `periods` window |
|
|
206
|
+
| QUANTILE | Calculate rolling quantile over a moving window |
|
|
206
207
|
| RANK | Calculate rank in a sliding window with size `periods` |
|
|
207
208
|
| RCROSS | For 2 arrays A and B, return true if A[i-1] > B[i-1] and A[i] <= B[i] alias: death_cross, cross_le |
|
|
208
209
|
| REF | Right shift input array by `periods`, r[i] = input[i - periods] |
|
|
@@ -56,7 +56,7 @@ Control computation behavior via `alpha.set_ctx()`:
|
|
|
56
56
|
alpha.set_ctx(flags=alpha.FLAG_SKIP_NAN)
|
|
57
57
|
data_nan = np.array([1, 2, np.nan, 4, 5, 6, 7, 8, 9, 10], dtype=np.float64)
|
|
58
58
|
result = alpha.MA(data_nan, 3)
|
|
59
|
-
#
|
|
59
|
+
#[1. 1.5 nan 2.333 3.667 5. 6. 7. 8. 9. ]
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
|
|
@@ -188,6 +188,7 @@ Naming Rules:
|
|
|
188
188
|
| MOMENT | Calculate rolling k-th central moment over a moving window |
|
|
189
189
|
| NEUTRALIZE | Neutralize the effect of a categorical variable on a numeric variable |
|
|
190
190
|
| PRODUCT | Calculate product of values in preceding `periods` window |
|
|
191
|
+
| QUANTILE | Calculate rolling quantile over a moving window |
|
|
191
192
|
| RANK | Calculate rank in a sliding window with size `periods` |
|
|
192
193
|
| RCROSS | For 2 arrays A and B, return true if A[i-1] > B[i-1] and A[i] <= B[i] alias: death_cross, cross_le |
|
|
193
194
|
| REF | Right shift input array by `periods`, r[i] = input[i - periods] |
|
|
@@ -623,6 +623,30 @@ def PRODUCT(
|
|
|
623
623
|
_algo.product(r, input, periods)
|
|
624
624
|
return r
|
|
625
625
|
|
|
626
|
+
def QUANTILE(
|
|
627
|
+
input: np.ndarray | list[np.ndarray], periods: int, q: float
|
|
628
|
+
) -> np.ndarray | list[np.ndarray]:
|
|
629
|
+
"""
|
|
630
|
+
Calculate rolling quantile over a moving window
|
|
631
|
+
|
|
632
|
+
QUANTILE(x, d, q) returns the q-th quantile (0 <= q <= 1) of values
|
|
633
|
+
in the preceding d periods. Uses linear interpolation between data points
|
|
634
|
+
(matching numpy/pandas percentile with interpolation='linear').
|
|
635
|
+
NaN values are excluded from the computation. Requires at least 1 valid value.
|
|
636
|
+
|
|
637
|
+
Ref: https://numpy.org/doc/stable/reference/generated/numpy.quantile.html
|
|
638
|
+
"""
|
|
639
|
+
if isinstance(input, list):
|
|
640
|
+
input = [_to_f64(x) for x in input]
|
|
641
|
+
r = [np.empty_like(x) for x in input]
|
|
642
|
+
_algo.quantile(r, input, periods, q)
|
|
643
|
+
return r
|
|
644
|
+
else:
|
|
645
|
+
input = _to_f64(input)
|
|
646
|
+
r = np.empty_like(input)
|
|
647
|
+
_algo.quantile(r, input, periods, q)
|
|
648
|
+
return r
|
|
649
|
+
|
|
626
650
|
def RANK(
|
|
627
651
|
input: np.ndarray | list[np.ndarray], periods: int
|
|
628
652
|
) -> np.ndarray | list[np.ndarray]:
|
|
@@ -33,6 +33,7 @@ the `np.ndarray` is `ndarray` type in `numpy` package
|
|
|
33
33
|
- MOMENT(input: np.ndarray[float], periods: int, k: int): Calculate rolling k-th central moment over a moving window MOMENT(x, d, k) = mean((x - mean)^k) over window of d periods. This is the raw (non-adjusted) sample moment. k=2 gives variance (population), k=3 gives raw third moment, etc.
|
|
34
34
|
- NEUTRALIZE(category: np.ndarray[float], input: np.ndarray[float]): Neutralize the effect of a categorical variable on a numeric variable
|
|
35
35
|
- PRODUCT(input: np.ndarray[float], periods: int): Calculate product of values in preceding `periods` window If periods is 0, it calculates the cumulative product from the first valid value.
|
|
36
|
+
- QUANTILE(input: np.ndarray[float], periods: int, q: float): Calculate rolling quantile over a moving window QUANTILE(x, d, q) returns the q-th quantile (0 <= q <= 1) of values in the preceding d periods. Uses linear interpolation between data points (matching numpy/pandas percentile with interpolation='linear'). NaN values are excluded from the computation. Requires at least 1 valid value.
|
|
36
37
|
- RANK(input: np.ndarray[float], periods: int): Calculate rank in a sliding window with size `periods` Uses min-rank method for ties (same as pandas rankdata method='min'). NaN values are treated as larger than all non-NaN values.
|
|
37
38
|
- RCROSS(a: np.ndarray[float], b: np.ndarray[float]): For 2 arrays A and B, return true if A[i-1] > B[i-1] and A[i] <= B[i] alias: death_cross, cross_le
|
|
38
39
|
- REF(input: np.ndarray[float], periods: int): Right shift input array by `periods`, r[i] = input[i - periods]
|
|
@@ -19,18 +19,17 @@ pub fn ta_backfill<NumT: Float + Send + Sync>(
|
|
|
19
19
|
return Err(Error::LengthMismatch(r.len(), input.len()));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
let r = ctx.align_end_mut(r);
|
|
23
|
-
let input = ctx.align_end(input);
|
|
24
22
|
|
|
25
23
|
r.par_chunks_mut(ctx.chunk_size(r.len()))
|
|
26
24
|
.zip(input.par_chunks(ctx.chunk_size(input.len())))
|
|
27
25
|
.for_each(|(r, x)| {
|
|
28
26
|
let start = ctx.start(r.len());
|
|
27
|
+
let end = ctx.end(r.len());
|
|
29
28
|
r[..start].fill(NumT::nan());
|
|
30
29
|
|
|
31
30
|
let mut last_valid = NumT::nan();
|
|
32
31
|
|
|
33
|
-
for i in start..
|
|
32
|
+
for i in start..end {
|
|
34
33
|
let val = x[i];
|
|
35
34
|
if is_normal(&val) {
|
|
36
35
|
last_valid = val;
|
|
@@ -59,13 +58,12 @@ pub fn ta_count_nans<NumT: Float + Send + Sync>(
|
|
|
59
58
|
return Err(Error::LengthMismatch(r.len(), input.len()));
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
let r = ctx.align_end_mut(r);
|
|
63
|
-
let input = ctx.align_end(input);
|
|
64
61
|
|
|
65
62
|
r.par_chunks_mut(ctx.chunk_size(r.len()))
|
|
66
63
|
.zip(input.par_chunks(ctx.chunk_size(input.len())))
|
|
67
64
|
.for_each(|(r, x)| {
|
|
68
65
|
let start = ctx.start(r.len());
|
|
66
|
+
let end = ctx.end(r.len());
|
|
69
67
|
r.fill(NumT::nan());
|
|
70
68
|
|
|
71
69
|
let mut nan_count: usize = 0;
|
|
@@ -78,7 +76,7 @@ pub fn ta_count_nans<NumT: Float + Send + Sync>(
|
|
|
78
76
|
}
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
for i in start..
|
|
79
|
+
for i in start..end {
|
|
82
80
|
let val = x[i];
|
|
83
81
|
if !is_normal(&val) {
|
|
84
82
|
nan_count += 1;
|
|
@@ -40,27 +40,14 @@ impl Context {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
pub fn align_end_mut<'a, T>(&'a self, r: &'a mut [T]) -> &'a mut [T] {
|
|
44
|
-
if self._end == 0 {
|
|
45
|
-
return r;
|
|
46
|
-
}
|
|
47
|
-
let end = self.end(r.len());
|
|
48
|
-
&mut r[..end]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
pub fn align_end<'a, T>(&'a self, r: &'a [T]) -> &'a [T] {
|
|
52
|
-
if self._end == 0 {
|
|
53
|
-
return r;
|
|
54
|
-
}
|
|
55
|
-
let end = self.end(r.len());
|
|
56
|
-
&r[..end]
|
|
57
|
-
}
|
|
58
|
-
|
|
59
43
|
pub fn end(&self, total: usize) -> usize {
|
|
60
44
|
if total == 0 {
|
|
61
45
|
return 0;
|
|
62
46
|
}
|
|
63
|
-
if self._end
|
|
47
|
+
if self._end == 0 {
|
|
48
|
+
return total;
|
|
49
|
+
}
|
|
50
|
+
if self._end > 0 {
|
|
64
51
|
(total).min(self._end as usize)
|
|
65
52
|
} else {
|
|
66
53
|
0.max(total as i32 + self._end) as usize
|
|
@@ -19,15 +19,12 @@ pub fn ta_cross<NumT: Float + Send + Sync>(
|
|
|
19
19
|
return Err(Error::LengthMismatch(r.len(), a.len()));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
let r = ctx.align_end_mut(r);
|
|
23
|
-
let a = ctx.align_end(a);
|
|
24
|
-
let b = ctx.align_end(b);
|
|
25
|
-
|
|
26
22
|
r.par_chunks_mut(ctx.chunk_size(r.len()))
|
|
27
23
|
.zip(a.par_chunks(ctx.chunk_size(a.len())))
|
|
28
24
|
.zip(b.par_chunks(ctx.chunk_size(b.len())))
|
|
29
25
|
.for_each(|((r, a), b)| {
|
|
30
26
|
let start = ctx.start(r.len());
|
|
27
|
+
let end = ctx.end(r.len());
|
|
31
28
|
r.fill(false);
|
|
32
29
|
|
|
33
30
|
if ctx.is_skip_nan() {
|
|
@@ -41,7 +38,7 @@ pub fn ta_cross<NumT: Float + Send + Sync>(
|
|
|
41
38
|
|
|
42
39
|
let mut was_less: Option<bool> = None;
|
|
43
40
|
|
|
44
|
-
for i in start..
|
|
41
|
+
for i in start..end {
|
|
45
42
|
let val_a = a[i];
|
|
46
43
|
let val_b = b[i];
|
|
47
44
|
|
|
@@ -60,7 +57,7 @@ pub fn ta_cross<NumT: Float + Send + Sync>(
|
|
|
60
57
|
}
|
|
61
58
|
} else {
|
|
62
59
|
// Normal logic (look at i-1)
|
|
63
|
-
for i in start..
|
|
60
|
+
for i in start..end {
|
|
64
61
|
if i == 0 {
|
|
65
62
|
continue;
|
|
66
63
|
}
|
|
@@ -94,21 +91,18 @@ pub fn ta_rcross<NumT: Float + Send + Sync>(
|
|
|
94
91
|
return Err(Error::LengthMismatch(r.len(), a.len()));
|
|
95
92
|
}
|
|
96
93
|
|
|
97
|
-
let r = ctx.align_end_mut(r);
|
|
98
|
-
let a = ctx.align_end(a);
|
|
99
|
-
let b = ctx.align_end(b);
|
|
100
|
-
|
|
101
94
|
r.par_chunks_mut(ctx.chunk_size(r.len()))
|
|
102
95
|
.zip(a.par_chunks(ctx.chunk_size(a.len())))
|
|
103
96
|
.zip(b.par_chunks(ctx.chunk_size(b.len())))
|
|
104
97
|
.for_each(|((r, a), b)| {
|
|
105
98
|
let start = ctx.start(r.len());
|
|
99
|
+
let end = ctx.end(r.len());
|
|
106
100
|
r.fill(false);
|
|
107
101
|
|
|
108
102
|
if ctx.is_skip_nan() {
|
|
109
103
|
let mut was_greater: Option<bool> = None;
|
|
110
104
|
|
|
111
|
-
for i in start..
|
|
105
|
+
for i in start..end {
|
|
112
106
|
let val_a = a[i];
|
|
113
107
|
let val_b = b[i];
|
|
114
108
|
|
|
@@ -126,7 +120,7 @@ pub fn ta_rcross<NumT: Float + Send + Sync>(
|
|
|
126
120
|
}
|
|
127
121
|
}
|
|
128
122
|
} else {
|
|
129
|
-
for i in start..
|
|
123
|
+
for i in start..end {
|
|
130
124
|
if i == 0 {
|
|
131
125
|
continue;
|
|
132
126
|
}
|
|
@@ -160,15 +154,12 @@ pub fn ta_longcross<NumT: Float + Send + Sync>(
|
|
|
160
154
|
return Err(Error::LengthMismatch(r.len(), a.len()));
|
|
161
155
|
}
|
|
162
156
|
|
|
163
|
-
let r = ctx.align_end_mut(r);
|
|
164
|
-
let a = ctx.align_end(a);
|
|
165
|
-
let b = ctx.align_end(b);
|
|
166
|
-
|
|
167
157
|
r.par_chunks_mut(ctx.chunk_size(r.len()))
|
|
168
158
|
.zip(a.par_chunks(ctx.chunk_size(a.len())))
|
|
169
159
|
.zip(b.par_chunks(ctx.chunk_size(b.len())))
|
|
170
160
|
.for_each(|((r, a), b)| {
|
|
171
161
|
let start = ctx.start(r.len());
|
|
162
|
+
let end = ctx.end(r.len());
|
|
172
163
|
r.fill(false);
|
|
173
164
|
|
|
174
165
|
if n == 0 {
|
|
@@ -180,7 +171,7 @@ pub fn ta_longcross<NumT: Float + Send + Sync>(
|
|
|
180
171
|
// Let's assume if N=0 it requires at least 1 previous period?
|
|
181
172
|
// Usually N >= 1 for such funcs.
|
|
182
173
|
// If N=0, let's treat as just A >= B
|
|
183
|
-
for i in start..
|
|
174
|
+
for i in start..end {
|
|
184
175
|
if is_normal(&a[i]) && is_normal(&b[i]) {
|
|
185
176
|
if a[i] >= b[i] {
|
|
186
177
|
r[i] = true;
|
|
@@ -205,7 +196,7 @@ pub fn ta_longcross<NumT: Float + Send + Sync>(
|
|
|
205
196
|
|
|
206
197
|
let mut less_count = 0;
|
|
207
198
|
|
|
208
|
-
for i in start..
|
|
199
|
+
for i in start..end {
|
|
209
200
|
let val_a = a[i];
|
|
210
201
|
let val_b = b[i];
|
|
211
202
|
|
|
@@ -240,7 +231,7 @@ pub fn ta_longcross<NumT: Float + Send + Sync>(
|
|
|
240
231
|
}
|
|
241
232
|
}
|
|
242
233
|
|
|
243
|
-
for i in start..
|
|
234
|
+
for i in start..end {
|
|
244
235
|
let curr_a = a[i];
|
|
245
236
|
let curr_b = b[i];
|
|
246
237
|
|
|
@@ -276,19 +267,16 @@ pub fn ta_rlongcross<NumT: Float + Send + Sync>(
|
|
|
276
267
|
return Err(Error::LengthMismatch(r.len(), a.len()));
|
|
277
268
|
}
|
|
278
269
|
|
|
279
|
-
let r = ctx.align_end_mut(r);
|
|
280
|
-
let a = ctx.align_end(a);
|
|
281
|
-
let b = ctx.align_end(b);
|
|
282
|
-
|
|
283
270
|
r.par_chunks_mut(ctx.chunk_size(r.len()))
|
|
284
271
|
.zip(a.par_chunks(ctx.chunk_size(a.len())))
|
|
285
272
|
.zip(b.par_chunks(ctx.chunk_size(b.len())))
|
|
286
273
|
.for_each(|((r, a), b)| {
|
|
287
274
|
let start = ctx.start(r.len());
|
|
275
|
+
let end = ctx.end(r.len());
|
|
288
276
|
r.fill(false);
|
|
289
277
|
|
|
290
278
|
if n == 0 {
|
|
291
|
-
for i in start..
|
|
279
|
+
for i in start..end {
|
|
292
280
|
if is_normal(&a[i]) && is_normal(&b[i]) {
|
|
293
281
|
if a[i] <= b[i] {
|
|
294
282
|
r[i] = true;
|
|
@@ -301,7 +289,7 @@ pub fn ta_rlongcross<NumT: Float + Send + Sync>(
|
|
|
301
289
|
if ctx.is_skip_nan() {
|
|
302
290
|
let mut greater_count = 0;
|
|
303
291
|
|
|
304
|
-
for i in start..
|
|
292
|
+
for i in start..end {
|
|
305
293
|
let val_a = a[i];
|
|
306
294
|
let val_b = b[i];
|
|
307
295
|
|
|
@@ -329,7 +317,7 @@ pub fn ta_rlongcross<NumT: Float + Send + Sync>(
|
|
|
329
317
|
}
|
|
330
318
|
}
|
|
331
319
|
|
|
332
|
-
for i in start..
|
|
320
|
+
for i in start..end {
|
|
333
321
|
let curr_a = a[i];
|
|
334
322
|
let curr_b = b[i];
|
|
335
323
|
|
|
@@ -425,4 +413,27 @@ mod tests {
|
|
|
425
413
|
|
|
426
414
|
assert_vec_eq_bool(&r, &vec![false, false, false, true]);
|
|
427
415
|
}
|
|
416
|
+
|
|
417
|
+
#[test]
|
|
418
|
+
fn test_longcross_end_boundary() {
|
|
419
|
+
let a = vec![1.0, 2.0, 3.0, 6.0, 7.0, 1.0, 2.0, 3.0, 6.0, 7.0];
|
|
420
|
+
let b = vec![5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0];
|
|
421
|
+
|
|
422
|
+
let mut r = vec![false; 10];
|
|
423
|
+
let mut ctx = Context::new(0, 2, 0);
|
|
424
|
+
for i in 4..=5 {
|
|
425
|
+
ctx._end = i;
|
|
426
|
+
ta_longcross(&ctx, &mut r, &a, &b, 2).unwrap();
|
|
427
|
+
// 0: cnt=1
|
|
428
|
+
// 1: cnt=2
|
|
429
|
+
// 2: 3<5. cnt=3 >= 2. -> T. cnt=0.
|
|
430
|
+
|
|
431
|
+
assert_vec_eq_bool(
|
|
432
|
+
&r,
|
|
433
|
+
&vec![
|
|
434
|
+
false, false, false, true, false, false, false, false, true, false,
|
|
435
|
+
],
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
428
439
|
}
|
|
@@ -64,8 +64,6 @@ pub fn ta_lwma<NumT: Float + Send + Sync>(
|
|
|
64
64
|
return Err(Error::LengthMismatch(r.len(), input.len()));
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
let r = ctx.align_end_mut(r);
|
|
68
|
-
let input = ctx.align_end(input);
|
|
69
67
|
|
|
70
68
|
if periods == 1 {
|
|
71
69
|
r.copy_from_slice(input);
|
|
@@ -79,10 +77,11 @@ pub fn ta_lwma<NumT: Float + Send + Sync>(
|
|
|
79
77
|
.zip(input.par_chunks(ctx.chunk_size(input.len())))
|
|
80
78
|
.for_each(|(r, x)| {
|
|
81
79
|
let start = ctx.start(r.len());
|
|
80
|
+
let end = ctx.end(r.len());
|
|
82
81
|
r.fill(NumT::nan());
|
|
83
82
|
|
|
84
83
|
if ctx.is_skip_nan() {
|
|
85
|
-
let iter = SkipNanWindow::new(x, periods, start);
|
|
84
|
+
let iter = SkipNanWindow::new(&x[..end], periods, start);
|
|
86
85
|
let mut simple_sum = NumT::zero();
|
|
87
86
|
let mut weighted_sum = NumT::zero();
|
|
88
87
|
|
|
@@ -155,7 +154,7 @@ pub fn ta_lwma<NumT: Float + Send + Sync>(
|
|
|
155
154
|
}
|
|
156
155
|
}
|
|
157
156
|
|
|
158
|
-
for i in start..
|
|
157
|
+
for i in start..end {
|
|
159
158
|
let val = x[i];
|
|
160
159
|
|
|
161
160
|
// Add new
|
|
@@ -220,8 +219,6 @@ pub fn ema_impl<NumT: Float + Send + Sync>(
|
|
|
220
219
|
return Err(Error::LengthMismatch(r.len(), input.len()));
|
|
221
220
|
}
|
|
222
221
|
|
|
223
|
-
let r = ctx.align_end_mut(r);
|
|
224
|
-
let input = ctx.align_end(input);
|
|
225
222
|
|
|
226
223
|
if weight < NumT::zero() || weight > NumT::one() {
|
|
227
224
|
return Err(Error::InvalidParameter(
|
|
@@ -23,8 +23,6 @@ pub fn ta_entropy<NumT: Float + Send + Sync>(
|
|
|
23
23
|
return Err(Error::LengthMismatch(r.len(), input.len()));
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
let r = ctx.align_end_mut(r);
|
|
27
|
-
let input = ctx.align_end(input);
|
|
28
26
|
|
|
29
27
|
let bins = if bins == 0 { 10 } else { bins };
|
|
30
28
|
|
|
@@ -32,13 +30,14 @@ pub fn ta_entropy<NumT: Float + Send + Sync>(
|
|
|
32
30
|
.zip(input.par_chunks(ctx.chunk_size(input.len())))
|
|
33
31
|
.for_each(|(r, x)| {
|
|
34
32
|
let start = ctx.start(r.len());
|
|
33
|
+
let end = ctx.end(r.len());
|
|
35
34
|
r.fill(NumT::nan());
|
|
36
35
|
|
|
37
36
|
// We need to collect window values to compute entropy,
|
|
38
37
|
// since we need min/max for binning.
|
|
39
38
|
let mut window: Vec<NumT> = Vec::with_capacity(periods);
|
|
40
39
|
|
|
41
|
-
for i in start..
|
|
40
|
+
for i in start..end {
|
|
42
41
|
let val = x[i];
|
|
43
42
|
|
|
44
43
|
// Add new value
|
|
@@ -87,13 +87,12 @@ where
|
|
|
87
87
|
return Err(Error::LengthMismatch(r.len(), input.len()));
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
let r = ctx.align_end_mut(r);
|
|
91
|
-
let input = ctx.align_end(input);
|
|
92
90
|
|
|
93
91
|
r.par_chunks_mut(ctx.chunk_size(r.len()))
|
|
94
92
|
.zip(input.par_chunks(ctx.chunk_size(input.len())))
|
|
95
93
|
.for_each(|(r, x)| {
|
|
96
94
|
let start = ctx.start(r.len());
|
|
95
|
+
let end = ctx.end(r.len());
|
|
97
96
|
r.fill(NumT::nan());
|
|
98
97
|
|
|
99
98
|
if periods == 0 {
|
|
@@ -102,7 +101,7 @@ where
|
|
|
102
101
|
let mut best_idx: usize = 0;
|
|
103
102
|
let mut has_nan_poison = false;
|
|
104
103
|
|
|
105
|
-
for i in start..
|
|
104
|
+
for i in start..end {
|
|
106
105
|
let val = x[i];
|
|
107
106
|
if !is_normal(&val) {
|
|
108
107
|
if !ctx.is_skip_nan() {
|
|
@@ -141,7 +140,7 @@ where
|
|
|
141
140
|
let mut deque: VecDeque<usize> = VecDeque::new();
|
|
142
141
|
|
|
143
142
|
if ctx.is_skip_nan() {
|
|
144
|
-
let iter = SkipNanWindow::new(x, periods, start);
|
|
143
|
+
let iter = SkipNanWindow::new(&x[..end], periods, start);
|
|
145
144
|
for i in iter {
|
|
146
145
|
let curr_val = x[i.end];
|
|
147
146
|
if is_normal(&curr_val) {
|
|
@@ -205,7 +204,7 @@ where
|
|
|
205
204
|
}
|
|
206
205
|
}
|
|
207
206
|
|
|
208
|
-
for i in pre_fill_start..
|
|
207
|
+
for i in pre_fill_start..end {
|
|
209
208
|
let val = x[i];
|
|
210
209
|
let is_valid = is_normal(&val);
|
|
211
210
|
|
|
@@ -78,9 +78,6 @@ pub fn ta_group_rank<NumT: Float + Send + Sync + Debug>(
|
|
|
78
78
|
return Err(Error::LengthMismatch(r.len(), input.len()));
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
let r = ctx.align_end_mut(r);
|
|
82
|
-
let category = ctx.align_end(category);
|
|
83
|
-
let input = ctx.align_end(input);
|
|
84
81
|
|
|
85
82
|
let group_size = ctx.chunk_size(r.len()) as usize;
|
|
86
83
|
let groups = ctx.groups() as usize;
|
|
@@ -89,9 +86,6 @@ pub fn ta_group_rank<NumT: Float + Send + Sync + Debug>(
|
|
|
89
86
|
return Err(Error::LengthMismatch(r.len(), group_size * groups));
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
let r = ctx.align_end_mut(r);
|
|
93
|
-
let category = ctx.align_end(category);
|
|
94
|
-
let input = ctx.align_end(input);
|
|
95
89
|
|
|
96
90
|
let r_ptr = UnsafePtr::new(r.as_mut_ptr(), r.len());
|
|
97
91
|
(0..group_size).into_par_iter().for_each(|j| {
|
|
@@ -178,9 +172,6 @@ pub fn ta_group_zscore<NumT: Float + Send + Sync + Debug>(
|
|
|
178
172
|
return Err(Error::LengthMismatch(r.len(), input.len()));
|
|
179
173
|
}
|
|
180
174
|
|
|
181
|
-
let r = ctx.align_end_mut(r);
|
|
182
|
-
let category = ctx.align_end(category);
|
|
183
|
-
let input = ctx.align_end(input);
|
|
184
175
|
|
|
185
176
|
let group_size = ctx.chunk_size(r.len()) as usize;
|
|
186
177
|
let groups = ctx.groups() as usize;
|
|
@@ -189,9 +180,6 @@ pub fn ta_group_zscore<NumT: Float + Send + Sync + Debug>(
|
|
|
189
180
|
return Err(Error::LengthMismatch(r.len(), group_size * groups));
|
|
190
181
|
}
|
|
191
182
|
|
|
192
|
-
let r = ctx.align_end_mut(r);
|
|
193
|
-
let category = ctx.align_end(category);
|
|
194
|
-
let input = ctx.align_end(input);
|
|
195
183
|
|
|
196
184
|
let r_ptr = UnsafePtr::new(r.as_mut_ptr(), r.len());
|
|
197
185
|
(0..group_size).into_par_iter().for_each(|j| {
|