Skip to main content

finance_query/indicators/
dema.rs

1//! Double Exponential Moving Average (DEMA) indicator.
2
3use super::{IndicatorError, Result, ema::ema};
4
5/// Calculate Double Exponential Moving Average (DEMA).
6///
7/// DEMA = 2 * EMA - EMA(EMA)
8/// Reduces lag compared to simple EMA.
9///
10/// # Arguments
11///
12/// * `data` - Price data (typically close prices)
13/// * `period` - Number of periods
14///
15/// # Formula
16///
17/// DEMA = 2 * EMA - EMA(EMA)
18///
19/// # Example
20///
21/// ```
22/// use finance_query::indicators::dema;
23///
24/// let prices = vec![10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0];
25/// let result = dema(&prices, 3).unwrap();
26/// ```
27pub fn dema(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    // Extract valid values for EMA2 calculation
43    let valid_ema1: Vec<f64> = ema1.iter().filter_map(|&x| x).collect();
44
45    if valid_ema1.len() < period {
46        return Err(IndicatorError::InsufficientData {
47            need: 2 * period - 1,
48            got: data.len(),
49        });
50    }
51
52    let ema2 = ema(&valid_ema1, period);
53
54    let mut result = vec![None; data.len()];
55
56    for i in 0..data.len() {
57        if i >= period - 1 {
58            let j = i - (period - 1);
59            if j < ema2.len()
60                && let (Some(e1), Some(e2)) = (ema1[i], ema2[j])
61            {
62                result[i] = Some(2.0 * e1 - e2);
63            }
64        }
65    }
66
67    Ok(result)
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn test_dema() {
76        let prices = vec![10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0];
77        let result = dema(&prices, 3).unwrap();
78
79        assert_eq!(result.len(), prices.len());
80        // First 2*period - 2 = 4 values should be None
81        assert!(result[0].is_none());
82        assert!(result[1].is_none());
83        assert!(result[2].is_none());
84        assert!(result[3].is_none());
85        assert!(result[4].is_some());
86    }
87}