py-alpha-lib 0.2.1__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.1 → py_alpha_lib-0.2.2}/CHANGELOG.md +6 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/Cargo.lock +1 -1
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/Cargo.toml +1 -1
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/PKG-INFO +2 -1
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/README.md +1 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/alpha/algo/algo_gen.py +24 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/alpha/algo.md +1 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/mod.rs +2 -0
- py_alpha_lib-0.2.2/src/algo/quantile.rs +224 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/.agent/skills/add_algo/SKILL.md +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/.github/workflows/CI.yml +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/.gitignore +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/.nwa-config.yaml +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/LICENSE +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/articles/001.md +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/articles/COMPARISON.md +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/benchmarks/benchmark_alpha101.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/benchmarks/benchmark_pandas.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/benchmarks/benchmark_polars_ta.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/build.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/gtja191/al/__init__.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/gtja191/al/alpha191.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/gtja191/al/alpha191_context.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/gtja191/alpha191.txt +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/gtja191/main.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/wq101/al/__init__.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/wq101/al/alpha101.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/wq101/al/alpha101_context.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/wq101/alpha101.txt +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/wq101/main.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/wq101/pd_/__init__.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/wq101/pd_/alpha101_adjusted.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/wq101/pl_/__init__.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/wq101/pl_/alpha101_adjusted.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/examples/wq101/result.md +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/pyproject.toml +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/alpha/__init__.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/alpha/algo/__init__.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/alpha/algo/algo.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/alpha/context.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/alpha/lang/__init__.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/alpha/lang/__main__.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/alpha/lang/alpha.lark +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/alpha/lang/parser.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/alpha/lang/to_python.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/conftest.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/tests/test_grammar.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/tests/test_rank.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/tests/test_talib.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/python/tests/test_to_python.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/rustfmt.toml +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/backfill.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/context.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/cross.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/ema.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/entropy.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/error.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/extremum.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/group.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/ma.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/misc.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/moments.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/neutralize.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/rank.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/returns.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/scan.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/series.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/skip_nan_window.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/slope.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/stats.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/stddev.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/sum.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/algo/zscore.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/src/lib.rs +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/tests/rank.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/tests/usage.py +0 -0
- {py_alpha_lib-0.2.1 → py_alpha_lib-0.2.2}/tests/verify_sumif.py +0 -0
|
@@ -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
|
|
@@ -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] |
|
|
@@ -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]
|
|
@@ -13,6 +13,7 @@ mod ma;
|
|
|
13
13
|
mod misc;
|
|
14
14
|
mod moments;
|
|
15
15
|
mod neutralize;
|
|
16
|
+
mod quantile;
|
|
16
17
|
mod rank;
|
|
17
18
|
mod returns;
|
|
18
19
|
mod scan;
|
|
@@ -36,6 +37,7 @@ pub use ma::*;
|
|
|
36
37
|
pub use misc::*;
|
|
37
38
|
pub use moments::*;
|
|
38
39
|
pub use neutralize::*;
|
|
40
|
+
pub use quantile::*;
|
|
39
41
|
pub use rank::*;
|
|
40
42
|
pub use returns::*;
|
|
41
43
|
pub use scan::*;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Copyright 2026 MSD-RS Project LiJia
|
|
2
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
3
|
+
|
|
4
|
+
use num_traits::Float;
|
|
5
|
+
use rayon::prelude::*;
|
|
6
|
+
|
|
7
|
+
use crate::algo::{Context, Error, is_normal};
|
|
8
|
+
|
|
9
|
+
/// Calculate rolling quantile over a moving window
|
|
10
|
+
///
|
|
11
|
+
/// QUANTILE(x, d, q) returns the q-th quantile (0 <= q <= 1) of values
|
|
12
|
+
/// in the preceding d periods. Uses linear interpolation between data points
|
|
13
|
+
/// (matching numpy/pandas percentile with interpolation='linear').
|
|
14
|
+
/// NaN values are excluded from the computation. Requires at least 1 valid value.
|
|
15
|
+
///
|
|
16
|
+
/// Ref: https://numpy.org/doc/stable/reference/generated/numpy.quantile.html
|
|
17
|
+
pub fn ta_quantile<NumT: Float + Send + Sync>(
|
|
18
|
+
ctx: &Context,
|
|
19
|
+
r: &mut [NumT],
|
|
20
|
+
input: &[NumT],
|
|
21
|
+
periods: usize,
|
|
22
|
+
q: NumT,
|
|
23
|
+
) -> Result<(), Error> {
|
|
24
|
+
if r.len() != input.len() {
|
|
25
|
+
return Err(Error::LengthMismatch(r.len(), input.len()));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if periods == 0 {
|
|
29
|
+
r.fill(NumT::nan());
|
|
30
|
+
return Ok(());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if q < NumT::zero() || q > NumT::one() || q.is_nan() {
|
|
34
|
+
r.fill(NumT::nan());
|
|
35
|
+
return Ok(());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
r.par_chunks_mut(ctx.chunk_size(r.len()))
|
|
39
|
+
.zip(input.par_chunks(ctx.chunk_size(input.len())))
|
|
40
|
+
.for_each(|(r, x)| {
|
|
41
|
+
let start = ctx.start(r.len());
|
|
42
|
+
let end = ctx.end(r.len());
|
|
43
|
+
r.fill(NumT::nan());
|
|
44
|
+
|
|
45
|
+
// Allocate a buffer for sorting within the window
|
|
46
|
+
let mut buf = vec![NumT::zero(); periods];
|
|
47
|
+
|
|
48
|
+
for i in start..end {
|
|
49
|
+
let val = x[i];
|
|
50
|
+
if !is_normal(&val) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Determine window boundaries
|
|
55
|
+
let win_start = if i >= periods { i - periods + 1 } else { 0 };
|
|
56
|
+
let win_end = i + 1;
|
|
57
|
+
|
|
58
|
+
// Collect valid (non-NaN) values into buffer
|
|
59
|
+
let mut count = 0;
|
|
60
|
+
for k in win_start..win_end {
|
|
61
|
+
let v = x[k];
|
|
62
|
+
if is_normal(&v) {
|
|
63
|
+
buf[count] = v;
|
|
64
|
+
count += 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if count == 0 {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check strict cycle: require full window of `periods` valid values
|
|
73
|
+
if ctx.is_strictly_cycle() {
|
|
74
|
+
if i < periods - 1 || count != periods || (win_end - win_start) != periods {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Sort the buffer (insertion sort for small windows, efficient for typical d=5..50)
|
|
80
|
+
let slice = &mut buf[..count];
|
|
81
|
+
for j in 1..count {
|
|
82
|
+
let key = slice[j];
|
|
83
|
+
let mut k = j;
|
|
84
|
+
while k > 0 && slice[k - 1] > key {
|
|
85
|
+
slice[k] = slice[k - 1];
|
|
86
|
+
k -= 1;
|
|
87
|
+
}
|
|
88
|
+
slice[k] = key;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Linear interpolation quantile (matching numpy/pandas)
|
|
92
|
+
let pos = q * NumT::from(count - 1).unwrap();
|
|
93
|
+
let lo = pos.floor();
|
|
94
|
+
let hi = lo + NumT::one();
|
|
95
|
+
let lo_idx = lo.to_usize().unwrap_or(0).min(count - 1);
|
|
96
|
+
let hi_idx = hi.to_usize().unwrap_or(0).min(count - 1);
|
|
97
|
+
let frac = pos - lo;
|
|
98
|
+
r[i] = slice[lo_idx] * (NumT::one() - frac) + slice[hi_idx] * frac;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
Ok(())
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[cfg(test)]
|
|
106
|
+
mod tests {
|
|
107
|
+
use super::*;
|
|
108
|
+
use crate::algo::{assert_vec_eq_nan, context::FLAG_STRICTLY_CYCLE};
|
|
109
|
+
|
|
110
|
+
#[test]
|
|
111
|
+
fn test_quantile_median() {
|
|
112
|
+
// Median (q=0.5) of [1,2,3] = 2.0
|
|
113
|
+
let input = vec![1.0, 2.0, 3.0, 4.0, 5.0];
|
|
114
|
+
let periods = 3;
|
|
115
|
+
let mut r = vec![0.0; input.len()];
|
|
116
|
+
let ctx = Context::new(0, 0, 0);
|
|
117
|
+
ta_quantile(&ctx, &mut r, &input, periods, 0.5).unwrap();
|
|
118
|
+
|
|
119
|
+
// i=0: [1] -> median=1.0
|
|
120
|
+
// i=1: [1,2] -> median=1.5
|
|
121
|
+
// i=2: [1,2,3] -> median=2.0
|
|
122
|
+
// i=3: [2,3,4] -> median=3.0
|
|
123
|
+
// i=4: [3,4,5] -> median=4.0
|
|
124
|
+
assert_vec_eq_nan(&r, &vec![1.0, 1.5, 2.0, 3.0, 4.0]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#[test]
|
|
128
|
+
fn test_quantile_strict() {
|
|
129
|
+
let input = vec![1.0, 2.0, 3.0, 4.0, 5.0];
|
|
130
|
+
let periods = 3;
|
|
131
|
+
let mut r = vec![0.0; input.len()];
|
|
132
|
+
let ctx = Context::new(0, 0, FLAG_STRICTLY_CYCLE);
|
|
133
|
+
ta_quantile(&ctx, &mut r, &input, periods, 0.5).unwrap();
|
|
134
|
+
|
|
135
|
+
assert_vec_eq_nan(
|
|
136
|
+
&r,
|
|
137
|
+
&vec![f64::NAN, f64::NAN, 2.0, 3.0, 4.0],
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#[test]
|
|
142
|
+
fn test_quantile_q0_q1() {
|
|
143
|
+
let input = vec![3.0, 1.0, 4.0, 1.0, 5.0];
|
|
144
|
+
let periods = 3;
|
|
145
|
+
let mut r = vec![0.0; input.len()];
|
|
146
|
+
let ctx = Context::new(0, 0, FLAG_STRICTLY_CYCLE);
|
|
147
|
+
|
|
148
|
+
// q=0 -> min
|
|
149
|
+
ta_quantile(&ctx, &mut r, &input, periods, 0.0).unwrap();
|
|
150
|
+
assert_vec_eq_nan(
|
|
151
|
+
&r,
|
|
152
|
+
&vec![f64::NAN, f64::NAN, 1.0, 1.0, 1.0],
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// q=1 -> max
|
|
156
|
+
ta_quantile(&ctx, &mut r, &input, periods, 1.0).unwrap();
|
|
157
|
+
assert_vec_eq_nan(
|
|
158
|
+
&r,
|
|
159
|
+
&vec![f64::NAN, f64::NAN, 4.0, 4.0, 5.0],
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[test]
|
|
164
|
+
fn test_quantile_with_nan() {
|
|
165
|
+
let input = vec![1.0, f64::NAN, 3.0, 4.0, 5.0];
|
|
166
|
+
let periods = 3;
|
|
167
|
+
let mut r = vec![0.0; input.len()];
|
|
168
|
+
let ctx = Context::new(0, 0, 0);
|
|
169
|
+
ta_quantile(&ctx, &mut r, &input, periods, 0.5).unwrap();
|
|
170
|
+
|
|
171
|
+
// i=0: [1] -> 1.0
|
|
172
|
+
// i=1: NaN -> NaN
|
|
173
|
+
// i=2: [1, 3] (NaN skipped) -> median of [1,3] = 2.0
|
|
174
|
+
// i=3: [3, 4] (NaN skipped) -> median of [3,4] = 3.5
|
|
175
|
+
// i=4: [3, 4, 5] -> median = 4.0
|
|
176
|
+
assert_vec_eq_nan(&r, &vec![1.0, f64::NAN, 2.0, 3.5, 4.0]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#[test]
|
|
180
|
+
fn test_quantile_groups() {
|
|
181
|
+
// 2 groups of 3 each
|
|
182
|
+
let input = vec![1.0, 2.0, 3.0, 10.0, 20.0, 30.0];
|
|
183
|
+
let periods = 3;
|
|
184
|
+
let mut r = vec![0.0; input.len()];
|
|
185
|
+
let ctx = Context::new(0, 2, FLAG_STRICTLY_CYCLE);
|
|
186
|
+
ta_quantile(&ctx, &mut r, &input, periods, 0.5).unwrap();
|
|
187
|
+
|
|
188
|
+
// Group 1: [1,2,3] -> median=2.0
|
|
189
|
+
// Group 2: [10,20,30] -> median=20.0
|
|
190
|
+
assert_vec_eq_nan(
|
|
191
|
+
&r,
|
|
192
|
+
&vec![f64::NAN, f64::NAN, 2.0, f64::NAN, f64::NAN, 20.0],
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[test]
|
|
197
|
+
fn test_quantile_periods_zero() {
|
|
198
|
+
let input = vec![1.0, 2.0, 3.0];
|
|
199
|
+
let mut r = vec![0.0; input.len()];
|
|
200
|
+
let ctx = Context::new(0, 0, 0);
|
|
201
|
+
ta_quantile(&ctx, &mut r, &input, 0, 0.5).unwrap();
|
|
202
|
+
// periods=0 → all NaN
|
|
203
|
+
assert!(r.iter().all(|x| x.is_nan()));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#[test]
|
|
207
|
+
fn test_quantile_invalid_q() {
|
|
208
|
+
let input = vec![1.0, 2.0, 3.0];
|
|
209
|
+
let mut r = vec![0.0; input.len()];
|
|
210
|
+
let ctx = Context::new(0, 0, 0);
|
|
211
|
+
|
|
212
|
+
// q < 0
|
|
213
|
+
ta_quantile(&ctx, &mut r, &input, 3, -0.1).unwrap();
|
|
214
|
+
assert!(r.iter().all(|x| x.is_nan()));
|
|
215
|
+
|
|
216
|
+
// q > 1
|
|
217
|
+
ta_quantile(&ctx, &mut r, &input, 3, 1.5).unwrap();
|
|
218
|
+
assert!(r.iter().all(|x| x.is_nan()));
|
|
219
|
+
|
|
220
|
+
// q = NaN
|
|
221
|
+
ta_quantile(&ctx, &mut r, &input, 3, f64::NAN).unwrap();
|
|
222
|
+
assert!(r.iter().all(|x| x.is_nan()));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|