Skip to main content

quantwave_core/indicators/incremental/
trix.rs

1//! Native O(1) TRIX — TA-Lib parity (`talib_rs::momentum::trix`).
2
3use crate::indicators::incremental::talib_ema::TalibEma;
4use crate::traits::Next;
5
6/// TRIX = ROC of triple EMA.
7#[derive(Debug, Clone)]
8#[allow(non_camel_case_types)]
9pub struct TRIX {
10    pub timeperiod: usize,
11    ema1: TalibEma,
12    ema2: TalibEma,
13    ema3: TalibEma,
14    e3_prev: Option<f64>,
15}
16
17impl TRIX {
18    pub fn new(timeperiod: usize) -> Self {
19        Self {
20            timeperiod,
21            ema1: TalibEma::new(timeperiod),
22            ema2: TalibEma::new(timeperiod),
23            ema3: TalibEma::new(timeperiod),
24            e3_prev: None,
25        }
26    }
27}
28
29impl Next<f64> for TRIX {
30    type Output = f64;
31
32    fn next(&mut self, input: f64) -> Self::Output {
33        if self.timeperiod < 2 {
34            return f64::NAN;
35        }
36
37        let e1 = self.ema1.next(input);
38        if e1.is_nan() {
39            return f64::NAN;
40        }
41        let e2 = self.ema2.next(e1);
42        if e2.is_nan() {
43            return f64::NAN;
44        }
45        let e3 = self.ema3.next(e2);
46        if e3.is_nan() {
47            return f64::NAN;
48        }
49
50        let Some(prev) = self.e3_prev else {
51            self.e3_prev = Some(e3);
52            return f64::NAN;
53        };
54
55        let out = if prev != 0.0 {
56            ((e3 - prev) / prev) * 100.0
57        } else {
58            0.0
59        };
60        self.e3_prev = Some(e3);
61        out
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use proptest::prelude::*;
69
70    proptest! {
71        #[test]
72        fn test_trix_parity(input in prop::collection::vec(1.0..100.0, 10..100)) {
73            let period = 14;
74            let mut trix = TRIX::new(period);
75            let streaming: Vec<f64> = input.iter().map(|&x| trix.next(x)).collect();
76            let batch = talib_rs::momentum::trix(&input, period)
77                .unwrap_or_else(|_| vec![f64::NAN; input.len()]);
78            for (s, b) in streaming.iter().zip(batch.iter()) {
79                if s.is_nan() {
80                    assert!(b.is_nan());
81                } else if !b.is_nan() {
82                    approx::assert_relative_eq!(s, b, epsilon = 1e-6);
83                }
84            }
85        }
86    }
87}