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.
Files changed (77) hide show
  1. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/CHANGELOG.md +15 -0
  2. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/Cargo.lock +15 -42
  3. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/Cargo.toml +3 -3
  4. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/PKG-INFO +3 -2
  5. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/README.md +2 -1
  6. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/algo/algo_gen.py +24 -0
  7. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/algo.md +1 -0
  8. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/backfill.rs +4 -6
  9. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/context.rs +4 -17
  10. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/cross.rs +37 -26
  11. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/ema.rs +3 -6
  12. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/entropy.rs +2 -3
  13. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/extremum.rs +4 -5
  14. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/group.rs +0 -12
  15. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/ma.rs +7 -9
  16. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/misc.rs +7 -10
  17. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/mod.rs +2 -0
  18. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/moments.rs +6 -8
  19. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/neutralize.rs +2 -7
  20. py_alpha_lib-0.2.2/src/algo/quantile.rs +224 -0
  21. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/rank.rs +2 -11
  22. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/returns.rs +3 -10
  23. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/scan.rs +4 -8
  24. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/series.rs +10 -14
  25. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/slope.rs +2 -3
  26. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/stats.rs +15 -24
  27. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/stddev.rs +3 -4
  28. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/sum.rs +10 -14
  29. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/zscore.rs +3 -8
  30. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/tests/usage.py +16 -17
  31. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/.agent/skills/add_algo/SKILL.md +0 -0
  32. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/.github/workflows/CI.yml +0 -0
  33. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/.gitignore +0 -0
  34. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/.nwa-config.yaml +0 -0
  35. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/LICENSE +0 -0
  36. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/articles/001.md +0 -0
  37. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/articles/COMPARISON.md +0 -0
  38. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/benchmarks/benchmark_alpha101.py +0 -0
  39. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/benchmarks/benchmark_pandas.py +0 -0
  40. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/benchmarks/benchmark_polars_ta.py +0 -0
  41. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/build.rs +0 -0
  42. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/gtja191/al/__init__.py +0 -0
  43. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/gtja191/al/alpha191.py +0 -0
  44. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/gtja191/al/alpha191_context.py +0 -0
  45. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/gtja191/alpha191.txt +0 -0
  46. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/gtja191/main.py +0 -0
  47. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/al/__init__.py +0 -0
  48. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/al/alpha101.py +0 -0
  49. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/al/alpha101_context.py +0 -0
  50. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/alpha101.txt +0 -0
  51. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/main.py +0 -0
  52. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/pd_/__init__.py +0 -0
  53. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/pd_/alpha101_adjusted.py +0 -0
  54. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/pl_/__init__.py +0 -0
  55. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/pl_/alpha101_adjusted.py +0 -0
  56. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/examples/wq101/result.md +0 -0
  57. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/pyproject.toml +0 -0
  58. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/__init__.py +0 -0
  59. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/algo/__init__.py +0 -0
  60. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/algo/algo.py +0 -0
  61. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/context.py +0 -0
  62. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/lang/__init__.py +0 -0
  63. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/lang/__main__.py +0 -0
  64. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/lang/alpha.lark +0 -0
  65. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/lang/parser.py +0 -0
  66. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/alpha/lang/to_python.py +0 -0
  67. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/conftest.py +0 -0
  68. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/tests/test_grammar.py +0 -0
  69. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/tests/test_rank.py +0 -0
  70. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/tests/test_talib.py +0 -0
  71. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/python/tests/test_to_python.py +0 -0
  72. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/rustfmt.toml +0 -0
  73. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/error.rs +0 -0
  74. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/algo/skip_nan_window.rs +0 -0
  75. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/src/lib.rs +0 -0
  76. {py_alpha_lib-0.2.0 → py_alpha_lib-0.2.2}/tests/rank.py +0 -0
  77. {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.0"
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.27.1"
143
+ version = "0.28.0"
162
144
  source = "registry+https://github.com/rust-lang/crates.io-index"
163
- checksum = "7aac2e6a6e4468ffa092ad43c39b81c79196c2bb773b8db4085f695efe3bba17"
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.27.2"
189
+ version = "0.28.2"
208
190
  source = "registry+https://github.com/rust-lang/crates.io-index"
209
- checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d"
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.27.2"
203
+ version = "0.28.2"
225
204
  source = "registry+https://github.com/rust-lang/crates.io-index"
226
- checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6"
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.27.2"
212
+ version = "0.28.2"
234
213
  source = "registry+https://github.com/rust-lang/crates.io-index"
235
- checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089"
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.2"
222
+ version = "0.13.3"
244
223
  source = "registry+https://github.com/rust-lang/crates.io-index"
245
- checksum = "2f8bae9ad5ba08b0b0ed2bb9c2bdbaeccc69cafca96d78cf0fbcea0d45d122bb"
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.27.2"
233
+ version = "0.28.2"
255
234
  source = "registry+https://github.com/rust-lang/crates.io-index"
256
- checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02"
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.27.2"
245
+ version = "0.28.2"
267
246
  source = "registry+https://github.com/rust-lang/crates.io-index"
268
- checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9"
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.0"
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.27.0", features = ["abi3"] }
14
+ pyo3 = { version = "0.28", features = ["abi3"] }
15
15
  pyo3-log = "0.13"
16
16
  thiserror = "2"
17
- numpy = "0.27.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.0
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
- # [1. 1.5 nan 3. 4.5 5. 6. 7. 8. 9.]
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
- # [1. 1.5 nan 3. 4.5 5. 6. 7. 8. 9.]
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..x.len() {
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..x.len() {
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 >= 0 {
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..a.len() {
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..a.len() {
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..a.len() {
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..a.len() {
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..a.len() {
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..a.len() {
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..a.len() {
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..a.len() {
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..a.len() {
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..a.len() {
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..x.len() {
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..x.len() {
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..x.len() {
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..x.len() {
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| {