quantwave_core/indicators/incremental/
trix.rs1use crate::indicators::incremental::talib_ema::TalibEma;
4use crate::traits::Next;
5
6#[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}