Skip to main content

mantis_ta/indicators/trend/
sma.rs

1use crate::indicators::Indicator;
2use crate::types::Candle;
3use crate::utils::ringbuf::RingBuf;
4
5/// Simple Moving Average over closing prices.
6///
7/// # Examples
8/// ```rust
9/// use mantis_ta::indicators::{Indicator, SMA};
10/// use mantis_ta::types::Candle;
11///
12/// let candles: Vec<Candle> = [1.0, 2.0, 3.0, 4.0]
13///     .iter()
14///     .enumerate()
15///     .map(|(i, c)| Candle {
16///         timestamp: i as i64,
17///         open: *c,
18///         high: *c,
19///         low: *c,
20///         close: *c,
21///         volume: 0.0,
22///     })
23///     .collect();
24///
25/// let values = SMA::new(3).calculate(&candles);
26/// assert_eq!(values[0], None);
27/// assert_eq!(values[1], None);
28/// assert_eq!(values[2], Some(2.0));
29/// assert_eq!(values[3], Some(3.0));
30/// ```
31#[derive(Debug, Clone)]
32pub struct SMA {
33    period: usize,
34    sum: f64,
35    window: RingBuf<f64>,
36}
37
38impl SMA {
39    pub fn new(period: usize) -> Self {
40        assert!(period > 0, "period must be > 0");
41        Self {
42            period,
43            sum: 0.0,
44            window: RingBuf::new(period, 0.0),
45        }
46    }
47
48    #[inline]
49    fn update(&mut self, value: f64) -> Option<f64> {
50        let overwritten = self.window.push(value).unwrap_or(0.0);
51        self.sum += value - overwritten;
52
53        if self.window.len() >= self.period {
54            Some(self.sum / self.period as f64)
55        } else {
56            None
57        }
58    }
59}
60
61impl Indicator for SMA {
62    type Output = f64;
63
64    fn next(&mut self, candle: &Candle) -> Option<Self::Output> {
65        self.update(candle.close)
66    }
67
68    fn reset(&mut self) {
69        self.sum = 0.0;
70        self.window = RingBuf::new(self.period, 0.0);
71    }
72
73    fn warmup_period(&self) -> usize {
74        self.period
75    }
76
77    fn clone_boxed(&self) -> Box<dyn Indicator<Output = Self::Output>> {
78        Box::new(self.clone())
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn computes_sma_after_warmup() {
88        let mut sma = SMA::new(3);
89        let candles = [1.0, 2.0, 3.0, 4.0]
90            .iter()
91            .map(|c| Candle {
92                timestamp: 0,
93                open: *c,
94                high: *c,
95                low: *c,
96                close: *c,
97                volume: 0.0,
98            })
99            .collect::<Vec<_>>();
100
101        let mut outputs = Vec::new();
102        for c in &candles {
103            outputs.push(sma.next(c));
104        }
105
106        assert_eq!(outputs[0], None);
107        assert_eq!(outputs[1], None);
108        assert_eq!(outputs[2], Some(2.0));
109        assert_eq!(outputs[3], Some(3.0));
110    }
111}