yata/methods/
vwma.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/// [Volume Weighed Moving Average](https://en.wikipedia.org/wiki/Moving_average#Weighted_moving_average) of specified `length`
9/// for timeseries of type ([`ValueType`], [`ValueType`]) which represents pair of values (`value`, `volume`)
10///
11/// # Parameters
12///
13/// `length` should be > `0`
14///
15/// Has a single parameter `length`: [`PeriodType`]
16///
17/// # Input type
18///
19/// Input type is [`ValueType`]
20///
21/// # Output type
22///
23/// Output type is [`ValueType`]
24///
25/// # Examples
26///
27/// ```
28/// use yata::prelude::*;
29/// use yata::methods::VWMA;
30///
31/// // VWMA of length=3
32/// let mut vwma = VWMA::new(3, &(3.0, 1.0)).unwrap();
33///
34/// // input value is a pair of f64 (value, weight)
35/// vwma.next(&(3.0, 1.0));
36/// vwma.next(&(6.0, 1.0));
37///
38/// assert_eq!(vwma.next(&(9.0, 2.0)), 6.75);
39/// assert!((vwma.next(&(12.0, 0.5))- 8.571428571428571).abs() < 1e-10);
40/// ```
41///
42/// # Performance
43///
44/// O(1)
45///
46/// [`ValueType`]: crate::core::ValueType
47/// [`PeriodType`]: crate::core::PeriodType
48#[derive(Debug, Clone)]
49#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
50pub struct VWMA {
51	sum: ValueType,
52	vol_sum: ValueType,
53	window: Window<(ValueType, ValueType)>,
54}
55
56impl Method for VWMA {
57	type Params = PeriodType;
58	type Input = (ValueType, ValueType);
59	type Output = ValueType;
60
61	fn new(length: Self::Params, &value: &Self::Input) -> Result<Self, Error> {
62		match length {
63			0 => Err(Error::WrongMethodParameters),
64			length => Ok(Self {
65				sum: value.0 * value.1 * length as ValueType,
66				vol_sum: value.1 * length as ValueType,
67				window: Window::new(length, value),
68			}),
69		}
70	}
71
72	#[inline]
73	fn next(&mut self, &value: &Self::Input) -> Self::Output {
74		let past_value = self.window.push(value);
75
76		self.vol_sum += value.1 - past_value.1;
77		self.sum += value.0.mul_add(value.1, -past_value.0 * past_value.1);
78
79		self.sum / self.vol_sum
80	}
81}
82
83impl Peekable<<Self as Method>::Output> for VWMA {
84	fn peek(&self) -> <Self as Method>::Output {
85		self.sum / self.vol_sum
86	}
87}
88
89#[cfg(test)]
90#[allow(clippy::suboptimal_flops)]
91mod tests {
92	use super::{Method, VWMA as TestingMethod};
93	use crate::core::ValueType;
94	use crate::helpers::{assert_eq_float, RandomCandles};
95	use crate::methods::tests::test_const;
96
97	#[test]
98	fn test_vwma_const() {
99		for i in 1..255 {
100			let input = ((i as ValueType + 56.0) / 16.3251, 3.55);
101			let mut method = TestingMethod::new(i, &input).unwrap();
102
103			let output = method.next(&input);
104			test_const(&mut method, &input, &output);
105		}
106	}
107
108	#[test]
109	fn test_vwma1() {
110		let mut candles = RandomCandles::default();
111
112		let mut ma =
113			TestingMethod::new(1, &(candles.first().close, candles.first().volume)).unwrap();
114
115		candles.take(100).for_each(|x| {
116			assert_eq_float(x.close, ma.next(&(x.close, x.volume)));
117		});
118	}
119
120	#[test]
121	fn test_vwma() {
122		let candles = RandomCandles::default();
123
124		let src: Vec<(ValueType, ValueType)> =
125			candles.take(300).map(|x| (x.close, x.volume)).collect();
126
127		(1..255).for_each(|ma_length| {
128			let mut ma = TestingMethod::new(ma_length, &src[0]).unwrap();
129			let ma_length = ma_length as usize;
130
131			src.iter().enumerate().for_each(|(i, x)| {
132				let mut slice: Vec<(ValueType, ValueType)> = Vec::with_capacity(ma_length);
133				for x in 0..ma_length {
134					slice.push(src[i.saturating_sub(x)]);
135				}
136
137				let sum = slice
138					.iter()
139					.fold(0.0, |s, (close, volume)| s + close * volume);
140				let vol_sum = slice.iter().fold(0.0, |s, (_close, vol)| s + vol);
141
142				let value2 = sum / vol_sum;
143
144				assert_eq_float(value2, ma.next(x));
145			});
146		});
147	}
148}