use crate::{
core::{Error, Method, MovingAverage, PeriodType, ValueType, Window},
helpers::Peekable,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[doc(alias = "VariableIndexDynamicAverage")]
#[doc(alias = "Variable")]
#[doc(alias = "Index")]
#[doc(alias = "Dynamic")]
#[doc(alias = "Average")]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Vidya {
f: ValueType,
up_sum: ValueType,
dn_sum: ValueType,
last_input: ValueType,
last_output: ValueType,
window: Window<ValueType>,
}
impl Vidya {
#[must_use]
pub const fn get_last_value(&self) -> <Self as Method>::Output {
self.last_output
}
}
impl Method for Vidya {
type Params = PeriodType;
type Input = ValueType;
type Output = Self::Input;
fn new(length: Self::Params, &input: &Self::Input) -> Result<Self, Error> {
match length {
0 | PeriodType::MAX => Err(Error::WrongMethodParameters),
length => Ok(Self {
f: 2. / (1 + length) as ValueType,
up_sum: 0.,
dn_sum: 0.,
last_input: input,
last_output: input,
window: Window::new(length, 0.),
}),
}
}
#[inline]
fn next(&mut self, &input: &Self::Input) -> Self::Output {
let change = input - self.last_input;
self.last_input = input;
let left_change = self.window.push(change);
self.up_sum -= left_change * (left_change > 0.) as u8 as ValueType;
self.dn_sum += left_change * (left_change < 0.) as u8 as ValueType;
self.up_sum += change * (change > 0.) as u8 as ValueType;
self.dn_sum -= change * (change < 0.) as u8 as ValueType;
self.last_output = if self.up_sum != 0. || self.dn_sum != 0. {
let cmo = ((self.up_sum - self.dn_sum) / (self.up_sum + self.dn_sum)).abs();
let f_cmo = self.f * cmo;
input.mul_add(f_cmo, (1.0 - f_cmo) * self.last_output)
} else {
input
};
self.last_output
}
}
impl MovingAverage for Vidya {}
impl Peekable<<Self as Method>::Output> for Vidya {
fn peek(&self) -> <Self as Method>::Output {
self.last_output
}
}
#[cfg(test)]
mod tests {
use super::Vidya as TestingMethod;
use super::{Method, ValueType};
use crate::helpers::{assert_eq_float, RandomCandles};
use crate::methods::tests::test_const;
#[test]
fn test_vidya_const() {
for i in 1..255 {
let input = (i as ValueType + 56.0) / 16.3251;
let mut method = TestingMethod::new(i, &input).unwrap();
let output = method.next(&input);
test_const(&mut method, &input, &output);
}
}
#[test]
fn test_vidya1() {
let mut candles = RandomCandles::default();
let mut ma = TestingMethod::new(1, &candles.first().close).unwrap();
candles.take(100).for_each(|x| {
assert_eq_float(x.close, ma.next(&x.close));
});
}
#[test]
#[allow(clippy::suboptimal_flops)]
fn test_vidya() {
let candles = RandomCandles::default();
let src: Vec<ValueType> = candles.take(300).map(|x| x.close).collect();
let change: Vec<_> = (0..1)
.map(|_| 0.0)
.chain(src.windows(2).map(|x| x[1] - x[0]))
.collect();
let pos_change: Vec<_> = change
.iter()
.map(|&x| if x > 0.0 { x } else { 0.0 })
.collect();
let neg_change: Vec<_> = change
.iter()
.map(|&x| if x < 0.0 { x.abs() } else { 0.0 })
.collect();
(1..255).for_each(|ma_length| {
let mut ma = TestingMethod::new(ma_length, &src[0]).unwrap();
let ma_length = ma_length as usize;
let mut value = src[0];
src.iter().enumerate().for_each(|(i, &x)| {
let from_slice = i.saturating_sub(ma_length - 1);
let pos: ValueType = pos_change[from_slice..=i].iter().sum();
let neg: ValueType = neg_change[from_slice..=i].iter().sum();
value = if (pos + neg) == 0.0 {
x
} else {
let cmo = (pos - neg) / (pos + neg);
let f = 2.0 / (ma_length + 1) as ValueType;
x * f * cmo.abs() + value * (1.0 - f * cmo.abs())
};
assert_eq_float(value, ma.next(&x));
});
});
}
}