quantwave_core/indicators/
volume_profile.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3use std::collections::VecDeque;
4
5#[derive(Debug, Clone)]
10pub struct VolumeProfile {
11 period: usize,
12 bins: usize,
13 window: VecDeque<(f64, f64)>, }
15
16impl VolumeProfile {
17 pub fn new(period: usize, bins: usize) -> Self {
18 Self {
19 period,
20 bins: bins.max(1),
21 window: VecDeque::with_capacity(period),
22 }
23 }
24}
25
26impl Next<(f64, f64)> for VolumeProfile {
27 type Output = f64;
28
29 fn next(&mut self, (price, volume): (f64, f64)) -> Self::Output {
30 self.window.push_back((price, volume));
31 if self.window.len() > self.period {
32 self.window.pop_front();
33 }
34
35 if self.window.is_empty() {
36 return f64::NAN;
37 }
38
39 let mut min_p = f64::MAX;
41 let mut max_p = f64::MIN;
42 for &(p, _) in self.window.iter() {
43 if p < min_p { min_p = p; }
44 if p > max_p { max_p = p; }
45 }
46
47 if min_p == max_p {
48 return min_p;
49 }
50
51 let mut histogram = vec![0.0; self.bins];
53 let bin_size = (max_p - min_p) / self.bins as f64;
54
55 for &(p, v) in self.window.iter() {
56 let mut bin_idx = ((p - min_p) / bin_size).floor() as usize;
57 if bin_idx >= self.bins {
58 bin_idx = self.bins - 1;
59 }
60 histogram[bin_idx] += v;
61 }
62
63 let mut max_v = -1.0;
65 let mut poc_idx = 0;
66 for (i, &v) in histogram.iter().enumerate() {
67 if v > max_v {
68 max_v = v;
69 poc_idx = i;
70 }
71 }
72
73 min_p + (poc_idx as f64 + 0.5) * bin_size
75 }
76}
77
78pub const VOLUME_PROFILE_METADATA: IndicatorMetadata = IndicatorMetadata {
79 name: "Volume Profile",
80 description: "Calculates the price level with the highest traded volume (Point of Control) over a sliding window.",
81 usage: "Use to identify significant support and resistance levels. The POC represents the price where most market activity occurred, often acting as a magnet for price or a strong barrier. Essential for volume spread analysis and auction market theory.",
82 keywords: &["volume", "profile", "poc", "support-resistance", "auction-market-theory"],
83 ehlers_summary: "Volume Profile is an advanced charting study that displays trading activity over a specified time period at specified price levels. The Point of Control (POC) is the single most important level in the profile, representing the price at which the most volume was traded. It serves as a key benchmark for identifying value areas and potential trend reversals.",
84 params: &[
85 ParamDef {
86 name: "period",
87 default: "200",
88 description: "Sliding window size",
89 },
90 ParamDef {
91 name: "bins",
92 default: "50",
93 description: "Number of price bins in the histogram",
94 },
95 ],
96 formula_source: "https://www.tradingview.com/support/solutions/43000502040-volume-profile-visible-range-vpvr/",
97 formula_latex: r#"
98\[
99BinIdx = \lfloor \frac{Price - Price_{min}}{BinSize} \rfloor
100\]
101\[
102POC = Price_{min} + (Idx_{max\_vol} + 0.5) \times BinSize
103\]
104"#,
105 gold_standard_file: "volume_profile.json",
106 category: "Volume",
107};
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::traits::Next;
113
114 #[test]
115 fn test_volume_profile_basic() {
116 let mut vp = VolumeProfile::new(10, 5);
117 for _ in 0..5 {
119 vp.next((100.0, 10.0));
120 }
121 let res = vp.next((110.0, 5.0));
123 assert!(res >= 100.0 && res <= 105.0); vp.next((110.0, 20.0));
127 vp.next((110.0, 20.0));
128 let res2 = vp.next((110.0, 20.0));
129 assert!(res2 >= 105.0); }
131}