use indicato_rs_proc::{Apply, Evaluate};
use crate::{
fin_error::{FinError, FinErrorType},
traits::{Apply, Current, Evaluate, Executable, ExecutionContext, IoState},
};
fn calculate_emas(input: f64, k: f64, current: f64, is_new: bool) -> f64 {
match is_new {
true => input,
false => (input - current) * k + current,
}
}
#[derive(Apply, Evaluate)]
pub struct ExponentialMovingAverage {
current: f64,
k: f64,
is_new: bool,
}
impl ExponentialMovingAverage {
pub fn new(period: usize) -> Result<Self, FinError> {
match period {
0 => Err(FinError::new(
FinErrorType::InvalidInput,
"Period must be greater than 0",
)),
_ => Ok(Self {
k: 2.0 / (period + 1) as f64,
current: 0.0,
is_new: true,
}),
}
}
}
impl IoState for ExponentialMovingAverage {
type Input = f64;
type Output = f64;
}
impl Executable for ExponentialMovingAverage {
fn execute(&mut self, input: f64, execution_context: &ExecutionContext) -> Self::Output {
let result = calculate_emas(input, self.k, self.current, self.is_new);
match execution_context {
ExecutionContext::Apply => {
self.current = result;
self.is_new = false;
}
ExecutionContext::Evaluate => {}
}
result
}
}
impl Current for ExponentialMovingAverage {
fn current(&self) -> f64 {
self.current
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_apply() {
let mut ema = ExponentialMovingAverage::new(3).unwrap();
assert_eq!(ema.apply(2.0), 2.0);
assert_eq!(ema.apply(5.0), 3.5);
assert_eq!(ema.apply(1.0), 2.25);
assert_eq!(ema.apply(6.25), 4.25);
}
#[test]
fn test_evaluate() {
let mut ema = ExponentialMovingAverage::new(3).unwrap();
assert_eq!(ema.apply(1.0), 1.0);
assert_eq!(ema.apply(2.0), 1.5);
assert_eq!(ema.apply(3.0), 2.25);
assert_eq!(ema.apply(4.0), 3.125);
assert_eq!(ema.evaluate(5.0), 4.0625);
assert_eq!(ema.apply(5.0), 4.0625);
}
#[test]
fn test_current() {
let mut ema = ExponentialMovingAverage::new(3).unwrap();
assert_eq!(ema.apply(1.0), 1.0);
assert_eq!(ema.apply(2.0), 1.5);
assert_eq!(ema.apply(3.0), 2.25);
assert_eq!(ema.apply(4.0), 3.125);
assert_eq!(ema.current(), 3.125);
}
#[test]
fn test_invalid_period() {
let ema = ExponentialMovingAverage::new(0);
assert!(ema.is_err());
}
#[test]
fn zero_ema_input() {
let mut ema = ExponentialMovingAverage::new(3).unwrap();
assert_eq!(ema.apply(0.0), 0.0);
assert_eq!(ema.apply(0.0), 0.0);
assert_eq!(ema.apply(0.0), 0.0);
}
}