quant_indicators/
stddev.rs1use quant_primitives::Candle;
4use rust_decimal::Decimal;
5
6use crate::error::IndicatorError;
7use crate::indicator::Indicator;
8use crate::series::Series;
9
10#[derive(Debug, Clone)]
37pub struct StdDev {
38 period: usize,
39 name: String,
40}
41
42impl StdDev {
43 pub fn new(period: usize) -> Result<Self, IndicatorError> {
49 if period == 0 {
50 return Err(IndicatorError::InvalidParameter {
51 message: "StdDev period must be > 0".to_string(),
52 });
53 }
54 Ok(Self {
55 period,
56 name: format!("StdDev({})", period),
57 })
58 }
59
60 pub fn period(&self) -> usize {
62 self.period
63 }
64}
65
66impl Indicator for StdDev {
67 fn name(&self) -> &str {
68 &self.name
69 }
70
71 fn warmup_period(&self) -> usize {
72 self.period
73 }
74
75 fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
76 if candles.len() < self.period {
77 return Err(IndicatorError::InsufficientData {
78 required: self.period,
79 actual: candles.len(),
80 });
81 }
82
83 let mut values = Vec::with_capacity(candles.len() - self.period + 1);
84 let period_dec = Decimal::from(self.period as u64);
85
86 for window in candles.windows(self.period) {
87 let sum: Decimal = window.iter().map(|c| c.close()).sum();
89 let mean = sum / period_dec;
90
91 let variance_sum: Decimal = window
93 .iter()
94 .map(|c| {
95 let diff = c.close() - mean;
96 diff * diff
97 })
98 .sum();
99
100 let variance = variance_sum / period_dec;
102
103 let stddev = decimal_sqrt(variance);
105
106 let ts = window[self.period - 1].timestamp();
108 values.push((ts, stddev));
109 }
110
111 Ok(Series::new(values))
112 }
113}
114
115pub(crate) fn decimal_sqrt(n: Decimal) -> Decimal {
117 if n.is_zero() {
118 return Decimal::ZERO;
119 }
120
121 if n.is_sign_negative() {
122 return Decimal::ZERO; }
124
125 let two = Decimal::TWO;
127 let epsilon = Decimal::new(1, 10); let mut x = n; for _ in 0..20 {
132 let x_new = (x + n / x) / two;
133 if (x_new - x).abs() < epsilon {
134 return x_new.round_dp(10);
135 }
136 x = x_new;
137 }
138
139 x.round_dp(10)
140}
141
142#[cfg(test)]
143#[path = "stddev_tests.rs"]
144mod tests;