Skip to main content

finance_query/indicators/
vwap.rs

1//! Volume Weighted Average Price (VWAP) indicator.
2
3use super::{IndicatorError, Result};
4
5/// Calculate Volume Weighted Average Price (VWAP).
6///
7/// VWAP is the average price weighted by volume. It's commonly used as a trading benchmark.
8/// Formula: VWAP = Σ(Typical Price × Volume) / Σ(Volume)
9/// where Typical Price = (High + Low + Close) / 3
10///
11/// # Arguments
12///
13/// * `highs` - High prices
14/// * `lows` - Low prices
15/// * `closes` - Close prices
16/// * `volumes` - Trading volumes
17///
18/// # Returns
19///
20/// Vector of cumulative VWAP values.
21///
22/// # Example
23///
24/// ```
25/// use finance_query::indicators::vwap;
26///
27/// let highs = vec![102.0, 104.0, 103.0, 105.0];
28/// let lows = vec![100.0, 101.0, 100.5, 102.0];
29/// let closes = vec![101.0, 103.0, 102.0, 104.0];
30/// let volumes = vec![1000.0, 1200.0, 900.0, 1500.0];
31///
32/// let result = vwap(&highs, &lows, &closes, &volumes).unwrap();
33/// assert_eq!(result.len(), 4);
34/// ```
35pub 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        // Typical Price = (H + L + C) / 3
57        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        // All values should exist
87        for val in &result {
88            assert!(val.is_some());
89        }
90
91        // VWAP should be reasonable (between low and high ranges)
92        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}