quantwave_core/indicators/
volume_profile.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3use crate::utils::RingBuffer as 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 {
44 min_p = p;
45 }
46 if p > max_p {
47 max_p = p;
48 }
49 }
50
51 if min_p == max_p {
52 return min_p;
53 }
54
55 let mut histogram = vec![0.0; self.bins];
57 let bin_size = (max_p - min_p) / self.bins as f64;
58
59 for &(p, v) in self.window.iter() {
60 let mut bin_idx = ((p - min_p) / bin_size).floor() as usize;
61 if bin_idx >= self.bins {
62 bin_idx = self.bins - 1;
63 }
64 histogram[bin_idx] += v;
65 }
66
67 let mut max_v = -1.0;
69 let mut poc_idx = 0;
70 for (i, &v) in histogram.iter().enumerate() {
71 if v > max_v {
72 max_v = v;
73 poc_idx = i;
74 }
75 }
76
77 min_p + (poc_idx as f64 + 0.5) * bin_size
79 }
80}
81
82pub const VOLUME_PROFILE_METADATA: IndicatorMetadata = IndicatorMetadata {
83 name: "Volume Profile",
84 description: "Calculates the price level with the highest traded volume (Point of Control) over a sliding window.",
85 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.",
86 keywords: &[
87 "volume",
88 "profile",
89 "poc",
90 "support-resistance",
91 "auction-market-theory",
92 ],
93 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.",
94 params: &[
95 ParamDef {
96 name: "period",
97 default: "200",
98 description: "Sliding window size",
99 },
100 ParamDef {
101 name: "bins",
102 default: "50",
103 description: "Number of price bins in the histogram",
104 },
105 ],
106 formula_source: "https://www.tradingview.com/support/solutions/43000502040-volume-profile-visible-range-vpvr/",
107 formula_latex: r#"
108\[
109BinIdx = \lfloor \frac{Price - Price_{min}}{BinSize} \rfloor
110\]
111\[
112POC = Price_{min} + (Idx_{max\_vol} + 0.5) \times BinSize
113\]
114"#,
115 gold_standard_file: "volume_profile.json",
116 category: "Volume",
117};
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::traits::Next;
123
124 #[test]
125 fn test_volume_profile_basic() {
126 let mut vp = VolumeProfile::new(10, 5);
127 for _ in 0..5 {
129 vp.next((100.0, 10.0));
130 }
131 let res = vp.next((110.0, 5.0));
133 assert!(res >= 100.0 && res <= 105.0); vp.next((110.0, 20.0));
137 vp.next((110.0, 20.0));
138 let res2 = vp.next((110.0, 20.0));
139 assert!(res2 >= 105.0); }
141}