yata/methods/
wma.rs

1use crate::core::{Error, PeriodType, ValueType, Window};
2use crate::core::{Method, MovingAverage};
3use crate::helpers::Peekable;
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8/// [Weighted Moving Average](https://en.wikipedia.org/wiki/Moving_average#Weighted_moving_average) of specified `length` for timeseries of type [`ValueType`].
9///
10/// # Parameters
11///
12/// Has a single parameter `length`: [`PeriodType`]
13///
14/// `length` should be > `0`
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::WMA;
29///
30/// // WMA of length=3
31/// let mut wma = WMA::new(3, &3.0).unwrap();
32///
33/// wma.next(&3.0);
34/// wma.next(&6.0);
35///
36/// assert_eq!(wma.next(&9.0), 7.0);
37/// assert_eq!(wma.next(&12.0), 10.0);
38/// ```
39///
40/// # Performance
41///
42/// O(1)
43///
44/// # See also
45///
46/// [Volume Weighted Moving Average](crate::methods::VWMA) for computing weighted moving average with custom weights over every value
47///
48/// [`ValueType`]: crate::core::ValueType
49/// [`PeriodType`]: crate::core::PeriodType
50#[derive(Debug, Clone)]
51#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
52pub struct WMA {
53	invert_sum: ValueType,
54	float_length: ValueType,
55	total: ValueType,
56	numerator: ValueType,
57	window: Window<ValueType>,
58}
59
60impl Method for WMA {
61	type Params = PeriodType;
62	type Input = ValueType;
63	type Output = Self::Input;
64
65	fn new(length: Self::Params, &value: &Self::Input) -> Result<Self, Error> {
66		match length {
67			0 => Err(Error::WrongMethodParameters),
68			length => {
69				let length2 = length as usize;
70				let sum = ((length2 * (length2 + 1)) / 2) as ValueType;
71				let float_length = length as ValueType;
72				Ok(Self {
73					invert_sum: sum.recip(),
74					float_length,
75					total: -value * float_length,
76					numerator: value * sum,
77					window: Window::new(length, value),
78				})
79			}
80		}
81	}
82
83	#[inline]
84	fn next(&mut self, &value: &Self::Input) -> Self::Output {
85		let prev_value = self.window.push(value);
86
87		self.numerator += self.float_length.mul_add(value, self.total);
88		self.total += prev_value - value;
89
90		self.numerator * self.invert_sum
91	}
92}
93
94impl MovingAverage for WMA {}
95
96impl Peekable<<Self as Method>::Output> for WMA {
97	fn peek(&self) -> <Self as Method>::Output {
98		self.numerator * self.invert_sum
99	}
100}
101
102#[cfg(test)]
103#[allow(clippy::suboptimal_flops)]
104mod tests {
105	use super::{Method, WMA as TestingMethod};
106	use crate::core::ValueType;
107	use crate::helpers::{assert_eq_float, RandomCandles};
108	use crate::methods::tests::test_const;
109	use crate::methods::Conv;
110
111	#[test]
112	fn test_wma_const() {
113		for i in 1..255 {
114			let input = (i as ValueType + 56.0) / 16.3251;
115			let mut method = TestingMethod::new(i, &input).unwrap();
116
117			let output = method.next(&input);
118			test_const(&mut method, &input, &output);
119		}
120	}
121
122	#[test]
123	fn test_wma1() {
124		let mut candles = RandomCandles::default();
125
126		let mut ma = TestingMethod::new(1, &candles.first().close).unwrap();
127
128		candles.take(100).for_each(|x| {
129			assert_eq_float(x.close, ma.next(&x.close));
130		});
131	}
132
133	#[test]
134	fn test_wma() {
135		let candles = RandomCandles::default();
136
137		let src: Vec<ValueType> = candles.take(300).map(|x| x.close).collect();
138
139		(1..255).for_each(|ma_length| {
140			let mut ma = TestingMethod::new(ma_length, &src[0]).unwrap();
141			let mut conv =
142				Conv::new((1..=ma_length).map(|x| x as ValueType).collect(), &src[0]).unwrap();
143			let ma_length = ma_length as usize;
144
145			let div = (1..=ma_length).sum::<usize>() as ValueType;
146			src.iter().enumerate().for_each(|(i, x)| {
147				let value = ma.next(x);
148				let value2 = (0..ma_length).fold(0.0, |s, v| {
149					let j = i.saturating_sub(v);
150					s + src[j] * (ma_length - v) as ValueType
151				}) / div;
152				let value3 = conv.next(x);
153
154				assert_eq_float(value2, value);
155				assert_eq_float(value3, value);
156			});
157		});
158	}
159}