mantis_ta/indicators/trend/
tema.rs1use super::EMA;
2use crate::indicators::Indicator;
3use crate::types::Candle;
4
5#[derive(Debug, Clone)]
33pub struct TEMA {
34 period: usize,
35 ema1: EMA,
36 ema2: EMA,
37 ema3: EMA,
38}
39
40impl TEMA {
41 pub fn new(period: usize) -> Self {
42 assert!(period > 0, "period must be > 0");
43 Self {
44 period,
45 ema1: EMA::new(period),
46 ema2: EMA::new(period),
47 ema3: EMA::new(period),
48 }
49 }
50
51 #[inline]
52 fn update(&mut self, value: f64) -> Option<f64> {
53 let ema1_val = self.ema1.next(&Candle {
54 timestamp: 0,
55 open: value,
56 high: value,
57 low: value,
58 close: value,
59 volume: 0.0,
60 });
61
62 if let Some(ema1) = ema1_val {
63 let ema2_val = self.ema2.next(&Candle {
64 timestamp: 0,
65 open: ema1,
66 high: ema1,
67 low: ema1,
68 close: ema1,
69 volume: 0.0,
70 });
71
72 if let Some(ema2) = ema2_val {
73 let ema3_val = self.ema3.next(&Candle {
74 timestamp: 0,
75 open: ema2,
76 high: ema2,
77 low: ema2,
78 close: ema2,
79 volume: 0.0,
80 });
81
82 if let Some(ema3) = ema3_val {
83 return Some(3.0 * ema1 - 3.0 * ema2 + ema3);
84 }
85 }
86 }
87
88 None
89 }
90}
91
92impl Indicator for TEMA {
93 type Output = f64;
94
95 fn next(&mut self, candle: &Candle) -> Option<Self::Output> {
96 self.update(candle.close)
97 }
98
99 fn reset(&mut self) {
100 self.ema1.reset();
101 self.ema2.reset();
102 self.ema3.reset();
103 }
104
105 fn warmup_period(&self) -> usize {
106 self.period * 3 - 2
107 }
108
109 fn clone_boxed(&self) -> Box<dyn Indicator<Output = Self::Output>> {
110 Box::new(self.clone())
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn tema_emits_after_warmup() {
120 let prices = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
121 let candles: Vec<Candle> = prices
122 .iter()
123 .enumerate()
124 .map(|(i, p)| Candle {
125 timestamp: i as i64,
126 open: *p,
127 high: *p,
128 low: *p,
129 close: *p,
130 volume: 0.0,
131 })
132 .collect();
133
134 let out = TEMA::new(3).calculate(&candles);
135 assert!(out.iter().take(6).all(|v| v.is_none()));
136 assert!(out.iter().skip(6).any(|v| v.is_some()));
137 }
138}