use crate::ring::Ring;
use crate::traits::Indicator;
const RECOMPUTE_EVERY: usize = 16;
#[derive(Debug, Clone)]
pub struct Sma<const N: usize> {
buf: Ring<N>,
sum: f64,
updates_since_recompute: usize,
}
impl<const N: usize> Sma<N> {
#[must_use]
pub const fn new() -> Self {
Self {
buf: Ring::new(),
sum: 0.0,
updates_since_recompute: 0,
}
}
#[must_use]
pub fn value(&self) -> Option<f64> {
if self.buf.is_full() {
Some(self.sum / N as f64)
} else {
None
}
}
}
impl<const N: usize> Default for Sma<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> Indicator for Sma<N> {
type Input = f64;
fn update(&mut self, input: f64) -> Option<f64> {
if !input.is_finite() {
return self.value();
}
match self.buf.push(input) {
Some(old) => {
self.sum -= old;
self.sum += input;
}
None => {
self.sum += input;
}
}
self.updates_since_recompute += 1;
if self.updates_since_recompute >= RECOMPUTE_EVERY * N {
self.sum = self.buf.chrono_sum();
self.updates_since_recompute = 0;
}
self.value()
}
fn reset(&mut self) {
*self = Self::new();
}
fn warmup_period(&self) -> usize {
N
}
fn is_ready(&self) -> bool {
self.buf.is_full()
}
fn name(&self) -> &'static str {
"sma"
}
}
#[cfg(test)]
mod tests {
use super::Sma;
use crate::traits::Indicator;
#[test]
fn warmup_then_mean() {
let mut sma = Sma::<4>::new();
assert!(!sma.is_ready());
for x in [10.0, 20.0, 30.0] {
assert_eq!(sma.update(x), None);
}
assert_eq!(sma.update(40.0), Some(25.0));
assert!(sma.is_ready());
assert_eq!(sma.warmup_period(), 4);
}
#[test]
fn reset_clears_state() {
let mut sma = Sma::<3>::new();
for x in [1.0, 2.0, 3.0, 4.0] {
sma.update(x);
}
assert!(sma.is_ready());
sma.reset();
assert!(!sma.is_ready());
assert_eq!(sma.value(), None);
}
#[test]
fn non_finite_is_ignored() {
let mut sma = Sma::<2>::new();
sma.update(2.0);
let ready = sma.update(4.0);
assert_eq!(ready, Some(3.0));
assert_eq!(sma.update(f64::NAN), Some(3.0));
assert_eq!(sma.update(f64::INFINITY), Some(3.0));
}
#[test]
fn byte_parity_with_wickra_core() {
let mut ours = Sma::<20>::new();
let mut theirs = wickra_core::Sma::new(20).unwrap();
for i in 0..2000 {
let x = 100.0 + 25.0 * (f64::from(i) * 0.05).sin() + 0.01 * f64::from(i);
let a = ours.update(x);
let b = <wickra_core::Sma as wickra_core::Indicator>::update(&mut theirs, x);
assert_eq!(
a.map(f64::to_bits),
b.map(f64::to_bits),
"divergence at index {i}"
);
}
}
}