yata/methods/
volatility.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/// Calculate moving linear volatility for last `length` values of type [`ValueType`]
9///
10/// LV = Σ\[abs([`Derivative`]\(1\))\]
11///
12/// # Parameters
13///
14/// Has a single parameter `length`: [`PeriodType`]
15///
16/// `length` should be > `0`
17///
18/// # Input type
19///
20/// Input type is [`ValueType`]
21///
22/// # Output type
23///
24/// Output type is [`ValueType`]
25///
26/// Output is always positive or `0.0`
27///
28/// # Examples
29///
30/// ```
31/// use yata::prelude::*;
32/// use yata::methods::LinearVolatility;
33///
34/// // volatility over 3 periods
35/// let mut vol = LinearVolatility::new(3, &1.0).unwrap();
36/// vol.next(&1.0);
37/// vol.next(&2.0);
38/// assert_eq!(vol.next(&3.0), 2.0);
39/// assert_eq!(vol.next(&1.0), 4.0);
40/// ```
41///
42/// # Performance
43///
44/// O(1)
45///
46/// [`Derivative`]: crate::methods::Derivative
47/// [`ValueType`]: crate::core::ValueType
48/// [`PeriodType`]: crate::core::PeriodType
49#[derive(Debug, Clone)]
50#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
51pub struct LinearVolatility {
52	window: Window<ValueType>,
53	prev_value: ValueType,
54	volatility: ValueType,
55}
56
57impl Method for LinearVolatility {
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 => Err(Error::WrongMethodParameters),
65			length => Ok(Self {
66				window: Window::new(length, 0.),
67				prev_value: value,
68				volatility: 0.,
69			}),
70		}
71	}
72
73	#[inline]
74	fn next(&mut self, &value: &Self::Input) -> Self::Output {
75		let derivative = (value - self.prev_value).abs();
76		self.prev_value = value;
77
78		let past_derivative = self.window.push(derivative);
79
80		self.volatility += derivative - past_derivative;
81
82		self.volatility
83	}
84}
85
86impl Peekable<<Self as Method>::Output> for LinearVolatility {
87	fn peek(&self) -> <Self as Method>::Output {
88		self.volatility
89	}
90}
91
92#[cfg(test)]
93mod tests {
94	use super::{LinearVolatility as TestingMethod, Method};
95	use crate::core::ValueType;
96	use crate::helpers::{assert_eq_float, RandomCandles};
97	use crate::methods::tests::test_const;
98	use crate::methods::Derivative;
99
100	#[test]
101	fn test_volatility_const() {
102		for i in 1..255 {
103			let input = (i as ValueType + 56.0) / 16.3251;
104			let mut method = TestingMethod::new(i, &input).unwrap();
105
106			test_const(&mut method, &input, &0.0);
107		}
108	}
109
110	#[test]
111	fn test_volatility1() {
112		let mut candles = RandomCandles::default();
113
114		let mut ma = TestingMethod::new(1, &candles.first().close).unwrap();
115		let mut der = Derivative::new(1, &candles.first().close).unwrap();
116
117		candles.take(100).for_each(|x| {
118			let v1 = der.next(&x.close).abs();
119			let v2 = ma.next(&x.close);
120
121			assert_eq_float(v1, v2);
122		});
123	}
124
125	#[test]
126	fn test_linear_volatility() {
127		let candles = RandomCandles::default();
128
129		let src: Vec<ValueType> = candles.take(300).map(|x| x.close).collect();
130
131		(1..255).for_each(|ma_length| {
132			let mut ma = TestingMethod::new(ma_length, &src[0]).unwrap();
133			let ma_length = ma_length as usize;
134
135			src.iter().enumerate().for_each(|(i, x)| {
136				let mut s = 0.;
137				for j in 0..ma_length {
138					let d = src[i.saturating_sub(j)] - src[i.saturating_sub(j + 1)];
139					s += d.abs();
140				}
141
142				let value = ma.next(x);
143				let value2 = s;
144
145				assert_eq_float(value2, value);
146			});
147		});
148	}
149}