use crate::indicators::utils::{validate_data_length, validate_period};
use crate::indicators::{Candle, Indicator, IndicatorError};
use std::collections::VecDeque;
#[derive(Debug)]
pub struct Vroc {
period: usize,
volume_buffer: VecDeque<f64>,
}
impl Vroc {
pub fn new(period: usize) -> Result<Self, IndicatorError> {
validate_period(period, 1)?;
Ok(Self {
period,
volume_buffer: VecDeque::with_capacity(period + 1),
})
}
}
impl Indicator<Candle, f64> for Vroc {
fn calculate(&mut self, data: &[Candle]) -> Result<Vec<f64>, IndicatorError> {
validate_data_length(data, self.period + 1)?;
let n = data.len();
let mut result = Vec::with_capacity(n - self.period);
self.reset();
for i in self.period..n {
let current_volume = data[i].volume;
let past_volume = data[i - self.period].volume;
if past_volume == 0.0 {
return Err(IndicatorError::CalculationError(
"Division by zero: past volume is zero".to_string(),
));
}
let vroc = (current_volume - past_volume) / past_volume * 100.0;
result.push(vroc);
}
Ok(result)
}
fn next(&mut self, value: Candle) -> Result<Option<f64>, IndicatorError> {
self.volume_buffer.push_back(value.volume);
if self.volume_buffer.len() > self.period + 1 {
self.volume_buffer.pop_front();
}
if self.volume_buffer.len() == self.period + 1 {
let current_volume = self.volume_buffer.back().unwrap();
let past_volume = self.volume_buffer.front().unwrap();
if *past_volume == 0.0 {
return Err(IndicatorError::CalculationError(
"Division by zero: past volume is zero".to_string(),
));
}
let vroc = (current_volume - past_volume) / past_volume * 100.0;
Ok(Some(vroc))
} else {
Ok(None)
}
}
fn reset(&mut self) {
self.volume_buffer.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indicators::Candle;
#[test]
fn test_vroc_new() {
assert!(Vroc::new(14).is_ok());
assert!(Vroc::new(0).is_err());
}
#[test]
fn test_vroc_calculation() {
let mut vroc = Vroc::new(2).unwrap();
let candles = vec![
Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1000.0,
},
Candle {
timestamp: 2,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1200.0,
},
Candle {
timestamp: 3,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1500.0,
},
Candle {
timestamp: 4,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 900.0,
},
];
let result = vroc.calculate(&candles).unwrap();
assert_eq!(result.len(), 2);
assert!((result[0] - 50.0).abs() < 0.01);
assert!((result[1] - (-25.0)).abs() < 0.01);
}
#[test]
fn test_vroc_next() {
let mut vroc = Vroc::new(2).unwrap();
let candle1 = Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1000.0,
};
assert_eq!(vroc.next(candle1).unwrap(), None);
let candle2 = Candle {
timestamp: 2,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1200.0,
};
assert_eq!(vroc.next(candle2).unwrap(), None);
let candle3 = Candle {
timestamp: 3,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1500.0,
};
let result = vroc.next(candle3).unwrap();
assert!((result.unwrap() - 50.0).abs() < 0.01);
}
#[test]
fn test_vroc_reset() {
let mut vroc = Vroc::new(2).unwrap();
let candle1 = Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1000.0,
};
vroc.next(candle1).unwrap();
let candle2 = Candle {
timestamp: 2,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1200.0,
};
vroc.next(candle2).unwrap();
vroc.reset();
let candle3 = Candle {
timestamp: 3,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1500.0,
};
assert_eq!(vroc.next(candle3).unwrap(), None);
}
#[test]
fn test_vroc_past_volume_zero() {
let mut vroc = Vroc::new(2).unwrap();
let candles = vec![
Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 0.0, },
Candle {
timestamp: 2,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1200.0,
},
Candle {
timestamp: 3,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1500.0,
},
];
let result = vroc.calculate(&candles);
assert!(result.is_err());
if let Err(IndicatorError::CalculationError(msg)) = result {
assert!(msg.contains("division by zero") || msg.contains("zero"));
} else {
panic!("Expected CalculationError");
}
vroc.reset();
assert_eq!(vroc.next(candles[0]).unwrap(), None); assert_eq!(vroc.next(candles[1]).unwrap(), None);
let next_result = vroc.next(candles[2]);
assert!(next_result.is_err());
if let Err(IndicatorError::CalculationError(msg)) = next_result {
assert!(msg.contains("division by zero") || msg.contains("zero"));
} else {
panic!("Expected CalculationError");
}
}
#[test]
fn test_vroc_minimum_period() {
let mut vroc = Vroc::new(1).unwrap();
let candles = vec![
Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1000.0,
},
Candle {
timestamp: 2,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1500.0,
},
Candle {
timestamp: 3,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 2000.0,
},
];
let result = vroc.calculate(&candles).unwrap();
assert_eq!(result.len(), 2);
assert!((result[0] - 50.0).abs() < 0.001);
assert!((result[1] - 33.33).abs() < 0.01);
}
#[test]
fn test_vroc_large_period() {
let data_length = 10;
let period = data_length - 1;
let mut vroc = Vroc::new(period).unwrap();
let mut candles = Vec::with_capacity(data_length);
for i in 0..data_length {
candles.push(Candle {
timestamp: i as u64,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1000.0 * (i + 1) as f64, });
}
let result = vroc.calculate(&candles).unwrap();
assert_eq!(result.len(), 1);
assert!((result[0] - 900.0).abs() < 0.001);
let mut vroc_large = Vroc::new(9).unwrap();
let result = vroc_large.calculate(&candles);
assert!(result.is_ok());
let mut vroc_too_large = Vroc::new(10).unwrap();
let result = vroc_too_large.calculate(&candles);
assert!(result.is_err()); assert!(result.is_err()); }
#[test]
fn test_vroc_reset_streaming() {
let mut vroc = Vroc::new(2).unwrap();
let candles = [
Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1000.0,
},
Candle {
timestamp: 2,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1200.0,
},
Candle {
timestamp: 3,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1500.0,
},
Candle {
timestamp: 4,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1800.0,
},
];
vroc.next(candles[0]).unwrap();
vroc.next(candles[1]).unwrap();
let first_result = vroc.next(candles[2]).unwrap().unwrap();
vroc.reset();
vroc.next(candles[1]).unwrap();
vroc.next(candles[2]).unwrap();
let second_result = vroc.next(candles[3]).unwrap().unwrap();
assert_eq!(first_result, 50.0);
assert_eq!(second_result, 50.0);
vroc.reset();
assert_eq!(vroc.next(candles[0]).unwrap(), None);
assert_eq!(vroc.next(candles[1]).unwrap(), None);
assert!(vroc.next(candles[2]).unwrap().is_some());
}
#[test]
fn test_vroc_batch_vs_streaming() {
let period = 3;
let mut batch_vroc = Vroc::new(period).unwrap();
let mut streaming_vroc = Vroc::new(period).unwrap();
let candles = vec![
Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1000.0,
},
Candle {
timestamp: 2,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1200.0,
},
Candle {
timestamp: 3,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1500.0,
},
Candle {
timestamp: 4,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1800.0,
},
Candle {
timestamp: 5,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 2100.0,
},
];
let batch_result = batch_vroc.calculate(&candles).unwrap();
let mut streaming_result = Vec::new();
for candle in &candles {
if let Some(value) = streaming_vroc.next(*candle).unwrap() {
streaming_result.push(value);
}
}
assert_eq!(batch_result.len(), streaming_result.len());
for i in 0..batch_result.len() {
assert!(
(batch_result[i] - streaming_result[i]).abs() < 0.001,
"Batch and streaming results differ at index {}: batch={}, streaming={}",
i,
batch_result[i],
streaming_result[i]
);
}
}
#[test]
fn test_vroc_extreme_volume_values() {
let mut vroc = Vroc::new(2).unwrap();
let candles = vec![
Candle {
timestamp: 1,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 100.0, },
Candle {
timestamp: 2,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 1_000_000_000.0, },
Candle {
timestamp: 3,
open: 10.0,
high: 11.0,
low: 9.0,
close: 10.0,
volume: 5_000_000_000.0, },
];
let result = vroc.calculate(&candles).unwrap();
assert_eq!(result.len(), 1);
assert!(
result[0] > 4_000_000_000.0,
"Extreme VROC value not calculated correctly"
);
vroc.reset();
assert_eq!(vroc.next(candles[0]).unwrap(), None); assert_eq!(vroc.next(candles[1]).unwrap(), None);
let streaming_result = vroc.next(candles[2]).unwrap().unwrap();
assert!((streaming_result - result[0]).abs() < 0.001);
}
}