velora_ta/volatility/
std_dev.rs1use chrono::{DateTime, Utc};
10
11use crate::{
12 traits::{Indicator, SingleIndicator},
13 utils::CircularBuffer,
14 IndicatorError, IndicatorResult,
15};
16
17#[derive(Debug, Clone)]
38pub struct StdDev {
39 period: usize,
40 buffer: CircularBuffer<f64>,
41 name: String,
42}
43
44impl StdDev {
45 pub fn new(period: usize) -> IndicatorResult<Self> {
55 if period <= 1 {
56 return Err(IndicatorError::InvalidParameter(
57 "Period must be greater than 1".to_string(),
58 ));
59 }
60
61 Ok(StdDev {
62 period,
63 buffer: CircularBuffer::new(period),
64 name: format!("StdDev({period})"),
65 })
66 }
67
68 fn calculate_std_dev(&self) -> Option<f64> {
70 self.buffer.std_dev()
71 }
72}
73
74impl Indicator for StdDev {
75 fn name(&self) -> &str {
76 &self.name
77 }
78
79 fn warmup_period(&self) -> usize {
80 self.period
81 }
82
83 fn is_ready(&self) -> bool {
84 self.buffer.is_full()
85 }
86
87 fn reset(&mut self) {
88 self.buffer.clear();
89 }
90}
91
92impl SingleIndicator for StdDev {
93 fn update(&mut self, price: f64, _timestamp: DateTime<Utc>) -> IndicatorResult<Option<f64>> {
94 if !price.is_finite() {
95 return Err(IndicatorError::InvalidPrice(
96 "Price must be a finite number".to_string(),
97 ));
98 }
99
100 self.buffer.push(price);
101 Ok(self.calculate_std_dev())
102 }
103
104 fn current(&self) -> Option<f64> {
105 self.calculate_std_dev()
106 }
107
108 fn calculate(&self, prices: &[f64]) -> IndicatorResult<Vec<Option<f64>>> {
109 if prices.is_empty() {
110 return Ok(Vec::new());
111 }
112
113 let mut std_dev = Self::new(self.period)?;
114 let mut result = Vec::with_capacity(prices.len());
115 let timestamp = Utc::now();
116
117 for &price in prices {
118 result.push(std_dev.update(price, timestamp)?);
119 }
120
121 Ok(result)
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn test_std_dev_creation() {
131 let std_dev = StdDev::new(20).unwrap();
132 assert_eq!(std_dev.warmup_period(), 20);
133 assert!(!std_dev.is_ready());
134 assert_eq!(std_dev.name(), "StdDev(20)");
135 }
136
137 #[test]
138 fn test_std_dev_invalid_period() {
139 assert!(StdDev::new(0).is_err());
140 assert!(StdDev::new(1).is_err());
141 }
142
143 #[test]
144 fn test_std_dev_no_volatility() {
145 let mut std_dev = StdDev::new(5).unwrap();
146 let timestamp = Utc::now();
147
148 for _ in 0..5 {
150 std_dev.update(100.0, timestamp).unwrap();
151 }
152
153 let value = std_dev.current().unwrap();
154 assert_eq!(value, 0.0);
155 }
156
157 #[test]
158 fn test_std_dev_high_volatility() {
159 let mut std_dev = StdDev::new(5).unwrap();
160 let timestamp = Utc::now();
161
162 let prices = vec![90.0, 110.0, 85.0, 115.0, 95.0];
164
165 for &price in &prices {
166 std_dev.update(price, timestamp).unwrap();
167 }
168
169 let value = std_dev.current().unwrap();
170 assert!(value > 10.0);
172 }
173
174 #[test]
175 fn test_std_dev_batch_calculation() {
176 let std_dev = StdDev::new(5).unwrap();
177 let prices = vec![100.0, 102.0, 104.0, 106.0, 108.0, 110.0];
178 let values = std_dev.calculate(&prices).unwrap();
179
180 assert_eq!(values.len(), 6);
181
182 assert!(values[4].is_some());
186 assert!(values[5].is_some());
187 }
188
189 #[test]
190 fn test_std_dev_reset() {
191 let mut std_dev = StdDev::new(5).unwrap();
192 let timestamp = Utc::now();
193
194 for i in 1..=10 {
195 std_dev.update(i as f64, timestamp).unwrap();
196 }
197
198 assert!(std_dev.is_ready());
199
200 std_dev.reset();
201 assert!(!std_dev.is_ready());
202 }
203}