1use crate::confluence::ConfluenceEngine;
6use crate::cvd::CVDTracker;
7use crate::engine::Indicators;
8use crate::liquidity::LiquidityProfile;
9use crate::settings::BotSettings;
10use crate::structure::MarketStructure;
11use crate::vol_regime::VolatilityPercentile;
12
13#[derive(Debug, Clone)]
17pub struct SignalComponents {
18 pub v_vwap: i8,
20 pub v_ema: i8,
21 pub v_st: i8,
22 pub v_ts: i8,
23 pub v_liq: i8,
24 pub v_conf_bull: i8,
25 pub v_conf_bear: i8,
26 pub v_struct: i8,
27 pub v_cvd: i8,
28 pub v_ao: i8,
29 pub v_hurst: i8,
30 pub v_accel_bull: i8,
31 pub v_accel_bear: i8,
32
33 pub hurst: f64,
35 pub price_accel: f64,
36 pub bull_score: f64,
37 pub bear_score: f64,
38 pub conf_min_adj: f64,
39 pub liq_imbalance: f64,
40 pub liq_buy_pct: f64,
41 pub poc: Option<f64>,
42 pub struct_bias: i8,
43 pub fib618: Option<f64>,
44 pub fib_zone: &'static str,
45 pub fib_ok: bool,
46 pub bos: bool,
47 pub choch: bool,
48 pub ts_norm: f64,
49 pub dominance: f64,
50 pub cvd_slope: Option<f64>,
51 pub cvd_div: i8,
52 pub ao: f64,
53 pub ao_rising: bool,
54 pub wr_pct: f64,
55 pub mom_pct: f64,
56 pub wave_ok_long: bool,
57 pub wave_ok_short: bool,
58 pub mom_ok_long: bool,
59 pub mom_ok_short: bool,
60 pub vol_pct: Option<f64>,
61 pub vol_regime: Option<&'static str>,
62}
63
64pub fn compute_signal(
73 close: f64,
74 ind: &Indicators,
75 liq: &LiquidityProfile,
76 conf: &ConfluenceEngine,
77 ms: &MarketStructure,
78 s: &BotSettings,
79 cvd: Option<&CVDTracker>,
80 vol: Option<&VolatilityPercentile>,
81) -> (i32, SignalComponents) {
82 if ind.vwap.is_none() || ind.ema.is_none() || ind.st.is_none() {
84 let comps = empty_components(ind, liq, conf, ms, cvd, vol);
85 return (0, comps);
86 }
87
88 let vwap = ind.vwap.unwrap();
89 let ema = ind.ema.unwrap();
90
91 let v1 = if close > vwap { 1_i8 } else { -1 }; let v2 = if close > ema { 1 } else { -1 }; let v3 = if ind.st_dir_pub == -1 { -1 } else { 1 }; let v4 = if ind.ts_bullish { 1 } else { -1 }; let v5 = if liq.bullish() { 1 } else { -1 }; let conf_adj = vol.map_or(1.0, |v| v.conf_adj);
99 let adj_min = s.conf_min_score * conf_adj;
100 let v6_bull = if conf.bull_score >= adj_min { 1_i8 } else { -1 }; let v6_bear = if conf.bear_score >= adj_min { 1_i8 } else { -1 }; let v7 = ms.bias; let v8: i8 = cvd.map_or(0, |c| {
106 if c.divergence != 0 {
107 c.divergence
108 } else if c.bullish {
109 1
110 } else {
111 -1
112 }
113 }); let v9: i8 = if ind.highs.len() >= 34 {
116 if ind.ao_rising { 1 } else { -1 }
117 } else {
118 0
119 }; let v10: i8 = if (ind.hurst - 0.5).abs() < 0.005 {
122 0
123 } else if ind.hurst >= s.hurst_threshold {
124 1
125 } else {
126 -1
127 }; let (v11_bull, v11_bear): (i8, i8) = if ind.price_accel.abs() < 0.005 {
130 (0, 0)
131 } else {
132 (
133 if ind.price_accel > 0.0 { 1 } else { -1 },
134 if ind.price_accel < 0.0 { 1 } else { -1 },
135 )
136 }; let fib_ok_long = !s.fib_zone_enabled || ms.in_discount || ms.fib500.is_none();
140 let fib_ok_short = !s.fib_zone_enabled || ms.in_premium || ms.fib500.is_none();
141
142 let (bull, bear) = match s.signal_mode.as_str() {
144 "strict" => {
145 let bull = v1 == 1
146 && v2 == 1
147 && v3 == -1
148 && v4 == 1
149 && v5 == 1
150 && v6_bull == 1
151 && v7 == 1
152 && fib_ok_long
153 && (v8 == 1 || v8 == 0);
154 let bear = v1 == -1
155 && v2 == -1
156 && v3 == 1
157 && v4 == -1
158 && v5 == -1
159 && v6_bear == 1
160 && v7 == -1
161 && fib_ok_short
162 && (v8 == -1 || v8 == 0);
163 (bull, bear)
164 }
165 "majority" => {
166 let core_bull = v1 == 1 && v2 == 1 && v3 == -1 && v4 == 1;
167 let core_bear = v1 == -1 && v2 == -1 && v3 == 1 && v4 == -1;
168
169 let ext_bull_count = [
170 v5 == 1,
171 v6_bull == 1,
172 v7 == 1,
173 fib_ok_long,
174 v8 == 1,
175 v9 == 1,
176 ind.wave_ok_long,
177 ind.mom_ok_long,
178 v10 == 1,
179 v11_bull == 1,
180 ]
181 .iter()
182 .filter(|&&b| b)
183 .count();
184
185 let ext_bear_count = [
186 v5 == -1,
187 v6_bear == 1,
188 v7 == -1,
189 fib_ok_short,
190 v8 == -1,
191 v9 == -1,
192 ind.wave_ok_short,
193 ind.mom_ok_short,
194 v10 == 1,
195 v11_bear == 1,
196 ]
197 .iter()
198 .filter(|&&b| b)
199 .count();
200
201 (
202 core_bull && ext_bull_count >= 2,
203 core_bear && ext_bear_count >= 2,
204 )
205 }
206 _ => {
207 let bull = v1 == 1 && v2 == 1 && v3 == -1 && v4 == 1;
209 let bear = v1 == -1 && v2 == -1 && v3 == 1 && v4 == -1;
210 (bull, bear)
211 }
212 };
213
214 let fib_zone = if ms.in_discount {
215 "discount"
216 } else if ms.in_premium {
217 "premium"
218 } else {
219 "mid"
220 };
221
222 let comps = SignalComponents {
223 v_vwap: v1,
224 v_ema: v2,
225 v_st: v3,
226 v_ts: v4,
227 v_liq: v5,
228 v_conf_bull: v6_bull,
229 v_conf_bear: v6_bear,
230 v_struct: v7,
231 v_cvd: v8,
232 v_ao: v9,
233 v_hurst: v10,
234 v_accel_bull: v11_bull,
235 v_accel_bear: v11_bear,
236 hurst: ind.hurst,
237 price_accel: ind.price_accel,
238 bull_score: conf.bull_score,
239 bear_score: conf.bear_score,
240 conf_min_adj: adj_min,
241 liq_imbalance: liq.imbalance,
242 liq_buy_pct: liq.buy_pct * 100.0,
243 poc: liq.poc_price,
244 struct_bias: ms.bias,
245 fib618: ms.fib618,
246 fib_zone,
247 fib_ok: if bull { fib_ok_long } else { fib_ok_short },
248 bos: ms.bos,
249 choch: ms.choch,
250 ts_norm: ind.ts_norm,
251 dominance: ind.dominance,
252 cvd_slope: cvd.map(|c| c.cvd_slope),
253 cvd_div: cvd.map_or(0, |c| c.divergence),
254 ao: ind.ao,
255 ao_rising: ind.ao_rising,
256 wr_pct: ind.wr_pct,
257 mom_pct: ind.mom_pct,
258 wave_ok_long: ind.wave_ok_long,
259 wave_ok_short: ind.wave_ok_short,
260 mom_ok_long: ind.mom_ok_long,
261 mom_ok_short: ind.mom_ok_short,
262 vol_pct: vol.map(|v| v.vol_pct),
263 vol_regime: vol.map(|v| v.vol_regime),
264 };
265
266 if bull {
267 return (1, comps);
268 }
269 if bear {
270 return (-1, comps);
271 }
272 (0, comps)
273}
274
275fn empty_components(
276 ind: &Indicators,
277 liq: &LiquidityProfile,
278 conf: &ConfluenceEngine,
279 ms: &MarketStructure,
280 cvd: Option<&CVDTracker>,
281 vol: Option<&VolatilityPercentile>,
282) -> SignalComponents {
283 SignalComponents {
284 v_vwap: 0,
285 v_ema: 0,
286 v_st: 0,
287 v_ts: 0,
288 v_liq: 0,
289 v_conf_bull: 0,
290 v_conf_bear: 0,
291 v_struct: 0,
292 v_cvd: 0,
293 v_ao: 0,
294 v_hurst: 0,
295 v_accel_bull: 0,
296 v_accel_bear: 0,
297 hurst: ind.hurst,
298 price_accel: ind.price_accel,
299 bull_score: conf.bull_score,
300 bear_score: conf.bear_score,
301 conf_min_adj: 0.0,
302 liq_imbalance: liq.imbalance,
303 liq_buy_pct: liq.buy_pct * 100.0,
304 poc: liq.poc_price,
305 struct_bias: ms.bias,
306 fib618: ms.fib618,
307 fib_zone: "mid",
308 fib_ok: false,
309 bos: false,
310 choch: false,
311 ts_norm: 0.5,
312 dominance: 0.0,
313 cvd_slope: cvd.map(|c| c.cvd_slope),
314 cvd_div: 0,
315 ao: ind.ao,
316 ao_rising: false,
317 wr_pct: 0.5,
318 mom_pct: 0.5,
319 wave_ok_long: false,
320 wave_ok_short: false,
321 mom_ok_long: false,
322 mom_ok_short: false,
323 vol_pct: vol.map(|v| v.vol_pct),
324 vol_regime: vol.map(|v| v.vol_regime),
325 }
326}
327
328pub struct SignalStreak {
332 required: usize,
333 direction: i32,
334 count: usize,
335}
336
337impl SignalStreak {
338 pub fn new(required: usize) -> Self {
339 Self {
340 required,
341 direction: 0,
342 count: 0,
343 }
344 }
345
346 pub fn update(&mut self, signal: i32) -> bool {
349 if signal != 0 && signal == self.direction {
350 self.count += 1;
351 } else {
352 self.direction = signal;
353 self.count = usize::from(signal != 0);
354 }
355 self.count >= self.required && signal != 0
356 }
357
358 pub fn reset(&mut self) {
359 self.direction = 0;
360 self.count = 0;
361 }
362
363 pub fn current_direction(&self) -> i32 {
364 self.direction
365 }
366 pub fn current_count(&self) -> usize {
367 self.count
368 }
369}