use crate::core::Method;
use crate::core::{Error, PeriodType, ValueType, Window, OHLCV};
use crate::helpers::Peekable;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ADI {
cmf_sum: ValueType,
window: Window<ValueType>,
}
impl ADI {
#[must_use]
#[deprecated(since = "0.6.1", note = "Use `Peekable::peek` instead")]
pub const fn get_value(&self) -> ValueType {
self.cmf_sum
}
}
impl Method for ADI {
type Params = PeriodType;
type Input = dyn OHLCV;
type Output = ValueType;
fn new(length: Self::Params, candle: &Self::Input) -> Result<Self, Error> {
let mut cmf_sum = 0.0;
let window = if length > 0 {
let clvv = candle.clv() * candle.volume();
cmf_sum = clvv * length as ValueType;
Window::new(length, clvv)
} else {
Window::empty()
};
Ok(Self { cmf_sum, window })
}
#[inline]
fn next(&mut self, candle: &Self::Input) -> Self::Output {
let clvv = candle.clv() * candle.volume();
self.cmf_sum += clvv;
if !self.window.is_empty() {
self.cmf_sum -= self.window.push(clvv);
}
self.peek()
}
}
impl Peekable<<Self as Method>::Output> for ADI {
fn peek(&self) -> <Self as Method>::Output {
self.cmf_sum
}
}
#[cfg(test)]
#[allow(clippy::suboptimal_flops)]
mod tests {
use super::ADI;
use crate::core::OHLCV;
use crate::core::{Candle, Method};
use crate::helpers::RandomCandles;
use crate::helpers::{assert_eq_float, assert_neq_float};
use crate::methods::tests::test_const;
#[test]
fn test_adi_const() {
let candle = Candle {
open: 121.0,
high: 133.0,
low: 49.0,
close: 70.0,
volume: 531.0,
};
for i in 1..30 {
let mut adi = ADI::new(i, &candle).unwrap();
let output = adi.next(&candle);
test_const(&mut adi, &candle, &output);
}
}
#[test]
#[should_panic(expected = "assertion")]
fn test_adi_windowless_const() {
let candle = Candle {
open: 121.0,
high: 133.0,
low: 49.0,
close: 70.0,
volume: 531.0,
};
let mut adi = ADI::new(0, &candle).unwrap();
let output = adi.next(&candle);
test_const(&mut adi, &candle, &output);
}
#[test]
fn test_adi() {
let mut candles = RandomCandles::default();
let first_candle = candles.first();
let mut adi = ADI::new(0, &first_candle).unwrap();
candles.take(100).fold(0., |s, candle| {
assert_eq_float(adi.next(&candle), s + candle.clv() * candle.volume());
s + candle.clv() * candle.volume()
});
}
#[test]
fn test_adi_windowed() {
let mut candles = RandomCandles::default();
let first = candles.first();
let mut adi = ADI::new(0, &first).unwrap();
let mut adiw = [
ADI::new(1, &first).unwrap(),
ADI::new(2, &first).unwrap(),
ADI::new(3, &first).unwrap(),
ADI::new(4, &first).unwrap(),
ADI::new(5, &first).unwrap(),
];
candles
.take(adiw.len())
.enumerate()
.for_each(|(i, candle)| {
let v1 = adi.next(&candle);
adiw.iter_mut().enumerate().for_each(|(j, adiw)| {
let v2 = adiw.next(&candle);
if i == j {
assert_eq_float(v1, v2);
} else {
assert_neq_float(v1, v2);
}
});
});
}
}