wickra_core/indicators/
tema.rs1use crate::error::Result;
4use crate::indicators::ema::Ema;
5use crate::traits::Indicator;
6
7#[derive(Debug, Clone)]
25pub struct Tema {
26 ema1: Ema,
27 ema2: Ema,
28 ema3: Ema,
29 period: usize,
30}
31
32impl Tema {
33 pub fn new(period: usize) -> Result<Self> {
36 Ok(Self {
37 ema1: Ema::new(period)?,
38 ema2: Ema::new(period)?,
39 ema3: Ema::new(period)?,
40 period,
41 })
42 }
43
44 pub const fn period(&self) -> usize {
46 self.period
47 }
48}
49
50impl Indicator for Tema {
51 type Input = f64;
52 type Output = f64;
53
54 fn update(&mut self, input: f64) -> Option<f64> {
55 let e1 = self.ema1.update(input)?;
56 let e2 = self.ema2.update(e1)?;
57 let e3 = self.ema3.update(e2)?;
58 Some(3.0 * e1 - 3.0 * e2 + e3)
59 }
60
61 fn reset(&mut self) {
62 self.ema1.reset();
63 self.ema2.reset();
64 self.ema3.reset();
65 }
66
67 fn warmup_period(&self) -> usize {
68 3 * self.period - 2
69 }
70
71 fn is_ready(&self) -> bool {
72 self.ema3.is_ready()
73 }
74
75 fn name(&self) -> &'static str {
76 "TEMA"
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::traits::BatchExt;
84 use approx::assert_relative_eq;
85
86 #[test]
87 fn constant_series_yields_constant_tema() {
88 let mut tema = Tema::new(5).unwrap();
89 let out = tema.batch(&[42.0_f64; 80]);
90 let last = out.iter().rev().flatten().next().unwrap();
91 assert_relative_eq!(*last, 42.0, epsilon = 1e-9);
92 }
93
94 #[test]
95 fn batch_equals_streaming() {
96 let prices: Vec<f64> = (1..=80)
97 .map(|i| (f64::from(i) * 0.3).sin() * 10.0)
98 .collect();
99 let mut a = Tema::new(5).unwrap();
100 let mut b = Tema::new(5).unwrap();
101 assert_eq!(
102 a.batch(&prices),
103 prices.iter().map(|p| b.update(*p)).collect::<Vec<_>>()
104 );
105 }
106
107 #[test]
108 fn reset_clears_state() {
109 let mut tema = Tema::new(5).unwrap();
110 tema.batch(&(1..=80).map(f64::from).collect::<Vec<_>>());
111 assert!(tema.is_ready());
112 tema.reset();
113 assert!(!tema.is_ready());
114 }
115
116 #[test]
117 fn rejects_zero_period() {
118 assert!(Tema::new(0).is_err());
119 }
120
121 #[test]
125 fn accessors_and_metadata() {
126 let tema = Tema::new(5).unwrap();
127 assert_eq!(tema.period(), 5);
128 assert_eq!(tema.warmup_period(), 3 * 5 - 2);
130 assert_eq!(tema.name(), "TEMA");
131 }
132}