use std::collections::VecDeque;
use crate::bar_indicators::indicator_value::IndicatorValue;
#[derive(Debug, Clone)]
pub struct Vwap {
period: usize,
price_volume_buffer: VecDeque<f64>,
volume_buffer: VecDeque<f64>,
cumulative_pv: f64,
cumulative_vol: f64,
value: f64,
}
impl Vwap {
pub fn is_ready(&self) -> bool {
self.cumulative_vol > 0.0
}
pub fn reset(&mut self) {
self.price_volume_buffer.clear();
self.volume_buffer.clear();
self.cumulative_pv = 0.0;
self.cumulative_vol = 0.0;
self.value = 0.0;
}
pub fn period(&self) -> usize {
self.period
}
pub fn new(period: usize) -> Self {
let period = period.max(1);
Self {
period,
price_volume_buffer: VecDeque::with_capacity(period),
volume_buffer: VecDeque::with_capacity(period),
cumulative_pv: 0.0,
cumulative_vol: 0.0,
value: 0.0,
}
}
pub fn update_bar(&mut self, _open: f64, high: f64, low: f64, close: f64, volume: f64) -> f64 {
let typical = (high + low + close) / 3.0;
let pv = typical * volume;
self.price_volume_buffer.push_back(pv);
self.volume_buffer.push_back(volume);
self.cumulative_pv += pv;
self.cumulative_vol += volume;
if self.price_volume_buffer.len() > self.period {
if let Some(old_pv) = self.price_volume_buffer.pop_front() {
self.cumulative_pv -= old_pv;
}
if let Some(old_vol) = self.volume_buffer.pop_front() {
self.cumulative_vol -= old_vol;
}
}
if self.cumulative_vol > 0.0 {
self.value = self.cumulative_pv / self.cumulative_vol;
}
self.value
}
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vwap_basic_calculation() {
let mut vwap = Vwap::new(3);
vwap.update_bar(0.0, 105.0, 95.0, 100.0, 1000.0);
vwap.update_bar(0.0, 115.0, 105.0, 110.0, 2000.0);
let v3 = vwap.update_bar(0.0, 110.0, 100.0, 105.0, 1000.0);
assert!(vwap.is_ready());
assert!((v3 - 106.25).abs() < 0.01);
}
#[test]
fn test_vwap_reset() {
let mut vwap = Vwap::new(3);
vwap.update_bar(0.0, 105.0, 95.0, 100.0, 1000.0);
vwap.update_bar(0.0, 115.0, 105.0, 110.0, 1000.0);
assert!(vwap.is_ready());
vwap.reset();
assert!(!vwap.is_ready());
}
}