use indicato_rs_proc::{Apply, Evaluate};
use crate::{
fin_error::{FinError, FinErrorType},
traits::{Apply, Current, Evaluate, Executable, ExecutionContext, IoState},
};
fn calculate_wilders(input: f64, previous: f64, period: usize) -> f64 {
(previous * (period as f64 - 1.0) + input) / period as f64
}
#[derive(Apply, Evaluate)]
pub struct WildersSmoothing {
period: usize,
current: Option<f64>,
cumulative: f64,
previous: f64,
seed_count: usize,
}
impl IoState for WildersSmoothing {
type Input = f64;
type Output = Option<f64>;
}
impl WildersSmoothing {
pub fn new(period: usize) -> Result<Self, FinError> {
match period {
0 => Err(FinError::new(
FinErrorType::InvalidInput,
"Period must be greater than 0",
)),
_ => Ok(Self {
period,
previous: 0.0,
current: None,
cumulative: 0.0,
seed_count: 1,
}),
}
}
}
impl Executable for WildersSmoothing {
fn execute(
&mut self,
input: Self::Input,
execution_context: &ExecutionContext,
) -> Self::Output {
match execution_context {
ExecutionContext::Apply => {
if self.seed_count < self.period {
self.cumulative += input;
self.previous = self.cumulative / self.seed_count as f64;
self.seed_count += 1;
None
} else {
let smoothed_result = calculate_wilders(input, self.previous, self.period);
self.current = Some(smoothed_result);
self.previous = smoothed_result;
self.current
}
}
ExecutionContext::Evaluate => {
if self.seed_count < self.period {
None
} else {
let current = calculate_wilders(input, self.previous, self.period);
Some(current)
}
}
}
}
}
impl Current for WildersSmoothing {
fn current(&self) -> Self::Output {
if self.seed_count < self.period {
None
} else {
self.current
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_apply() {
let mut ws = WildersSmoothing::new(3).unwrap();
assert_eq!(ws.apply(1.0), None);
assert_eq!(ws.apply(2.0), None);
assert_eq!(ws.apply(3.0), Some(2.0));
assert_eq!(ws.apply(2.0), Some(2.0));
assert_eq!(ws.apply(5.0), Some(3.0));
}
#[test]
fn test_evaluate() {
let mut ws = WildersSmoothing::new(3).unwrap();
assert_eq!(ws.apply(1.0), None);
assert_eq!(ws.apply(2.0), None);
assert_eq!(ws.apply(3.0), Some(2.0));
assert_eq!(ws.apply(2.0), Some(2.0));
assert_eq!(ws.evaluate(5.0), Some(3.0));
assert_eq!(ws.apply(5.0), Some(3.0));
}
#[test]
fn test_current() {
let ws = WildersSmoothing::new(3).unwrap();
assert!(ws.current().is_none());
}
#[test]
fn test_invalid_period() {
let ws = WildersSmoothing::new(0);
assert!(ws.is_err());
}
}