Skip to main content

mantis_ta/indicators/volume/
volume_sma.rs

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