kestrel_chartkit/indicator/
tema.rs1use super::smoothing::Ema;
2use super::{Indicator, IndicatorAlert, IndicatorOutput};
3use crate::model::Bar;
4
5#[derive(Debug, Clone)]
8pub struct TemaEngine {
9 period: usize,
10 ema1: Ema,
11 ema2: Ema,
12 ema3: Ema,
13 count: usize,
14}
15
16impl TemaEngine {
17 pub fn new(period: usize) -> Self {
18 Self {
19 period: period.max(1),
20 ema1: Ema::new(period),
21 ema2: Ema::new(period),
22 ema3: Ema::new(period),
23 count: 0,
24 }
25 }
26}
27
28impl Indicator for TemaEngine {
29 fn name(&self) -> &str {
30 "tema"
31 }
32
33 fn warmup_period(&self) -> usize {
34 self.period * 3
35 }
36
37 fn reset(&mut self) {
38 self.ema1.reset();
39 self.ema2.reset();
40 self.ema3.reset();
41 self.count = 0;
42 }
43
44 fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
45 self.count += 1;
46 let e1 = self.ema1.update(bar.close);
47 let e2 = self.ema2.update(e1);
48 let e3 = self.ema3.update(e2);
49
50 if self.count < self.period {
51 return None;
52 }
53
54 let tema_val = 3.0 * e1 - 3.0 * e2 + e3;
55 Some(IndicatorOutput::new(tema_val))
56 }
57
58 fn alerts(&self) -> Vec<IndicatorAlert> {
59 Vec::new()
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn test_tema_basic() {
69 let mut tema = TemaEngine::new(5);
70 let mut out = None;
71 for i in 0..20 {
72 let b = Bar::new(i, 100.0, 105.0, 95.0, 100.0 + i as f64, 1000.0);
73 out = tema.on_bar(&b);
74 }
75 assert!(out.is_some());
76 }
77}