Skip to main content

egui_charts/studies/builtin/
tema.rs

1//! Triple Exponential Moving Avg (TEMA) Indicator
2//!
3//! TEMA further reduces lag compared to DEMA by applying EMA three times
4//! with a specific formula.
5//!
6//! # Formula
7//! TEMA = 3 * EMA1 - 3 * EMA2 + EMA3
8//! Where:
9//! - EMA1 = EMA(price, period)
10//! - EMA2 = EMA(EMA1, period)
11//! - EMA3 = EMA(EMA2, period)
12//!
13//! # Example
14//! ```
15//! # let bars: Vec<egui_charts::model::Bar> = (0..60).map(|i| { let p = 100.0 + (i as f64 * 0.3).sin() * 5.0; egui_charts::model::Bar::new(chrono::Utc::now(), p, p + 1.0, p - 1.0, p + 0.5, 1_000.0) }).collect();
16//! use egui_charts::studies::{TEMA, Indicator};
17//!
18//! let mut tema = TEMA::new(14);
19//! tema.calculate(&bars);
20//! ```
21
22use super::smoothing::Smoothing;
23use crate::model::Bar;
24use crate::studies::palette;
25use crate::studies::stepper::Stepper;
26use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
27use egui::Color32;
28
29/// Triple Exponential Moving Avg indicator
30#[derive(Clone)]
31pub struct TEMA {
32    period: usize,
33    values: IndicatorValues,
34    stepper: Stepper<[Smoothing; 3]>,
35    color: Color32,
36    visible: bool,
37}
38
39impl TEMA {
40    /// Create a new TEMA indicator
41    ///
42    /// # Arguments
43    /// * `period` - Number of periods for calculation
44    pub fn new(period: usize) -> Self {
45        Self {
46            period: period.max(1),
47            values: IndicatorValues::new(),
48            stepper: Stepper::default(),
49            color: palette::BLUE,
50            visible: true,
51        }
52    }
53
54    /// Set the line color
55    pub fn with_color(mut self, color: Color32) -> Self {
56        self.color = color;
57        self
58    }
59}
60
61/// Construct with the conventional default parameters.
62impl Default for TEMA {
63    fn default() -> Self {
64        Self::new(9)
65    }
66}
67
68impl Indicator for TEMA {
69    fn source(&self) -> Option<crate::studies::StudySource> {
70        Some(crate::studies::StudySource::Price(
71            crate::model::PriceSource::Close,
72        ))
73    }
74
75    crate::studies::inputs::study_inputs!(self;
76        period: length("Period", 1, usize::MAX)
77    ; {});
78
79    fn name(&self) -> &str {
80        "TEMA"
81    }
82
83    fn desc(&self) -> &str {
84        "Triple Exponential Moving Avg - Minimal lag moving avg"
85    }
86
87    fn calculate(&mut self, data: &[Bar]) {
88        self.update(data, 0);
89    }
90
91    fn update(&mut self, data: &[Bar], from: usize) {
92        self.stepper
93            .run(data, from, &mut self.values, |state, bar| {
94                if !bar.close.is_finite() {
95                    return IndicatorValue::None;
96                }
97                let alpha = 2.0 / (self.period as f64 + 1.0);
98                let ema1 = state[0].next(bar.close, self.period, alpha);
99                let ema2 = state[1].next(ema1, self.period, alpha);
100                let ema3 = state[2].next(ema2, self.period, alpha);
101                IndicatorValue::Single(3.0 * ema1 - 3.0 * ema2 + ema3)
102            });
103    }
104
105    fn is_incremental(&self) -> bool {
106        true
107    }
108
109    fn values(&self) -> &IndicatorValues {
110        &self.values
111    }
112
113    fn colors(&self) -> Vec<Color32> {
114        vec![self.color]
115    }
116
117    fn set_colors(&mut self, colors: Vec<Color32>) {
118        if !colors.is_empty() {
119            self.color = colors[0];
120        }
121    }
122
123    fn is_overlay(&self) -> bool {
124        true
125    }
126
127    fn line_count(&self) -> usize {
128        1
129    }
130
131    fn is_visible(&self) -> bool {
132        self.visible
133    }
134
135    fn set_visible(&mut self, visible: bool) {
136        self.visible = visible;
137    }
138
139    fn clone_box(&self) -> Box<dyn Indicator> {
140        Box::new(self.clone())
141    }
142
143    fn line_names(&self) -> Vec<String> {
144        vec![format!("TEMA({})", self.period)]
145    }
146}