Skip to main content

finance_query/indicators/
tema.rs

1//! Triple Exponential Moving Average (TEMA) indicator.
2
3use super::{IndicatorError, Result, ema::ema};
4
5/// Calculate Triple Exponential Moving Average (TEMA).
6///
7/// TEMA = 3 * EMA - 3 * EMA(EMA) + EMA(EMA(EMA))
8/// Further reduces lag compared to DEMA.
9///
10/// # Arguments
11///
12/// * `data` - Price data (typically close prices)
13/// * `period` - Number of periods
14///
15/// # Formula
16///
17/// TEMA = 3 * EMA - 3 * EMA(EMA) + EMA(EMA(EMA))
18///
19/// # Example
20///
21/// ```
22/// use finance_query::indicators::tema;
23///
24/// let prices = vec![10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0];
25/// let result = tema(&prices, 3).unwrap();
26/// ```
27pub fn tema(data: &[f64], period: usize) -> Result<Vec<Option<f64>>> {
28    if period == 0 {
29        return Err(IndicatorError::InvalidPeriod(
30            "Period must be greater than 0".to_string(),
31        ));
32    }
33    if data.len() < period {
34        return Err(IndicatorError::InsufficientData {
35            need: period,
36            got: data.len(),
37        });
38    }
39
40    let ema1 = ema(data, period);
41
42    let valid_ema1: Vec<f64> = ema1.iter().filter_map(|&x| x).collect();
43    if valid_ema1.len() < period {
44        return Err(IndicatorError::InsufficientData {
45            need: 2 * period - 1,
46            got: data.len(),
47        });
48    }
49
50    let ema2 = ema(&valid_ema1, period);
51
52    let valid_ema2: Vec<f64> = ema2.iter().filter_map(|&x| x).collect();
53    if valid_ema2.len() < period {
54        return Err(IndicatorError::InsufficientData {
55            need: 3 * period - 2,
56            got: data.len(),
57        });
58    }
59
60    let ema3 = ema(&valid_ema2, period);
61
62    let mut result = vec![None; data.len()];
63
64    for i in 0..data.len() {
65        let offset1 = period - 1;
66        let offset2 = 2 * (period - 1);
67
68        if i >= offset2 {
69            let j = i - offset1;
70            let k = i - offset2;
71
72            if j < ema2.len()
73                && k < ema3.len()
74                && let (Some(e1), Some(e2), Some(e3)) = (ema1[i], ema2[j], ema3[k])
75            {
76                result[i] = Some(3.0 * e1 - 3.0 * e2 + e3);
77            }
78        }
79    }
80
81    Ok(result)
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_tema() {
90        let prices = vec![10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0];
91        let result = tema(&prices, 3).unwrap();
92
93        assert_eq!(result.len(), prices.len());
94        // First 3*period - 3 = 6 values should be None
95        assert!(result[0].is_none());
96        assert!(result[5].is_none());
97        assert!(result[6].is_some());
98    }
99}