use super::{MathError, Result};
use std::collections::VecDeque;
#[derive(Debug, Clone)]
pub struct SimpleMovingAverage {
period: usize,
values: VecDeque<f64>,
sum: f64,
count: usize, }
impl SimpleMovingAverage {
pub fn new(period: usize) -> Result<Self> {
if period == 0 {
return Err(MathError::InvalidInput(
"Period must be greater than zero".to_string(),
));
}
Ok(Self {
period,
values: VecDeque::with_capacity(period), sum: 0.0,
count: 0,
})
}
#[inline]
pub fn update(&mut self, value: f64) -> Result<()> {
self.values.push_back(value);
self.sum += value;
self.count += 1;
if self.count > self.period {
if let Some(old_value) = self.values.pop_front() {
self.sum -= old_value;
self.count -= 1;
}
}
Ok(())
}
#[inline]
pub fn value(&self) -> Result<f64> {
if self.count < self.period {
return Err(MathError::InsufficientData(format!(
"Not enough data for SMA calculation. Need {} values, have {}.",
self.period, self.count
)));
}
Ok(self.sum / self.period as f64)
}
#[inline]
pub fn period(&self) -> usize {
self.period
}
pub fn reset(&mut self) {
self.values.clear();
self.sum = 0.0;
self.count = 0;
}
#[inline]
pub fn count(&self) -> usize {
self.count
}
#[inline]
pub fn is_ready(&self) -> bool {
self.count >= self.period
}
}
#[derive(Debug, Clone)]
pub struct ExponentialMovingAverage {
period: usize,
multiplier: f64,
current_ema: Option<f64>,
values_seen: usize,
alpha: f64, }
impl ExponentialMovingAverage {
pub fn new(period: usize) -> Result<Self> {
if period == 0 {
return Err(MathError::InvalidInput(
"Period must be greater than zero".to_string(),
));
}
let multiplier = 2.0 / (period as f64 + 1.0);
let alpha = 2.0 / (period as f64 + 1.0);
Ok(Self {
period,
multiplier,
current_ema: None,
values_seen: 0,
alpha,
})
}
#[inline]
pub fn update(&mut self, value: f64) -> Result<()> {
self.values_seen += 1;
match self.current_ema {
None => {
self.current_ema = Some(value);
}
Some(current) => {
let new_ema = self.alpha * value + (1.0 - self.alpha) * current;
self.current_ema = Some(new_ema);
}
}
Ok(())
}
#[inline]
pub fn value(&self) -> Result<f64> {
self.current_ema.ok_or_else(|| {
MathError::InsufficientData(
"Not enough data for EMA calculation. Need at least 1 value.".to_string(),
)
})
}
#[inline]
pub fn period(&self) -> usize {
self.period
}
pub fn reset(&mut self) {
self.current_ema = None;
self.values_seen = 0;
}
#[inline]
pub fn values_seen(&self) -> usize {
self.values_seen
}
#[inline]
pub fn is_ready(&self) -> bool {
self.current_ema.is_some()
}
#[inline]
pub fn alpha(&self) -> f64 {
self.alpha
}
}
#[derive(Debug, Clone)]
pub struct VolumeWeightedMovingAverage {
period: usize,
price_volume_products: VecDeque<f64>,
volumes: VecDeque<f64>,
sum_price_volume: f64,
sum_volume: f64,
}
impl VolumeWeightedMovingAverage {
pub fn new(period: usize) -> Result<Self> {
if period == 0 {
return Err(MathError::InvalidInput(
"Period must be greater than zero".to_string(),
));
}
Ok(Self {
period,
price_volume_products: VecDeque::with_capacity(period),
volumes: VecDeque::with_capacity(period),
sum_price_volume: 0.0,
sum_volume: 0.0,
})
}
pub fn update(&mut self, price: f64, volume: f64) -> Result<()> {
if volume < 0.0 {
return Err(MathError::InvalidInput(
"Volume cannot be negative".to_string(),
));
}
let price_volume = price * volume;
self.price_volume_products.push_back(price_volume);
self.volumes.push_back(volume);
self.sum_price_volume += price_volume;
self.sum_volume += volume;
if self.price_volume_products.len() > self.period {
if let Some(old_price_volume) = self.price_volume_products.pop_front() {
self.sum_price_volume -= old_price_volume;
}
if let Some(old_volume) = self.volumes.pop_front() {
self.sum_volume -= old_volume;
}
}
Ok(())
}
pub fn value(&self) -> Result<f64> {
if self.price_volume_products.len() < self.period {
return Err(MathError::InsufficientData(format!(
"Not enough data for VWMA calculation. Need {} values, have {}.",
self.period,
self.price_volume_products.len()
)));
}
if self.sum_volume == 0.0 {
return Err(MathError::CalculationError(
"Volume sum is zero, cannot calculate VWMA".to_string(),
));
}
Ok(self.sum_price_volume / self.sum_volume)
}
pub fn period(&self) -> usize {
self.period
}
pub fn reset(&mut self) {
self.price_volume_products.clear();
self.volumes.clear();
self.sum_price_volume = 0.0;
self.sum_volume = 0.0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sma_calculation() {
let mut sma = SimpleMovingAverage::new(3).unwrap();
assert!(sma.value().is_err());
sma.update(2.0).unwrap();
sma.update(4.0).unwrap();
assert!(sma.value().is_err());
sma.update(6.0).unwrap();
assert_eq!(sma.value().unwrap(), 4.0);
sma.update(8.0).unwrap();
assert_eq!(sma.value().unwrap(), 6.0); }
#[test]
fn test_ema_calculation() {
let mut ema = ExponentialMovingAverage::new(3).unwrap();
let prices = [1.0, 2.0, 3.0, 4.0, 5.0];
let mut results = Vec::new();
for price in prices {
ema.update(price).unwrap();
if ema.is_ready() {
results.push(ema.value().unwrap());
}
}
assert_eq!(results.len(), 5); let first_ema = results[0];
assert!(
(0.9..=1.1).contains(&first_ema),
"First EMA value: {}",
first_ema
);
let last_ema = results[results.len() - 1];
assert!(
(4.0..=4.1).contains(&last_ema),
"Last EMA value: {}",
last_ema
);
}
#[test]
fn test_vwma_calculation() {
let mut vwma = VolumeWeightedMovingAverage::new(2).unwrap();
assert!(vwma.value().is_err());
vwma.update(10.0, 100.0).unwrap(); vwma.update(20.0, 200.0).unwrap();
let expected = (10.0 * 100.0 + 20.0 * 200.0) / (100.0 + 200.0);
assert!((vwma.value().unwrap() - expected).abs() < 0.001);
vwma.update(15.0, 300.0).unwrap();
let expected = (20.0 * 200.0 + 15.0 * 300.0) / (200.0 + 300.0);
assert!((vwma.value().unwrap() - expected).abs() < 0.001);
}
}