mantis_ta/indicators/trend/
sma.rs1use crate::indicators::Indicator;
2use crate::types::Candle;
3use crate::utils::ringbuf::RingBuf;
4
5#[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}