yata/methods/
st_dev.rs

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/// Moving [Standard Deviation](https://en.wikipedia.org/wiki/Standard_deviation) over the window of size `length` for timeseries of type [`ValueType`]
9///
10/// # Parameters
11///
12/// Has a single parameter `length`: [`PeriodType`]
13///
14/// `length` should be > `1`
15///
16/// # Input type
17///
18/// Input type is [`ValueType`]
19///
20/// # Output type
21///
22/// Output type is [`ValueType`]
23///
24/// # Examples
25///
26/// ```
27/// use yata::prelude::*;
28/// use yata::methods::StDev;
29///
30/// // StDev over the window with length=3
31/// let mut stdev = StDev::new(3, &1.0).unwrap();
32///
33/// stdev.next(&1.0);
34/// stdev.next(&2.0);
35///
36/// assert_eq!(stdev.next(&3.0), 1.0);
37/// assert_eq!(stdev.next(&4.0), 1.0);
38/// ```
39///
40/// # Performance
41///
42/// O(1)
43///
44/// [`ValueType`]: crate::core::ValueType
45/// [`PeriodType`]: crate::core::PeriodType
46#[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		// same as `value * value - prev_value * prev_value`
90		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		// self.sq_val_sum - self.val_sum * self.mean;
102		let sum = self.val_sum.mul_add(self.mean, self.sq_val_sum);
103
104		(sum * self.k)
105			.abs() // sometimes float values may produce negative values, when sum is really near to zero value
106			.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}