1use crate::core::Method;
2use crate::core::{Error, PeriodType, ValueType, Window};
3use crate::helpers::Peekable;
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone)]
47#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
48pub struct StDev {
49 mean: ValueType,
50 val_sum: ValueType,
51 sq_val_sum: ValueType,
52 divider: ValueType,
53 k: ValueType,
54 window: Window<ValueType>,
55}
56
57impl Method for StDev {
58 type Params = PeriodType;
59 type Input = ValueType;
60 type Output = Self::Input;
61
62 fn new(length: Self::Params, &value: &Self::Input) -> Result<Self, Error> {
63 match length {
64 0 | 1 => Err(Error::WrongMethodParameters),
65 length => {
66 let k = ((length - 1) as ValueType).recip();
67
68 let float_length = length as ValueType;
69 let mean = -value;
70 let divider = -float_length.recip();
71
72 Ok(Self {
73 mean,
74 val_sum: value * float_length,
75 sq_val_sum: value * value * float_length,
76 divider,
77 k,
78 window: Window::new(length, value),
79 })
80 }
81 }
82 }
83
84 #[inline]
85 fn next(&mut self, &value: &Self::Input) -> Self::Output {
86 let prev_value = self.window.push(value);
87 let diff = value - prev_value;
88
89 self.sq_val_sum += diff * (value + prev_value);
91
92 self.val_sum += diff;
93 self.mean += diff * self.divider;
94
95 self.peek()
96 }
97}
98
99impl Peekable<<Self as Method>::Output> for StDev {
100 fn peek(&self) -> <Self as Method>::Output {
101 let sum = self.val_sum.mul_add(self.mean, self.sq_val_sum);
103
104 (sum * self.k)
105 .abs() .sqrt()
107 }
108}
109
110#[cfg(test)]
111#[allow(clippy::suboptimal_flops)]
112mod tests {
113 use super::{Method, StDev as TestingMethod};
114 use crate::core::ValueType;
115 use crate::helpers::{assert_eq_float, RandomCandles};
116 use crate::methods::tests::test_const_float;
117
118 #[test]
119 fn test_st_dev_const() {
120 for i in 2..255 {
121 let input = (i as ValueType + 56.0) / 16.3251;
122 let mut method = TestingMethod::new(i, &input).unwrap();
123
124 test_const_float(&mut method, &input, 0.0);
125 }
126 }
127
128 #[test]
129 fn test_st_dev() {
130 let candles = RandomCandles::default();
131
132 let src: Vec<ValueType> = candles
133 .take(300)
134 .enumerate()
135 .map(|(i, x)| x.close * if i % 2 == 0 { 1.0 } else { -1.0 })
136 .collect();
137
138 (2..255).for_each(|ma_length| {
139 let mut ma = TestingMethod::new(ma_length, &src[0]).unwrap();
140 let ma_length = ma_length as usize;
141
142 src.iter().enumerate().for_each(|(i, x)| {
143 let mut avg = 0.;
144 for j in 0..ma_length {
145 avg += src[i.saturating_sub(j)] / ma_length as ValueType;
146 }
147
148 let mut diff_sq_sum = 0.;
149 for j in 0..ma_length {
150 diff_sq_sum +=
151 (src[i.saturating_sub(j)] - avg).powi(2) / (ma_length - 1) as ValueType;
152 }
153
154 let value = ma.next(x);
155 let value2 = diff_sq_sum.sqrt();
156
157 println!("{value2} <=> {value} at {i} with length {ma_length}");
158 assert_eq_float(value2, value);
159 });
160 });
161 }
162}