finance_query/indicators/
vwap.rs1use super::{IndicatorError, Result};
4
5pub fn vwap(
36 highs: &[f64],
37 lows: &[f64],
38 closes: &[f64],
39 volumes: &[f64],
40) -> Result<Vec<Option<f64>>> {
41 if highs.is_empty() {
42 return Err(IndicatorError::InsufficientData { need: 1, got: 0 });
43 }
44
45 if highs.len() != lows.len() || highs.len() != closes.len() || highs.len() != volumes.len() {
46 return Err(IndicatorError::InvalidPeriod(
47 "All arrays must have the same length".to_string(),
48 ));
49 }
50
51 let mut result = Vec::with_capacity(highs.len());
52 let mut pv_sum = 0.0;
53 let mut volume_sum = 0.0;
54
55 for i in 0..closes.len() {
56 let typical_price = (highs[i] + lows[i] + closes[i]) / 3.0;
58 pv_sum += typical_price * volumes[i];
59 volume_sum += volumes[i];
60
61 if volume_sum > 0.0 {
62 result.push(Some(pv_sum / volume_sum));
63 } else {
64 result.push(None);
65 }
66 }
67
68 Ok(result)
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn test_vwap_basic() {
77 let highs = vec![102.0, 104.0, 103.0, 105.0];
78 let lows = vec![100.0, 101.0, 100.5, 102.0];
79 let closes = vec![101.0, 103.0, 102.0, 104.0];
80 let volumes = vec![1000.0, 1200.0, 900.0, 1500.0];
81
82 let result = vwap(&highs, &lows, &closes, &volumes).unwrap();
83
84 assert_eq!(result.len(), 4);
85
86 for val in &result {
88 assert!(val.is_some());
89 }
90
91 for vwap_val in result.iter().flatten() {
93 assert!(vwap_val > &0.0);
94 }
95 }
96
97 #[test]
98 fn test_vwap_empty() {
99 let highs: Vec<f64> = vec![];
100 let lows: Vec<f64> = vec![];
101 let closes: Vec<f64> = vec![];
102 let volumes: Vec<f64> = vec![];
103
104 let result = vwap(&highs, &lows, &closes, &volumes);
105 assert!(result.is_err());
106 }
107
108 #[test]
109 fn test_vwap_mismatched_lengths() {
110 let highs = vec![102.0, 104.0];
111 let lows = vec![100.0];
112 let closes = vec![101.0, 103.0];
113 let volumes = vec![1000.0, 1200.0];
114
115 let result = vwap(&highs, &lows, &closes, &volumes);
116 assert!(result.is_err());
117 }
118}