use indicato_rs_proc::{Apply, Evaluate};
use crate::{
fin_error::{FinError, FinErrorType},
traits::{Apply, Current, Evaluate, Executable, ExecutionContext, IoState},
};
use super::ExponentialMovingAverage;
#[derive(Apply, Evaluate)]
pub struct MovingAverageConvergenceDivergence {
short_ema: ExponentialMovingAverage,
long_ema: ExponentialMovingAverage,
}
impl IoState for MovingAverageConvergenceDivergence {
type Input = f64;
type Output = f64;
}
impl MovingAverageConvergenceDivergence {
pub fn new(short_period: usize, long_period: usize) -> Result<Self, FinError> {
match (short_period, long_period) {
(0, _) | (_, 0) => Err(FinError::new(
FinErrorType::InvalidInput,
"Periods must be greater than 0",
)),
_ => Ok(Self {
short_ema: ExponentialMovingAverage::new(short_period)?,
long_ema: ExponentialMovingAverage::new(long_period)?,
}),
}
}
}
impl Current for MovingAverageConvergenceDivergence {
fn current(&self) -> Self::Output {
self.short_ema.current() - self.long_ema.current()
}
}
impl Executable for MovingAverageConvergenceDivergence {
fn execute(&mut self, input: f64, execution_context: &ExecutionContext) -> Self::Output {
let short_ema = self.short_ema.execute(input, execution_context);
let long_ema = self.long_ema.execute(input, execution_context);
short_ema - long_ema
}
}
#[cfg(test)]
mod test {
use approx::assert_abs_diff_eq;
use super::*;
#[test]
fn test_macd() {
let mut macd = MovingAverageConvergenceDivergence::new(2, 4).unwrap();
assert_eq!(macd.apply(3.0), 0.0);
assert_abs_diff_eq!(macd.apply(4.8), 0.48, epsilon = 10e-7);
assert_abs_diff_eq!(macd.apply(6.3), 0.848, epsilon = 10e-7);
assert_abs_diff_eq!(macd.apply(5.0), 0.3488, epsilon = 10e-7);
assert_abs_diff_eq!(macd.evaluate(10.0), 1.48928, epsilon = 10e-7);
assert_abs_diff_eq!(macd.current(), 0.3488, epsilon = 10e-7);
}
#[test]
fn test_macd_new_invalid() {
assert!(MovingAverageConvergenceDivergence::new(0, 0).is_err());
assert!(MovingAverageConvergenceDivergence::new(0, 1).is_err());
assert!(MovingAverageConvergenceDivergence::new(1, 0).is_err());
}
}