use crate::core::{FormicaXError, OHLCV};
use chrono::{DateTime, Utc};
use std::collections::VecDeque;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum VWAPType {
Session,
Rolling { window_size: usize },
Anchored { anchor_time: DateTime<Utc> },
Custom {
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
},
}
#[derive(Debug, Clone)]
pub struct VWAPResult {
pub vwap: f64,
pub total_volume: f64,
pub data_points: usize,
pub calculated_at: DateTime<Utc>,
pub calculation_time: Duration,
}
#[derive(Debug)]
pub struct VWAPCalculator {
vwap_type: VWAPType,
window_buffer: VecDeque<OHLCV>,
last_vwap: Option<f64>,
performance_tracker: PerformanceTracker,
}
#[derive(Debug)]
struct PerformanceTracker {
total_calculations: usize,
total_time: Duration,
min_time: Duration,
max_time: Duration,
}
impl VWAPCalculator {
pub fn new() -> Self {
Self {
vwap_type: VWAPType::Session,
window_buffer: VecDeque::new(),
last_vwap: None,
performance_tracker: PerformanceTracker::new(),
}
}
pub fn session_based() -> Self {
Self {
vwap_type: VWAPType::Session,
window_buffer: VecDeque::new(),
last_vwap: None,
performance_tracker: PerformanceTracker::new(),
}
}
pub fn rolling_window(window_size: usize) -> Self {
Self {
vwap_type: VWAPType::Rolling { window_size },
window_buffer: VecDeque::with_capacity(window_size),
last_vwap: None,
performance_tracker: PerformanceTracker::new(),
}
}
pub fn anchored(anchor_time: DateTime<Utc>) -> Self {
Self {
vwap_type: VWAPType::Anchored { anchor_time },
window_buffer: VecDeque::new(),
last_vwap: None,
performance_tracker: PerformanceTracker::new(),
}
}
pub fn with_type(mut self, vwap_type: VWAPType) -> Self {
self.vwap_type = vwap_type;
self
}
pub fn calculate(&self, data: &[OHLCV]) -> Result<VWAPResult, FormicaXError> {
if data.is_empty() {
return Err(FormicaXError::Data(crate::core::DataError::EmptyDataset));
}
let start_time = Instant::now();
let filtered_data = self.filter_data(data)?;
if filtered_data.is_empty() {
return Err(FormicaXError::Data(crate::core::DataError::EmptyDataset));
}
let (vwap, total_volume) = self.compute_vwap(&filtered_data);
let calculation_time = start_time.elapsed();
Ok(VWAPResult {
vwap,
total_volume,
data_points: filtered_data.len(),
calculated_at: Utc::now(),
calculation_time,
})
}
pub fn calculate_incremental(
&mut self,
new_data: &[OHLCV],
) -> Result<VWAPResult, FormicaXError> {
let start_time = Instant::now();
if let VWAPType::Rolling { window_size } = self.vwap_type {
for ohlcv in new_data {
self.window_buffer.push_back(ohlcv.clone());
if self.window_buffer.len() > window_size {
self.window_buffer.pop_front();
}
}
let data: Vec<OHLCV> = self.window_buffer.iter().cloned().collect();
let (vwap, total_volume) = self.compute_vwap(&data);
let calculation_time = start_time.elapsed();
self.last_vwap = Some(vwap);
self.performance_tracker.record(calculation_time);
Ok(VWAPResult {
vwap,
total_volume,
data_points: data.len(),
calculated_at: Utc::now(),
calculation_time,
})
} else {
self.calculate(new_data)
}
}
pub fn last_vwap(&self) -> Option<f64> {
self.last_vwap
}
pub fn performance_stats(&self) -> VWAPPerformanceStats {
self.performance_tracker.stats()
}
fn filter_data(&self, data: &[OHLCV]) -> Result<Vec<OHLCV>, FormicaXError> {
match self.vwap_type {
VWAPType::Session => {
Ok(data.to_vec())
}
VWAPType::Rolling { window_size } => {
let start = if data.len() > window_size {
data.len() - window_size
} else {
0
};
Ok(data[start..].to_vec())
}
VWAPType::Anchored { anchor_time } => {
Ok(data
.iter()
.filter(|ohlcv| ohlcv.timestamp >= anchor_time)
.cloned()
.collect())
}
VWAPType::Custom {
start_time,
end_time,
} => {
Ok(data
.iter()
.filter(|ohlcv| ohlcv.timestamp >= start_time && ohlcv.timestamp <= end_time)
.cloned()
.collect())
}
}
}
fn compute_vwap(&self, data: &[OHLCV]) -> (f64, f64) {
let mut cumulative_pv = 0.0; let mut total_volume = 0.0;
for ohlcv in data {
let typical_price = (ohlcv.high + ohlcv.low + ohlcv.close) / 3.0;
let volume = ohlcv.volume as f64;
cumulative_pv += typical_price * volume;
total_volume += volume;
}
let vwap = if total_volume > 0.0 {
cumulative_pv / total_volume
} else {
0.0
};
(vwap, total_volume)
}
}
impl PerformanceTracker {
fn new() -> Self {
Self {
total_calculations: 0,
total_time: Duration::ZERO,
min_time: Duration::from_secs(u64::MAX),
max_time: Duration::ZERO,
}
}
fn record(&mut self, duration: Duration) {
self.total_calculations += 1;
self.total_time += duration;
self.min_time = self.min_time.min(duration);
self.max_time = self.max_time.max(duration);
}
fn stats(&self) -> VWAPPerformanceStats {
let avg_time = if self.total_calculations > 0 {
self.total_time / self.total_calculations as u32
} else {
Duration::ZERO
};
VWAPPerformanceStats {
total_calculations: self.total_calculations,
average_time: avg_time,
min_time: self.min_time,
max_time: self.max_time,
total_time: self.total_time,
}
}
}
#[derive(Debug, Clone)]
pub struct VWAPPerformanceStats {
pub total_calculations: usize,
pub average_time: Duration,
pub min_time: Duration,
pub max_time: Duration,
pub total_time: Duration,
}
impl Default for VWAPCalculator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Utc;
fn create_test_data() -> Vec<OHLCV> {
vec![
OHLCV::new(Utc::now(), 100.0, 105.0, 98.0, 102.0, 1000),
OHLCV::new(Utc::now(), 102.0, 107.0, 100.0, 104.0, 1200),
OHLCV::new(Utc::now(), 104.0, 109.0, 102.0, 106.0, 1100),
]
}
#[test]
fn test_vwap_calculator_creation() {
let calculator = VWAPCalculator::new();
assert_eq!(calculator.vwap_type, VWAPType::Session);
}
#[test]
fn test_session_vwap_calculation() {
let data = create_test_data();
let calculator = VWAPCalculator::session_based();
let result = calculator.calculate(&data).unwrap();
assert!(result.vwap > 0.0);
assert_eq!(result.data_points, 3);
assert!(result.total_volume > 0.0);
assert!(result.calculation_time < Duration::from_millis(1));
}
#[test]
fn test_rolling_vwap_calculation() {
let data = create_test_data();
let mut calculator = VWAPCalculator::rolling_window(2);
let result = calculator.calculate_incremental(&data).unwrap();
assert_eq!(result.data_points, 2);
assert!(result.vwap > 0.0);
}
#[test]
fn test_empty_data_error() {
let calculator = VWAPCalculator::new();
let result = calculator.calculate(&[]);
assert!(result.is_err());
}
#[test]
fn test_anchored_vwap() {
let anchor_time = Utc::now();
let data = create_test_data();
let calculator = VWAPCalculator::anchored(anchor_time);
let result = calculator.calculate(&data).unwrap();
assert!(result.vwap > 0.0);
assert!(result.data_points > 0);
}
#[test]
fn test_performance_tracking() {
let data = create_test_data();
let mut calculator = VWAPCalculator::rolling_window(10);
for _ in 0..5 {
calculator.calculate_incremental(&data).unwrap();
}
let stats = calculator.performance_stats();
assert_eq!(stats.total_calculations, 5);
assert!(stats.average_time > Duration::ZERO);
assert!(stats.min_time > Duration::ZERO);
assert!(stats.max_time > Duration::ZERO);
}
}