quantwave_core/indicators/incremental/
ma_stream.rs1use crate::indicators::incremental::dema::DEMA;
10use crate::indicators::incremental::hilbert_ta::TalibMama;
11use crate::indicators::incremental::overlap_ta::{KAMA, T3, TRIMA};
12use crate::indicators::incremental::talib_ema::TalibEma;
13use crate::indicators::incremental::talib_sma::TalibSma;
14use crate::indicators::incremental::talib_tema::TalibTema;
15use crate::indicators::incremental::talib_wma::TalibWma;
16use crate::indicators::ma_type::MaType;
17use crate::traits::Next;
18
19pub const MA_DISPATCH_T3_VFACTOR: f64 = 0.7;
21pub const MA_DISPATCH_MAMA_FASTLIMIT: f64 = 0.5;
23pub const MA_DISPATCH_MAMA_SLOWLIMIT: f64 = 0.05;
25
26#[derive(Debug, Clone)]
28pub enum MaStream {
29 Sma(TalibSma),
30 Ema(TalibEma),
31 Wma(TalibWma),
32 Dema(DEMA),
33 Tema(TalibTema),
34 Trima(TRIMA),
35 Kama(KAMA),
36 Mama(Box<TalibMama>),
38 T3(T3),
39}
40
41impl MaStream {
42 pub fn new(period: usize, ma_type: MaType) -> Self {
49 match ma_type {
50 MaType::Sma => Self::Sma(TalibSma::new(period)),
51 MaType::Ema => Self::Ema(TalibEma::new(period)),
52 MaType::Wma => Self::Wma(TalibWma::new(period)),
53 MaType::Dema => Self::Dema(DEMA::new(period)),
54 MaType::Tema => Self::Tema(TalibTema::new(period)),
55 MaType::Trima => Self::Trima(TRIMA::new(period)),
56 MaType::Kama => Self::Kama(KAMA::new(period)),
57 MaType::Mama => Self::Mama(Box::new(TalibMama::new(
58 MA_DISPATCH_MAMA_FASTLIMIT,
59 MA_DISPATCH_MAMA_SLOWLIMIT,
60 ))),
61 MaType::T3 => Self::T3(T3::new(period, MA_DISPATCH_T3_VFACTOR)),
62 }
63 }
64
65 pub fn ma_type(&self) -> MaType {
67 match self {
68 Self::Sma(_) => MaType::Sma,
69 Self::Ema(_) => MaType::Ema,
70 Self::Wma(_) => MaType::Wma,
71 Self::Dema(_) => MaType::Dema,
72 Self::Tema(_) => MaType::Tema,
73 Self::Trima(_) => MaType::Trima,
74 Self::Kama(_) => MaType::Kama,
75 Self::Mama(_) => MaType::Mama,
76 Self::T3(_) => MaType::T3,
77 }
78 }
79
80 pub fn next(&mut self, v: f64) -> f64 {
81 match self {
82 Self::Sma(s) => s.next(v),
83 Self::Ema(e) => e.next(v),
84 Self::Wma(w) => w.next(v),
85 Self::Dema(d) => d.next(v),
86 Self::Tema(t) => t.next(v),
87 Self::Trima(t) => t.next(v),
88 Self::Kama(k) => k.next(v),
89 Self::Mama(m) => m.next(v).0,
90 Self::T3(t) => t.next(v),
91 }
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use proptest::prelude::*;
99
100 pub(crate) fn ma_oracle(input: &[f64], period: usize, ma_type: MaType) -> Vec<f64> {
102 let batch = match ma_type {
103 MaType::Sma => talib_rs::overlap::sma(input, period),
104 MaType::Ema => talib_rs::overlap::ema(input, period),
105 MaType::Wma => talib_rs::overlap::wma(input, period),
106 MaType::Dema => talib_rs::overlap::dema(input, period),
107 MaType::Tema => talib_rs::overlap::tema(input, period),
108 MaType::Trima => talib_rs::overlap::trima(input, period),
109 MaType::Kama => talib_rs::overlap::kama(input, period),
110 MaType::Mama => talib_rs::overlap::mama(
111 input,
112 MA_DISPATCH_MAMA_FASTLIMIT,
113 MA_DISPATCH_MAMA_SLOWLIMIT,
114 )
115 .map(|(mama, _fama)| mama),
116 MaType::T3 => talib_rs::overlap::t3(input, period, MA_DISPATCH_T3_VFACTOR),
117 };
118 batch.unwrap_or_else(|_| vec![f64::NAN; input.len()])
119 }
120
121 proptest! {
122 #[test]
127 fn test_ma_stream_parity_all_matypes(
128 input in prop::collection::vec(0.1..100.0, 120..200),
129 idx in 0usize..9,
130 ) {
131 let ma_type = MaType::ALL[idx];
132 let period = 10;
133 let mut stream = MaStream::new(period, ma_type);
134 let streaming: Vec<f64> = input.iter().map(|&x| stream.next(x)).collect();
135 let batch = ma_oracle(&input, period, ma_type);
136 prop_assert_eq!(stream.ma_type(), ma_type);
137 for (i, (s, b)) in streaming.iter().zip(batch.iter()).enumerate() {
138 if s.is_nan() {
139 prop_assert!(b.is_nan(), "{ma_type} bar {i}: streaming NaN, batch {b}");
140 } else {
141 prop_assert!(!b.is_nan(), "{ma_type} bar {i}: streaming {s}, batch NaN");
142 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
143 }
144 }
145 }
146 }
147
148 #[test]
150 fn non_sma_matypes_differ_from_sma() {
151 let input: Vec<f64> = (0..200)
152 .map(|i| 50.0 + 10.0 * (i as f64 * 0.3).sin() + i as f64 * 0.05)
153 .collect();
154 let sma: Vec<f64> = {
155 let mut s = MaStream::new(10, MaType::Sma);
156 input.iter().map(|&x| s.next(x)).collect()
157 };
158 for ma_type in MaType::ALL {
159 if ma_type == MaType::Sma {
160 continue;
161 }
162 let mut s = MaStream::new(10, ma_type);
163 let got: Vec<f64> = input.iter().map(|&x| s.next(x)).collect();
164 let identical = got
165 .iter()
166 .zip(sma.iter())
167 .all(|(a, b)| (a.is_nan() && b.is_nan()) || (a - b).abs() < 1e-12);
168 assert!(!identical, "{ma_type} still produces SMA values");
169 }
170 }
171}