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