Skip to main content

finance_query/indicators/
tema.rs

1//! Triple Exponential Moving Average (TEMA) indicator.
2
3use super::{IndicatorError, Result, ema::ema_raw};
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    // ema_raw returns only valid values — no Option/None padding or filter_map needed
41    let ema1 = ema_raw(data, period); // len = N - (period-1)
42    if ema1.len() < period {
43        return Err(IndicatorError::InsufficientData {
44            need: 2 * period - 1,
45            got: data.len(),
46        });
47    }
48    let ema2 = ema_raw(&ema1, period); // len = N - 2*(period-1)
49    if ema2.len() < period {
50        return Err(IndicatorError::InsufficientData {
51            need: 3 * period - 2,
52            got: data.len(),
53        });
54    }
55    let ema3 = ema_raw(&ema2, period); // len = N - 3*(period-1)
56
57    let mut result = vec![None; data.len()];
58    let off = period - 1;
59
60    // ema3[k3] → original index k3 + 3*off
61    // matching ema2 index = k3 + off, ema1 index = k3 + 2*off
62    for (k3, &e3) in ema3.iter().enumerate() {
63        let orig_idx = k3 + 3 * off;
64        let e1 = ema1[k3 + 2 * off];
65        let e2 = ema2[k3 + off];
66        result[orig_idx] = Some(3.0 * e1 - 3.0 * e2 + e3);
67    }
68
69    Ok(result)
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn test_tema() {
78        let prices = vec![10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0];
79        let result = tema(&prices, 3).unwrap();
80
81        assert_eq!(result.len(), prices.len());
82        // First 3*period - 3 = 6 values should be None
83        assert!(result[0].is_none());
84        assert!(result[5].is_none());
85        assert!(result[6].is_some());
86    }
87}