1use crate::types::Candle;
7use std::collections::VecDeque;
8
9pub struct MarketStructure {
10 swing_len: usize,
11 atr_mult: f64,
12 maxlen: usize,
13
14 highs: VecDeque<f64>,
15 lows: VecDeque<f64>,
16 closes: VecDeque<f64>,
17
18 swing_hi: Option<f64>,
19 swing_lo: Option<f64>,
20 prev_swing_hi: Option<f64>,
21 prev_swing_lo: Option<f64>,
22 atr: Option<f64>,
23 bias_internal: i8,
24 fib_hi: Option<f64>,
25 fib_lo: Option<f64>,
26 fib_dir: i8,
27 last_broken_hi: Option<f64>,
28 last_broken_lo: Option<f64>,
29
30 pub bias: i8,
32 pub fib618: Option<f64>,
33 pub fib500: Option<f64>,
34 pub fib382: Option<f64>,
35 pub fib786: Option<f64>,
36 pub in_discount: bool,
37 pub in_premium: bool,
38 pub bos: bool,
39 pub choch: bool,
40 pub choch_dir: i8,
41 pub confluence: f64,
43}
44
45impl MarketStructure {
46 pub fn new(swing_len: usize, atr_mult_min: f64) -> Self {
47 let maxlen = swing_len * 4 + 10;
48 Self {
49 swing_len,
50 atr_mult: atr_mult_min,
51 maxlen,
52 highs: VecDeque::with_capacity(maxlen),
53 lows: VecDeque::with_capacity(maxlen),
54 closes: VecDeque::with_capacity(maxlen),
55 swing_hi: None,
56 swing_lo: None,
57 prev_swing_hi: None,
58 prev_swing_lo: None,
59 atr: None,
60 bias_internal: 0,
61 fib_hi: None,
62 fib_lo: None,
63 fib_dir: 0,
64 last_broken_hi: None,
65 last_broken_lo: None,
66 bias: 0,
67 fib618: None,
68 fib500: None,
69 fib382: None,
70 fib786: None,
71 in_discount: false,
72 in_premium: false,
73 bos: false,
74 choch: false,
75 choch_dir: 0,
76 confluence: 0.0,
77 }
78 }
79
80 pub fn update(&mut self, candle: &Candle) {
81 if self.highs.len() == self.maxlen {
82 self.highs.pop_front();
83 }
84 if self.lows.len() == self.maxlen {
85 self.lows.pop_front();
86 }
87 if self.closes.len() == self.maxlen {
88 self.closes.pop_front();
89 }
90 self.highs.push_back(candle.high);
91 self.lows.push_back(candle.low);
92 self.closes.push_back(candle.close);
93
94 let prev_c = if self.closes.len() >= 2 {
96 *self.closes.iter().rev().nth(1).unwrap()
97 } else {
98 candle.close
99 };
100 let tr = (candle.high - candle.low)
101 .max((candle.high - prev_c).abs())
102 .max((candle.low - prev_c).abs());
103 self.atr = Some(match self.atr {
104 None => tr,
105 Some(prev) => prev / 14.0 + tr * (1.0 - 1.0 / 14.0),
106 });
107 let atr = self.atr.unwrap_or(1e-9).max(1e-9);
108
109 let ph = self.pivot_high();
110 let pl = self.pivot_low();
111
112 self.bos = false;
113 self.choch = false;
114 self.choch_dir = 0;
115
116 if let Some(ph_val) = ph {
117 let atr_ok = self
118 .swing_lo
119 .is_none_or(|slo| (ph_val - slo) >= atr * self.atr_mult);
120 if atr_ok {
121 self.prev_swing_hi = self.swing_hi;
122 self.swing_hi = Some(ph_val);
123 }
124 }
125 if let Some(pl_val) = pl {
126 let atr_ok = self
127 .swing_hi
128 .is_none_or(|shi| (shi - pl_val) >= atr * self.atr_mult);
129 if atr_ok {
130 self.prev_swing_lo = self.swing_lo;
131 self.swing_lo = Some(pl_val);
132 }
133 }
134
135 let cl = candle.close;
136
137 if let Some(shi) = self.swing_hi
138 && cl > shi
139 && self.last_broken_hi != Some(shi)
140 {
141 if self.bias_internal <= 0 {
142 self.choch = true;
143 self.choch_dir = 1;
144 self.fib_dir = 1;
145 self.fib_hi = Some(candle.high);
146 self.fib_lo = self.swing_lo;
147 } else {
148 self.bos = true;
149 self.fib_hi = Some(candle.high);
150 self.fib_lo = self.swing_lo;
151 self.fib_dir = 1;
152 }
153 self.bias_internal = 1;
154 self.last_broken_hi = Some(shi);
155 }
156 if let Some(slo) = self.swing_lo
157 && cl < slo
158 && self.last_broken_lo != Some(slo)
159 {
160 if self.bias_internal >= 0 {
161 self.choch = true;
162 self.choch_dir = -1;
163 self.fib_dir = -1;
164 self.fib_lo = Some(candle.low);
165 self.fib_hi = self.swing_hi;
166 } else {
167 self.bos = true;
168 self.fib_lo = Some(candle.low);
169 self.fib_hi = self.swing_hi;
170 self.fib_dir = -1;
171 }
172 self.bias_internal = -1;
173 self.last_broken_lo = Some(slo);
174 }
175
176 self.bias = self.bias_internal;
177
178 if let (Some(fh), Some(fl)) = (self.fib_hi, self.fib_lo)
179 && self.fib_dir != 0
180 {
181 self.compute_fibs(fh, fl, self.fib_dir);
182 }
183
184 if let (Some(f5), dir) = (self.fib500, self.fib_dir) {
185 if dir != 0 {
186 if dir == 1 {
187 self.in_discount = cl <= f5;
188 self.in_premium = cl > f5;
189 } else {
190 self.in_premium = cl >= f5;
191 self.in_discount = cl < f5;
192 }
193 }
194 } else {
195 self.in_discount = false;
196 self.in_premium = false;
197 }
198
199 let tol = atr * 0.3;
201 let mut score = 0.0_f64;
202 if self.fib382.is_some_and(|f| (cl - f).abs() < tol) {
203 score += 1.5;
204 }
205 if self.fib500.is_some_and(|f| (cl - f).abs() < tol) {
206 score += 2.0;
207 }
208 if self.fib618.is_some_and(|f| (cl - f).abs() < tol) {
209 score += 2.5;
210 }
211 if self.fib786.is_some_and(|f| (cl - f).abs() < tol) {
212 score += 1.5;
213 }
214 self.confluence = (score * 10.0).min(100.0);
215 }
216
217 fn pivot_high(&self) -> Option<f64> {
218 let arr: Vec<f64> = self.highs.iter().copied().collect();
219 let n = self.swing_len;
220 if arr.len() < 2 * n + 1 {
221 return None;
222 }
223 let mid = arr[arr.len() - n - 1];
224 let left_ok = (1..=n).all(|i| mid >= arr[arr.len() - n - 1 - i]);
225 let right_ok = (1..=n).all(|i| mid >= arr[arr.len() - n - 1 + i]);
226 if left_ok && right_ok { Some(mid) } else { None }
227 }
228
229 fn pivot_low(&self) -> Option<f64> {
230 let arr: Vec<f64> = self.lows.iter().copied().collect();
231 let n = self.swing_len;
232 if arr.len() < 2 * n + 1 {
233 return None;
234 }
235 let mid = arr[arr.len() - n - 1];
236 let left_ok = (1..=n).all(|i| mid <= arr[arr.len() - n - 1 - i]);
237 let right_ok = (1..=n).all(|i| mid <= arr[arr.len() - n - 1 + i]);
238 if left_ok && right_ok { Some(mid) } else { None }
239 }
240
241 fn compute_fibs(&mut self, hi: f64, lo: f64, direction: i8) {
242 let rng = hi - lo;
243 if rng <= 0.0 {
244 return;
245 }
246 if direction == 1 {
247 self.fib382 = Some(hi - rng * 0.382);
248 self.fib500 = Some(hi - rng * 0.500);
249 self.fib618 = Some(hi - rng * 0.618);
250 self.fib786 = Some(hi - rng * 0.786);
251 } else {
252 self.fib382 = Some(lo + rng * 0.382);
253 self.fib500 = Some(lo + rng * 0.500);
254 self.fib618 = Some(lo + rng * 0.618);
255 self.fib786 = Some(lo + rng * 0.786);
256 }
257 }
258}