use super::{MathError, Result};
use std::collections::VecDeque;
#[derive(Debug, Clone)]
pub struct VolumeMovingAverage {
period: usize,
volumes: VecDeque<f64>,
sum: f64,
}
impl VolumeMovingAverage {
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,
volumes: VecDeque::with_capacity(period),
sum: 0.0,
})
}
pub fn update(&mut self, volume: f64) -> Result<()> {
if volume < 0.0 {
return Err(MathError::InvalidInput(
"Volume cannot be negative".to_string(),
));
}
self.volumes.push_back(volume);
self.sum += volume;
if self.volumes.len() > self.period {
if let Some(old_volume) = self.volumes.pop_front() {
self.sum -= old_volume;
}
}
Ok(())
}
pub fn value(&self) -> Result<f64> {
if self.volumes.len() < self.period {
return Err(MathError::InsufficientData(format!(
"Not enough data for VMA calculation. Need {} values, have {}.",
self.period,
self.volumes.len()
)));
}
Ok(self.sum / self.period as f64)
}
pub fn period(&self) -> usize {
self.period
}
pub fn reset(&mut self) {
self.volumes.clear();
self.sum = 0.0;
}
}
#[derive(Debug, Clone)]
pub struct OnBalanceVolume {
obv: f64,
previous_close: Option<f64>,
}
impl Default for OnBalanceVolume {
fn default() -> Self {
Self::new()
}
}
impl OnBalanceVolume {
pub fn new() -> Self {
Self {
obv: 0.0,
previous_close: None,
}
}
pub fn update(&mut self, close: f64, volume: f64) -> Result<()> {
if volume < 0.0 {
return Err(MathError::InvalidInput(
"Volume cannot be negative".to_string(),
));
}
if let Some(prev_close) = self.previous_close {
if close > prev_close {
self.obv += volume;
} else if close < prev_close {
self.obv -= volume;
}
}
self.previous_close = Some(close);
Ok(())
}
pub fn value(&self) -> Result<f64> {
if self.previous_close.is_none() {
return Err(MathError::InsufficientData(
"Not enough data for OBV calculation. Need at least one price-volume update."
.to_string(),
));
}
Ok(self.obv)
}
pub fn reset(&mut self) {
self.obv = 0.0;
self.previous_close = None;
}
}
#[derive(Debug, Clone)]
pub struct VolumeRateOfChange {
period: usize,
volumes: VecDeque<f64>,
}
impl VolumeRateOfChange {
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,
volumes: VecDeque::with_capacity(period + 1),
})
}
pub fn update(&mut self, volume: f64) -> Result<()> {
if volume < 0.0 {
return Err(MathError::InvalidInput(
"Volume cannot be negative".to_string(),
));
}
self.volumes.push_back(volume);
if self.volumes.len() > self.period + 1 {
self.volumes.pop_front();
}
Ok(())
}
pub fn value(&self) -> Result<f64> {
if self.volumes.len() <= self.period {
return Err(MathError::InsufficientData(format!(
"Not enough data for VROC calculation. Need at least {} values, have {}.",
self.period + 1,
self.volumes.len()
)));
}
let current_volume = *self.volumes.back().unwrap();
let old_volume = self.volumes[0];
if old_volume == 0.0 {
return Err(MathError::CalculationError(
"Cannot calculate VROC: old volume is zero".to_string(),
));
}
Ok((current_volume - old_volume) / old_volume * 100.0)
}
pub fn period(&self) -> usize {
self.period
}
pub fn reset(&mut self) {
self.volumes.clear();
}
}
#[derive(Debug, Clone)]
pub struct VolumePriceTrend {
vpt: f64,
previous_close: Option<f64>,
}
impl Default for VolumePriceTrend {
fn default() -> Self {
Self::new()
}
}
impl VolumePriceTrend {
pub fn new() -> Self {
Self {
vpt: 0.0,
previous_close: None,
}
}
pub fn update(&mut self, close: f64, volume: f64) -> Result<()> {
if volume < 0.0 {
return Err(MathError::InvalidInput(
"Volume cannot be negative".to_string(),
));
}
if let Some(prev_close) = self.previous_close {
let price_change_percent = (close - prev_close) / prev_close;
self.vpt += volume * price_change_percent;
}
self.previous_close = Some(close);
Ok(())
}
pub fn value(&self) -> Result<f64> {
if self.previous_close.is_none() {
return Err(MathError::InsufficientData(
"Not enough data for VPT calculation. Need at least one price-volume update."
.to_string(),
));
}
Ok(self.vpt)
}
pub fn reset(&mut self) {
self.vpt = 0.0;
self.previous_close = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vma_calculation() {
let mut vma = VolumeMovingAverage::new(3).unwrap();
vma.update(1000.0).unwrap();
vma.update(1500.0).unwrap();
vma.update(2000.0).unwrap();
let vma_value = vma.value().unwrap();
assert!((vma_value - 1500.0).abs() < 0.001);
vma.update(3000.0).unwrap();
let updated_vma = vma.value().unwrap();
assert!((updated_vma - 2166.67).abs() < 0.01);
}
#[test]
fn test_obv_calculation() {
let mut obv = OnBalanceVolume::new();
assert!(obv.value().is_err());
obv.update(10.0, 1000.0).unwrap();
assert_eq!(obv.value().unwrap(), 0.0);
obv.update(11.0, 1500.0).unwrap();
assert_eq!(obv.value().unwrap(), 1500.0);
obv.update(10.5, 2000.0).unwrap();
assert_eq!(obv.value().unwrap(), -500.0);
obv.update(10.5, 1000.0).unwrap();
assert_eq!(obv.value().unwrap(), -500.0);
}
#[test]
fn test_vroc_calculation() {
let mut vroc = VolumeRateOfChange::new(2).unwrap();
vroc.update(1000.0).unwrap();
vroc.update(1200.0).unwrap();
assert!(vroc.value().is_err());
vroc.update(1500.0).unwrap();
let vroc_value = vroc.value().unwrap();
assert!((vroc_value - 50.0).abs() < 0.001);
}
#[test]
fn test_vpt_calculation() {
let mut vpt = VolumePriceTrend::new();
assert!(vpt.value().is_err());
vpt.update(10.0, 1000.0).unwrap();
assert_eq!(vpt.value().unwrap(), 0.0);
vpt.update(11.0, 1500.0).unwrap();
assert!((vpt.value().unwrap() - 150.0).abs() < 0.001);
vpt.update(10.45, 2000.0).unwrap();
let vpt_value = vpt.value().unwrap();
assert!((vpt_value - 50.0).abs() < 0.01);
}
}