inditech 0.1.0

A set of technical indicators for time series analysis
Documentation
use num_traits::Float;
use ringvec::RingVec;
use std::fmt::Debug;
use std::iter::Sum;

use crate::named::Named;
use crate::slider::Slider;

use super::identity::Identity;

#[derive(Debug)]
pub struct EMA<F> {
    internal: Box<dyn Slider<F>>,
    buffer: RingVec<F>,
    weight: F,
}

impl<F> EMA<F>
where
    F: Float + Default + 'static + Debug,
{
    pub fn new(capacity: usize) -> Self {
        let internal: Box<Identity<F>> = Box::new(Identity::new());

        Self {
            internal,
            buffer: RingVec::new(capacity),
            weight: F::from(2.0 / (1.0 + capacity as f32)).unwrap(),
        }
    }

    pub fn wrap(capacity: usize, internal: Box<dyn Slider<F>>) -> Self {
        Self {
            internal,
            buffer: RingVec::new(capacity),
            weight: F::from(2.0 / (1.0 + capacity as f32)).unwrap(),
        }
    }
}

impl<F> Slider<F> for EMA<F>
where
    F: Float + Default + Sum<F> + std::fmt::Debug,
{
    fn last(&self) -> Option<F> {
        self.buffer.peek_newest().copied()
    }

    fn push(&mut self, item: F) {
        self.internal.push(item);
        let internal_last = self.internal.last().unwrap_or(item);

        let new_value = internal_last * self.weight
            + self.buffer.peek_newest().copied().unwrap_or(item) * (F::one() - self.weight);

        self.buffer.push_force(new_value);
    }
}

impl<F> Named for EMA<F>
where
    F: Default,
{
    fn name(&self) -> String {
        format!("ema{}", self.buffer.capacity())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn ema() {
        let mut ema = EMA::new(3);

        ema.push(1.);
        ema.push(2.);
        ema.push(3.);
        ema.push(4.);
        ema.push(5.);
        ema.push(4.);
        ema.push(3.);

        dbg!(ema.last());
    }

    #[test]
    fn alpha() {
        let ema: EMA<f32> = EMA::new(9);

        assert_eq!(ema.weight, 0.2);
    }
}