quantwave_core/indicators/
range_bars.rs1#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct RangeBar {
15 pub open: f64,
16 pub high: f64,
17 pub low: f64,
18 pub close: f64,
19}
20
21#[derive(Debug, Clone)]
23pub struct RangeBarBuilder {
24 range_size: f64,
25 open: f64,
26 high: f64,
27 low: f64,
28 initialized: bool,
29}
30
31impl RangeBarBuilder {
32 pub fn new(range_size: f64) -> Self {
34 Self {
35 range_size,
36 open: 0.0,
37 high: 0.0,
38 low: 0.0,
39 initialized: false,
40 }
41 }
42
43 pub fn next(&mut self, price: f64) -> Option<RangeBar> {
46 if self.range_size <= 0.0 || !price.is_finite() {
47 return None;
48 }
49 if !self.initialized {
50 self.open = price;
51 self.high = price;
52 self.low = price;
53 self.initialized = true;
54 return None;
55 }
56 if price > self.high {
57 self.high = price;
58 }
59 if price < self.low {
60 self.low = price;
61 }
62 if self.high - self.low >= self.range_size {
63 let bar = RangeBar {
64 open: self.open,
65 high: self.high,
66 low: self.low,
67 close: price,
68 };
69 self.open = price;
71 self.high = price;
72 self.low = price;
73 return Some(bar);
74 }
75 None
76 }
77}
78
79pub fn range_bars_batch(prices: &[f64], range_size: f64) -> Vec<RangeBar> {
81 let mut b = RangeBarBuilder::new(range_size);
82 let mut out = Vec::new();
83 for &p in prices {
84 if let Some(bar) = b.next(p) {
85 out.push(bar);
86 }
87 }
88 out
89}
90
91pub fn range_bars_atr_batch(prices: &[f64], atr: f64, multiplier: f64) -> Vec<RangeBar> {
93 range_bars_batch(prices, atr * multiplier)
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use proptest::prelude::*;
100
101 #[test]
102 fn gold_single_bar() {
103 let bars = range_bars_batch(&[10.0, 10.5, 11.0, 12.0], 2.0);
105 assert_eq!(bars.len(), 1);
106 assert_eq!(bars[0].open, 10.0);
107 assert_eq!(bars[0].close, 12.0);
108 assert_eq!(bars[0].high, 12.0);
109 assert_eq!(bars[0].low, 10.0);
110 }
111
112 #[test]
113 fn gold_span_includes_both_directions() {
114 let bars = range_bars_batch(&[10.0, 11.0, 9.0], 2.0);
116 assert_eq!(bars.len(), 1);
117 assert_eq!(bars[0].high, 11.0);
118 assert_eq!(bars[0].low, 9.0);
119 assert_eq!(bars[0].close, 9.0);
120 }
121
122 #[test]
123 fn no_bar_within_range() {
124 assert!(range_bars_batch(&[10.0, 10.9, 10.1, 10.5], 2.0).is_empty());
125 }
126
127 proptest! {
128 #[test]
130 fn streaming_equals_batch(
131 prices in prop::collection::vec(0.0f64..1000.0, 1..200),
132 range_size in 0.5f64..50.0,
133 ) {
134 let batch = range_bars_batch(&prices, range_size);
135 let mut b = RangeBarBuilder::new(range_size);
136 let mut streamed = Vec::new();
137 for &p in &prices {
138 if let Some(bar) = b.next(p) {
139 streamed.push(bar);
140 }
141 }
142 prop_assert_eq!(streamed, batch);
143 }
144
145 #[test]
147 fn bar_span_at_least_range(
148 prices in prop::collection::vec(0.0f64..1000.0, 1..200),
149 range_size in 0.5f64..50.0,
150 ) {
151 for bar in range_bars_batch(&prices, range_size) {
152 prop_assert!(bar.high - bar.low >= range_size - 1e-9);
153 prop_assert!(bar.high >= bar.open && bar.high >= bar.close);
154 prop_assert!(bar.low <= bar.open && bar.low <= bar.close);
155 }
156 }
157 }
158}