mantis_ta/indicators/volatility/
stddev.rs1use crate::indicators::Indicator;
2use crate::types::Candle;
3use crate::utils::ringbuf::RingBuf;
4
5#[derive(Debug, Clone)]
34pub struct StdDev {
35 period: usize,
36 window: RingBuf<f64>,
37}
38
39impl StdDev {
40 pub fn new(period: usize) -> Self {
41 assert!(period > 0, "period must be > 0");
42 Self {
43 period,
44 window: RingBuf::new(period, 0.0),
45 }
46 }
47
48 #[inline]
49 fn update(&mut self, value: f64) -> Option<f64> {
50 self.window.push(value);
51
52 if self.window.len() < self.period {
53 return None;
54 }
55
56 let mean = self.window.iter().sum::<f64>() / self.period as f64;
57 let variance = self
58 .window
59 .iter()
60 .map(|v| {
61 let diff = v - mean;
62 diff * diff
63 })
64 .sum::<f64>()
65 / self.period as f64;
66
67 Some(variance.sqrt())
68 }
69}
70
71impl Indicator for StdDev {
72 type Output = f64;
73
74 fn next(&mut self, candle: &Candle) -> Option<Self::Output> {
75 self.update(candle.close)
76 }
77
78 fn reset(&mut self) {
79 self.window = RingBuf::new(self.period, 0.0);
80 }
81
82 fn warmup_period(&self) -> usize {
83 self.period
84 }
85
86 fn clone_boxed(&self) -> Box<dyn Indicator<Output = Self::Output>> {
87 Box::new(self.clone())
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn computes_stddev_after_warmup() {
97 let mut stddev = StdDev::new(3);
98 let candles = [1.0, 2.0, 3.0, 4.0]
99 .iter()
100 .map(|c| Candle {
101 timestamp: 0,
102 open: *c,
103 high: *c,
104 low: *c,
105 close: *c,
106 volume: 0.0,
107 })
108 .collect::<Vec<_>>();
109
110 let mut outputs = Vec::new();
111 for c in &candles {
112 outputs.push(stddev.next(c));
113 }
114
115 assert_eq!(outputs[0], None);
116 assert_eq!(outputs[1], None);
117 assert!(outputs[2].is_some());
119 assert!((outputs[2].unwrap() - 0.8165).abs() < 0.001);
120 assert!(outputs[3].is_some());
121 }
122
123 #[test]
124 fn stddev_reset_clears_state() {
125 let mut stddev = StdDev::new(3);
126 let candle = Candle {
127 timestamp: 0,
128 open: 1.0,
129 high: 1.0,
130 low: 1.0,
131 close: 1.0,
132 volume: 0.0,
133 };
134
135 stddev.next(&candle);
136 stddev.next(&candle);
137 stddev.next(&candle);
138 assert!(stddev.next(&candle).is_some());
139
140 stddev.reset();
141 assert_eq!(stddev.next(&candle), None);
142 }
143
144 #[test]
145 fn stddev_with_constant_values() {
146 let mut stddev = StdDev::new(3);
147 let candles = [5.0, 5.0, 5.0]
148 .iter()
149 .map(|c| Candle {
150 timestamp: 0,
151 open: *c,
152 high: *c,
153 low: *c,
154 close: *c,
155 volume: 0.0,
156 })
157 .collect::<Vec<_>>();
158
159 let outputs: Vec<_> = candles.iter().map(|c| stddev.next(c)).collect();
160 assert_eq!(outputs[0], None);
161 assert_eq!(outputs[1], None);
162 assert_eq!(outputs[2], Some(0.0));
163 }
164
165 #[test]
166 fn stddev_warmup_period() {
167 let stddev = StdDev::new(5);
168 assert_eq!(stddev.warmup_period(), 5);
169 }
170}