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