use crate::core::Method;
use crate::core::{Error, PeriodType, ValueType, Window};
use crate::helpers::Peekable;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct StDev {
mean: ValueType,
val_sum: ValueType,
sq_val_sum: ValueType,
divider: ValueType,
k: ValueType,
window: Window<ValueType>,
}
impl Method for StDev {
type Params = PeriodType;
type Input = ValueType;
type Output = Self::Input;
fn new(length: Self::Params, &value: &Self::Input) -> Result<Self, Error> {
match length {
0 | 1 => Err(Error::WrongMethodParameters),
length => {
let k = ((length - 1) as ValueType).recip();
let float_length = length as ValueType;
let mean = -value;
let divider = -float_length.recip();
Ok(Self {
mean,
val_sum: value * float_length,
sq_val_sum: value * value * float_length,
divider,
k,
window: Window::new(length, value),
})
}
}
}
#[inline]
fn next(&mut self, &value: &Self::Input) -> Self::Output {
let prev_value = self.window.push(value);
let diff = value - prev_value;
self.sq_val_sum += diff * (value + prev_value);
self.val_sum += diff;
self.mean += diff * self.divider;
self.peek()
}
}
impl Peekable<<Self as Method>::Output> for StDev {
fn peek(&self) -> <Self as Method>::Output {
let sum = self.val_sum.mul_add(self.mean, self.sq_val_sum);
(sum * self.k)
.abs() .sqrt()
}
}
#[cfg(test)]
#[allow(clippy::suboptimal_flops)]
mod tests {
use super::{Method, StDev as TestingMethod};
use crate::core::ValueType;
use crate::helpers::{assert_eq_float, RandomCandles};
use crate::methods::tests::test_const_float;
#[test]
fn test_st_dev_const() {
for i in 2..255 {
let input = (i as ValueType + 56.0) / 16.3251;
let mut method = TestingMethod::new(i, &input).unwrap();
test_const_float(&mut method, &input, 0.0);
}
}
#[test]
fn test_st_dev() {
let candles = RandomCandles::default();
let src: Vec<ValueType> = candles
.take(300)
.enumerate()
.map(|(i, x)| x.close * if i % 2 == 0 { 1.0 } else { -1.0 })
.collect();
(2..255).for_each(|ma_length| {
let mut ma = TestingMethod::new(ma_length, &src[0]).unwrap();
let ma_length = ma_length as usize;
src.iter().enumerate().for_each(|(i, x)| {
let mut avg = 0.;
for j in 0..ma_length {
avg += src[i.saturating_sub(j)] / ma_length as ValueType;
}
let mut diff_sq_sum = 0.;
for j in 0..ma_length {
diff_sq_sum +=
(src[i.saturating_sub(j)] - avg).powi(2) / (ma_length - 1) as ValueType;
}
let value = ma.next(x);
let value2 = diff_sq_sum.sqrt();
println!("{value2} <=> {value} at {i} with length {ma_length}");
assert_eq_float(value2, value);
});
});
}
}