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#[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}