1use std::collections::{HashMap, VecDeque};
15
16use chrono::{NaiveDate, TimeZone, Utc};
17
18use crate::error::IndicatorError;
20use crate::indicator::{Indicator, IndicatorOutput};
21use crate::indicator_config::IndicatorConfig;
22use crate::registry::param_usize;
23use crate::signal::vol_regime::PercentileTracker;
24use crate::types::Candle;
25
26#[derive(Debug, Clone)]
33pub struct EngineIndicator {
34 pub config: IndicatorConfig,
35}
36
37impl EngineIndicator {
38 pub fn new(config: IndicatorConfig) -> Self {
39 Self { config }
40 }
41 pub fn with_defaults() -> Self {
42 Self::new(IndicatorConfig::default())
43 }
44}
45
46impl Indicator for EngineIndicator {
47 fn name(&self) -> &'static str {
48 "Engine"
49 }
50 fn required_len(&self) -> usize {
51 self.config.training_period
52 }
53 fn required_columns(&self) -> &[&'static str] {
54 &["open", "high", "low", "close", "volume"]
55 }
56
57 fn calculate(&self, candles: &[Candle]) -> Result<IndicatorOutput, IndicatorError> {
58 self.check_len(candles)?;
59 let mut ind = Indicators::new(self.config.clone());
60 let n = candles.len();
61 let mut vwap_out = vec![f64::NAN; n];
62 let mut ema_out = vec![f64::NAN; n];
63 let mut st_out = vec![f64::NAN; n];
64 let mut st_dir_out = vec![f64::NAN; n];
65 let mut ts_norm_out = vec![f64::NAN; n];
66 let mut ts_bullish_out = vec![f64::NAN; n];
67 let mut hurst_out = vec![f64::NAN; n];
68 let mut accel_out = vec![f64::NAN; n];
69 let mut ao_out = vec![f64::NAN; n];
70 let mut dominance_out = vec![f64::NAN; n];
71 for (i, c) in candles.iter().enumerate() {
72 ind.update(c);
73 vwap_out[i] = ind.vwap.unwrap_or(f64::NAN);
74 ema_out[i] = ind.ema.unwrap_or(f64::NAN);
75 st_out[i] = ind.st.unwrap_or(f64::NAN);
76 st_dir_out[i] = ind.st_dir_pub as f64;
77 ts_norm_out[i] = ind.ts_norm;
78 ts_bullish_out[i] = if ind.ts_bullish { 1.0 } else { 0.0 };
79 hurst_out[i] = ind.hurst;
80 accel_out[i] = ind.price_accel;
81 ao_out[i] = ind.ao;
82 dominance_out[i] = ind.dominance;
83 }
84 Ok(IndicatorOutput::from_pairs([
85 ("engine_vwap", vwap_out),
86 ("engine_ema", ema_out),
87 ("engine_st", st_out),
88 ("engine_st_dir", st_dir_out),
89 ("engine_ts_norm", ts_norm_out),
90 ("engine_ts_bullish", ts_bullish_out),
91 ("engine_hurst", hurst_out),
92 ("engine_accel", accel_out),
93 ("engine_ao", ao_out),
94 ("engine_dominance", dominance_out),
95 ]))
96 }
97}
98
99pub fn factory<S: ::std::hash::BuildHasher>(
102 params: &HashMap<String, String, S>,
103) -> Result<Box<dyn Indicator>, IndicatorError> {
104 let training_period = param_usize(params, "training_period", 100)?;
105 let ema_len = param_usize(params, "ema_len", 9)?;
106 let atr_len = param_usize(params, "atr_len", 10)?;
107 let config = IndicatorConfig {
108 ema_len,
109 atr_len,
110 training_period,
111 ..IndicatorConfig::default()
112 };
113 Ok(Box::new(EngineIndicator::new(config)))
114}
115
116#[inline]
119fn rma_step(prev: Option<f64>, val: f64, len: usize) -> f64 {
120 let k = 1.0 / len as f64;
121 prev.map_or(val, |p| val * k + p * (1.0 - k))
122}
123
124fn wma(arr: &[f64]) -> f64 {
125 if arr.is_empty() {
126 return 0.0;
127 }
128 let n = arr.len() as f64;
129 let weights_sum = n * (n + 1.0) / 2.0;
130 arr.iter()
131 .enumerate()
132 .map(|(i, &v)| v * (i as f64 + 1.0))
133 .sum::<f64>()
134 / weights_sum
135}
136
137fn hurst_scalar(closes: &[f64], max_lag: usize) -> f64 {
139 let n = closes.len();
140 if n < max_lag * 2 + 1 {
141 return 0.5;
142 }
143 let mut log_lags: Vec<f64> = Vec::new();
144 let mut log_rs: Vec<f64> = Vec::new();
145
146 for lag in 2..=max_lag {
147 let chunks = n / lag;
148 if chunks < 1 {
149 continue;
150 }
151 let mut rs_vals: Vec<f64> = Vec::new();
152 for ci in 0..chunks {
153 let chunk = &closes[ci * lag..(ci + 1) * lag];
154 if chunk.len() < 2 {
155 continue;
156 }
157 let _mean = chunk.iter().sum::<f64>() / chunk.len() as f64;
158 let rets: Vec<f64> = chunk.windows(2).map(|w| w[1] - w[0]).collect();
159 let ret_mean = rets.iter().sum::<f64>() / rets.len() as f64;
160 let devs: Vec<f64> = {
161 let mut cum = 0.0;
162 rets.iter()
163 .map(|&r| {
164 cum += r - ret_mean;
165 cum
166 })
167 .collect()
168 };
169 let r = devs.iter().copied().fold(f64::NEG_INFINITY, f64::max)
170 - devs.iter().copied().fold(f64::INFINITY, f64::min);
171 let ddof = rets.len() as f64 - 1.0;
172 let s = if ddof > 0.0 {
173 let var = rets.iter().map(|&x| (x - ret_mean).powi(2)).sum::<f64>() / ddof;
174 var.sqrt()
175 } else {
176 0.0
177 };
178 if s > 1e-12 {
179 rs_vals.push(r / s);
180 }
181 }
182 if !rs_vals.is_empty() {
183 log_lags.push((lag as f64).ln());
184 log_rs.push(rs_vals.iter().sum::<f64>().ln() - (rs_vals.len() as f64).ln());
185 }
186 }
187
188 if log_lags.len() < 3 {
189 return 0.5;
190 }
191 let n = log_lags.len() as f64;
192 let mx = log_lags.iter().sum::<f64>() / n;
193 let my = log_rs.iter().sum::<f64>() / n;
194 let num: f64 = log_lags
195 .iter()
196 .zip(log_rs.iter())
197 .map(|(&x, &y)| (x - mx) * (y - my))
198 .sum();
199 let den: f64 = log_lags.iter().map(|&x| (x - mx).powi(2)).sum();
200 if den < 1e-12 {
201 return 0.5;
202 }
203 (num / den).clamp(0.0, 1.0)
204}
205
206pub struct Indicators {
213 cfg: IndicatorConfig,
214 maxlen: usize,
215
216 pub opens: VecDeque<f64>,
217 pub highs: VecDeque<f64>,
218 pub lows: VecDeque<f64>,
219 pub closes: VecDeque<f64>,
220 pub volumes: VecDeque<f64>,
221 pub times: VecDeque<i64>,
222 bar: usize,
223
224 vwap_vol: f64,
226 vwap_tpv: f64,
227 vwap_date: Option<NaiveDate>,
228
229 ema9: Option<f64>,
231
232 rma_atr: Option<f64>,
234 st_upper: Option<f64>,
235 st_lower: Option<f64>,
236 st_dir: i8,
237 st_value: Option<f64>,
238 kmeans_centroids: Option<[f64; 3]>,
239 kmeans_last_bar: usize,
240
241 dyn_ema: Option<f64>,
243 prev_close: Option<f64>,
244 max_abs_buf: VecDeque<f64>,
245 delta_buf: VecDeque<f64>,
246 rma_c: Option<f64>,
247 rma_o: Option<f64>,
248 wave_speed: f64,
249 wave_pos: i8,
250 speed_norm: VecDeque<f64>,
251 hma_buf: VecDeque<f64>,
252 bull_waves: VecDeque<f64>,
253 bear_waves: VecDeque<f64>,
254 wr_tracker: PercentileTracker,
255 mom_tracker: PercentileTracker,
256 cur_ratio: f64,
257
258 hurst_last_bar: usize,
260 hurst_last_close: f64,
262
263 vel_buf: VecDeque<f64>,
265
266 pub vwap: Option<f64>,
269 pub ema: Option<f64>,
271 pub st: Option<f64>,
273 pub st_dir_pub: i8,
275 pub atr: Option<f64>,
277 pub cluster: usize,
279 pub dyn_ema_pub: Option<f64>,
281 pub ts_speed: f64,
283 pub ts_norm: f64,
285 pub ts_bullish: bool,
287 pub bull_avg: f64,
289 pub bear_avg: f64,
291 pub dominance: f64,
293 pub ao: f64,
295 pub ao_rising: bool,
297 pub wr_pct: f64,
299 pub mom_pct: f64,
301 pub wave_ok_long: bool,
302 pub wave_ok_short: bool,
303 pub mom_ok_long: bool,
304 pub mom_ok_short: bool,
305 pub hurst: f64,
307 pub price_accel: f64,
309}
310
311impl Indicators {
312 pub fn new(cfg: IndicatorConfig) -> Self {
313 let maxlen = cfg.history_candles.max(cfg.training_period + 50).max(300);
314 let ts_collen = cfg.ts_collen;
315 let ts_lookback = cfg.ts_lookback;
316
317 let mut wr_tracker = PercentileTracker::new(200);
318 for i in 0..100 {
319 wr_tracker.push(if i % 2 == 0 { 0.5 } else { 2.0 });
320 }
321
322 Self {
323 cfg,
324 maxlen,
325 opens: VecDeque::with_capacity(maxlen),
326 highs: VecDeque::with_capacity(maxlen),
327 lows: VecDeque::with_capacity(maxlen),
328 closes: VecDeque::with_capacity(maxlen),
329 volumes: VecDeque::with_capacity(maxlen),
330 times: VecDeque::with_capacity(maxlen),
331 bar: 0,
332 vwap_vol: 0.0,
333 vwap_tpv: 0.0,
334 vwap_date: None,
335 ema9: None,
336 rma_atr: None,
337 st_upper: None,
338 st_lower: None,
339 st_dir: 1,
340 st_value: None,
341 kmeans_centroids: None,
342 kmeans_last_bar: 0,
343 dyn_ema: None,
344 prev_close: None,
345 max_abs_buf: VecDeque::with_capacity(200),
346 delta_buf: VecDeque::with_capacity(200),
347 rma_c: None,
348 rma_o: None,
349 wave_speed: 0.0,
350 wave_pos: 0,
351 speed_norm: VecDeque::with_capacity(ts_collen),
352 hma_buf: VecDeque::new(),
353 bull_waves: VecDeque::with_capacity(ts_lookback * 4),
354 bear_waves: VecDeque::with_capacity(ts_lookback * 4),
355 wr_tracker,
356 mom_tracker: PercentileTracker::seeded(200, 0.5, 0.5),
357 cur_ratio: 0.0,
358 hurst_last_bar: 0,
359 hurst_last_close: f64::NAN,
360 vel_buf: VecDeque::with_capacity(110),
361 vwap: None,
362 ema: None,
363 st: None,
364 st_dir_pub: 1,
365 atr: None,
366 cluster: 1,
367 dyn_ema_pub: None,
368 ts_speed: 0.0,
369 ts_norm: 0.5,
370 ts_bullish: false,
371 bull_avg: 0.0,
372 bear_avg: 0.0,
373 dominance: 0.0,
374 ao: 0.0,
375 ao_rising: false,
376 wr_pct: 0.5,
377 mom_pct: 0.5,
378 wave_ok_long: true,
379 wave_ok_short: true,
380 mom_ok_long: true,
381 mom_ok_short: true,
382 hurst: 0.5,
383 price_accel: 0.0,
384 }
385 }
386
387 fn upd_vwap(&mut self, candle: &Candle) -> f64 {
390 let dt = Utc
391 .timestamp_millis_opt(candle.time)
392 .single()
393 .unwrap_or_else(Utc::now)
394 .date_naive();
395 if Some(dt) != self.vwap_date {
396 self.vwap_vol = 0.0;
397 self.vwap_tpv = 0.0;
398 self.vwap_date = Some(dt);
399 }
400 let tp = candle.typical_price();
401 self.vwap_vol += candle.volume;
402 self.vwap_tpv += tp * candle.volume;
403 if self.vwap_vol > 0.0 {
404 self.vwap_tpv / self.vwap_vol
405 } else {
406 candle.close
407 }
408 }
409
410 fn upd_atr(&mut self, candle: &Candle) -> f64 {
413 let prev_c = self
414 .closes
415 .iter()
416 .rev()
417 .nth(1)
418 .copied()
419 .unwrap_or(candle.close);
420 let tr = (candle.high - candle.low)
421 .max((candle.high - prev_c).abs())
422 .max((candle.low - prev_c).abs());
423 let atr = rma_step(self.rma_atr, tr, self.cfg.atr_len);
424 self.rma_atr = Some(atr);
425 atr
426 }
427
428 fn kmeans_atr(&mut self, atr_val: f64) -> f64 {
431 let [c_h, c_m, c_l] = match self.kmeans_centroids {
432 Some(c)
433 if (self.bar - self.kmeans_last_bar) < self.cfg.engine.kmeans_recompute_bars =>
434 {
435 c
436 }
437 _ => {
438 let c = self.compute_kmeans_centroids();
439 self.kmeans_centroids = Some(c);
440 self.kmeans_last_bar = self.bar;
441 c
442 }
443 };
444 let dists = [
445 (c_h - atr_val).abs(),
446 (c_m - atr_val).abs(),
447 (c_l - atr_val).abs(),
448 ];
449 self.cluster = dists
450 .iter()
451 .enumerate()
452 .min_by(|a, b| a.1.total_cmp(b.1))
453 .map_or(1, |(i, _)| i);
454 [c_h, c_m, c_l][self.cluster]
455 }
456
457 fn compute_kmeans_centroids(&self) -> [f64; 3] {
458 let n = self.cfg.training_period.min(self.closes.len());
459 if n == 0 {
460 return [0.0; 3];
461 }
462 let ha: Vec<f64> = self.highs.iter().rev().take(n).copied().collect();
463 let la: Vec<f64> = self.lows.iter().rev().take(n).copied().collect();
464 let ca: Vec<f64> = self.closes.iter().rev().take(n).copied().collect();
465
466 let mut trs = vec![ha[0] - la[0]];
467 for i in 1..n {
468 trs.push(
469 (ha[i] - la[i])
470 .max((ha[i] - ca[i - 1]).abs())
471 .max((la[i] - ca[i - 1]).abs()),
472 );
473 }
474 let alpha = 1.0 / self.cfg.atr_len as f64;
475 let mut atr_w = vec![trs[0]];
476 for i in 1..trs.len() {
477 atr_w.push(alpha * trs[i] + (1.0 - alpha) * atr_w[i - 1]);
478 }
479
480 let lo = atr_w.iter().copied().fold(f64::INFINITY, f64::min);
481 let hi = atr_w.iter().copied().fold(f64::NEG_INFINITY, f64::max);
482 let rng = if (hi - lo).abs() > 1e-9 {
483 hi - lo
484 } else {
485 1e-9
486 };
487
488 let mut c_h = lo + rng * self.cfg.highvol_pct;
489 let mut c_m = lo + rng * self.cfg.midvol_pct;
490 let mut c_l = lo + rng * self.cfg.lowvol_pct;
491
492 for _ in 0..self.cfg.engine.kmeans_max_iters {
493 let mut g: [Vec<f64>; 3] = [Vec::new(), Vec::new(), Vec::new()];
494 for &v in &atr_w {
495 let dists = [(v - c_h).abs(), (v - c_m).abs(), (v - c_l).abs()];
496 let idx = dists
497 .iter()
498 .enumerate()
499 .min_by(|a, b| a.1.total_cmp(b.1))
500 .map_or(1, |(i, _)| i);
501 g[idx].push(v);
502 }
503 let nh = if g[0].is_empty() {
504 c_h
505 } else {
506 g[0].iter().sum::<f64>() / g[0].len() as f64
507 };
508 let nm = if g[1].is_empty() {
509 c_m
510 } else {
511 g[1].iter().sum::<f64>() / g[1].len() as f64
512 };
513 let nl = if g[2].is_empty() {
514 c_l
515 } else {
516 g[2].iter().sum::<f64>() / g[2].len() as f64
517 };
518 if (nh - c_h).abs() < 1e-9 && (nm - c_m).abs() < 1e-9 && (nl - c_l).abs() < 1e-9 {
519 break;
520 }
521 c_h = nh;
522 c_m = nm;
523 c_l = nl;
524 }
525 [c_h, c_m, c_l]
526 }
527
528 fn upd_supertrend(&mut self, adaptive_atr: f64, close: f64) -> (f64, i8) {
531 let hl2 = f64::midpoint(
532 self.highs.back().copied().unwrap_or(close),
533 self.lows.back().copied().unwrap_or(close),
534 );
535 let factor = self.cfg.st_factor;
536 let raw_upper = hl2 + factor * adaptive_atr;
537 let raw_lower = hl2 - factor * adaptive_atr;
538
539 let prev_u = self.st_upper.unwrap_or(raw_upper);
540 let prev_l = self.st_lower.unwrap_or(raw_lower);
541 let prev_st = self.st_value.unwrap_or(raw_upper);
542 let prev_c = self.closes.iter().rev().nth(1).copied().unwrap_or(close);
543
544 let lower = if raw_lower > prev_l || prev_c < prev_l {
545 raw_lower
546 } else {
547 prev_l
548 };
549 let upper = if raw_upper < prev_u || prev_c > prev_u {
550 raw_upper
551 } else {
552 prev_u
553 };
554
555 let direction = if prev_st == prev_u {
556 if close > upper { -1 } else { 1 }
557 } else {
558 if close < lower { 1 } else { -1 }
559 };
560
561 let st_val = if direction == -1 { lower } else { upper };
562 self.st_upper = Some(upper);
563 self.st_lower = Some(lower);
564 self.st_dir = direction;
565 self.st_value = Some(st_val);
566 (st_val, direction)
567 }
568
569 fn upd_trend_speed(&mut self, candle: &Candle) {
572 let cl = candle.close;
573 let op = candle.open;
574
575 let abs_cd = (cl - op).abs();
576 if self.max_abs_buf.len() == 200 {
577 self.max_abs_buf.pop_front();
578 }
579 self.max_abs_buf.push_back(abs_cd);
580 let max_abs = self
581 .max_abs_buf
582 .iter()
583 .copied()
584 .fold(f64::NEG_INFINITY, f64::max)
585 .max(1.0);
586 let cd_norm = (abs_cd + max_abs) / (2.0 * max_abs);
587 let dyn_len = 5.0 + cd_norm * (self.cfg.ts_max_length as f64 - 5.0);
588
589 let prev_c = self.prev_close.unwrap_or(cl);
590 let delta = (cl - prev_c).abs();
591 if self.delta_buf.len() == 200 {
592 self.delta_buf.pop_front();
593 }
594 self.delta_buf.push_back(delta);
595 let max_d = self
596 .delta_buf
597 .iter()
598 .copied()
599 .fold(f64::NEG_INFINITY, f64::max)
600 .max(1.0);
601 let accel = delta / max_d;
602
603 let alpha = (2.0 / (dyn_len + 1.0) * (1.0 + accel * self.cfg.ts_accel_mult)).min(1.0);
604 let trend = match self.dyn_ema {
605 None => cl,
606 Some(prev) => alpha * cl + (1.0 - alpha) * prev,
607 };
608 self.dyn_ema = Some(trend);
609 self.dyn_ema_pub = self.dyn_ema;
610
611 self.rma_c = Some(rma_step(self.rma_c, cl, self.cfg.ts_rma_len));
612 self.rma_o = Some(rma_step(self.rma_o, op, self.cfg.ts_rma_len));
613
614 let prev_cl = self.closes.iter().rev().nth(1).copied().unwrap_or(cl);
615 let c_rma = self.rma_c.unwrap_or(0.0);
616 let o_rma = self.rma_o.unwrap_or(0.0);
617 let lookback_cap = self.cfg.ts_lookback * 4;
618
619 if cl > trend && prev_cl <= trend {
620 if self.wave_pos != 0 {
621 if self.bear_waves.len() == lookback_cap {
622 self.bear_waves.pop_front();
623 }
624 self.bear_waves.push_back(self.wave_speed);
625 }
626 self.wave_pos = 1;
627 self.wave_speed = c_rma - o_rma;
628 } else if cl < trend && prev_cl >= trend {
629 if self.wave_pos != 0 {
630 if self.bull_waves.len() == lookback_cap {
631 self.bull_waves.pop_front();
632 }
633 self.bull_waves.push_back(self.wave_speed);
634 }
635 self.wave_pos = -1;
636 self.wave_speed = c_rma - o_rma;
637 } else {
638 self.wave_speed += c_rma - o_rma;
639 }
640
641 if self.speed_norm.len() == self.cfg.ts_collen {
642 self.speed_norm.pop_front();
643 }
644 self.speed_norm.push_back(self.wave_speed);
645
646 self.ts_speed = self.hma_smooth(self.cfg.ts_hma_len);
647 self.ts_bullish = self.ts_speed > 0.0;
648
649 let sp_min = self
650 .speed_norm
651 .iter()
652 .copied()
653 .fold(f64::INFINITY, f64::min);
654 let sp_max = self
655 .speed_norm
656 .iter()
657 .copied()
658 .fold(f64::NEG_INFINITY, f64::max);
659 let sp_rng = if (sp_max - sp_min).abs() > 1e-9 {
660 sp_max - sp_min
661 } else {
662 1.0
663 };
664 self.ts_norm = (self.wave_speed - sp_min) / sp_rng;
665
666 let lb = self.cfg.ts_lookback;
667 let bull_r: Vec<f64> = self.bull_waves.iter().rev().take(lb).copied().collect();
668 let bear_r: Vec<f64> = self.bear_waves.iter().rev().take(lb).copied().collect();
669 self.bull_avg = if bull_r.is_empty() {
670 0.0
671 } else {
672 bull_r.iter().sum::<f64>() / bull_r.len() as f64
673 };
674 self.bear_avg = if bear_r.is_empty() {
675 0.0
676 } else {
677 bear_r.iter().sum::<f64>() / bear_r.len() as f64
678 };
679 self.dominance = self.bull_avg - self.bear_avg.abs();
680 self.prev_close = Some(cl);
681
682 let bear_abs = self.bear_avg.abs().max(1e-9);
683 let wave_ratio = if self.bull_avg > 0.0 {
684 self.bull_avg / bear_abs
685 } else {
686 1.0 / bear_abs
687 };
688 self.wr_tracker.push(wave_ratio);
689 self.wr_pct = self.wr_tracker.pct(wave_ratio);
690
691 self.cur_ratio = if self.wave_speed > 0.0 && self.bull_avg > 0.0 {
692 self.wave_speed / self.bull_avg
693 } else if self.wave_speed < 0.0 && bear_abs > 0.0 {
694 -self.wave_speed.abs() / bear_abs
695 } else {
696 0.0
697 };
698 self.mom_tracker.push(self.cur_ratio.abs());
699 self.mom_pct = self.mom_tracker.pct(self.cur_ratio.abs());
700
701 let wl = self.cfg.wave_pct_l.clamp(0.01, 0.99);
702 let ws = (1.0 - self.cfg.wave_pct_s).clamp(0.01, 0.99);
703 let ml = self.cfg.mom_pct_min.clamp(0.01, 0.99);
704
705 self.wave_ok_long = self.wr_pct >= wl;
706 self.wave_ok_short = self.wr_pct <= ws;
707 self.mom_ok_long = self.mom_pct >= ml && self.cur_ratio > 0.0;
708 self.mom_ok_short = self.mom_pct >= ml && self.cur_ratio < 0.0;
709 }
710
711 fn hma_smooth(&mut self, length: usize) -> f64 {
713 let sn: Vec<f64> = self.speed_norm.iter().copied().collect();
714 if sn.len() < 2 {
715 return *sn.last().unwrap_or(&0.0);
716 }
717 let half = (length / 2).max(1);
718 let sqrt_n = (length as f64).sqrt().round() as usize;
719 let raw = 2.0 * wma(&sn[sn.len().saturating_sub(half)..])
720 - wma(&sn[sn.len().saturating_sub(length)..]);
721 if self.hma_buf.len() == sqrt_n {
722 self.hma_buf.pop_front();
723 }
724 self.hma_buf.push_back(raw);
725 let hma_arr: Vec<f64> = self.hma_buf.iter().copied().collect();
726 wma(&hma_arr)
727 }
728
729 fn upd_ao(&mut self) {
732 if self.highs.len() < 34 {
733 return;
734 }
735 let hs: Vec<f64> = self.highs.iter().copied().collect();
736 let ls: Vec<f64> = self.lows.iter().copied().collect();
737 let hl2: Vec<f64> = hs
738 .iter()
739 .zip(ls.iter())
740 .map(|(h, l)| (h + l) / 2.0)
741 .collect();
742 let n = hl2.len();
743 let ao_new =
744 hl2[n - 5..].iter().sum::<f64>() / 5.0 - hl2[n - 34..].iter().sum::<f64>() / 34.0;
745 self.ao_rising = ao_new > self.ao;
746 self.ao = ao_new;
747 }
748
749 fn upd_hurst(&mut self) {
752 let lb = self.cfg.hurst_lookback;
753 let min_bars = lb * 2 + 1;
754 if self.closes.len() < min_bars
755 || (self.bar - self.hurst_last_bar) < self.cfg.engine.hurst_recompute_bars
756 {
757 return;
758 }
759 let cur_close = *self.closes.back().unwrap_or(&0.0);
763 let pct_move = if self.hurst_last_close.is_nan() || self.hurst_last_close.abs() < 1e-10 {
764 f64::INFINITY } else {
766 (cur_close - self.hurst_last_close).abs() / self.hurst_last_close
767 };
768 if pct_move < 1e-4 {
769 self.hurst_last_bar = self.bar;
771 return;
772 }
773 let cl_arr: Vec<f64> = self.closes.iter().rev().take(min_bars).copied().collect();
774 self.hurst = hurst_scalar(&cl_arr, lb);
775 self.hurst_last_bar = self.bar;
776 self.hurst_last_close = cur_close;
777 }
778
779 fn upd_accel(&mut self) {
782 let k = 3usize;
783 let n = self.closes.len();
784 if n <= k * 2 {
785 return;
786 }
787 let cl: Vec<f64> = self.closes.iter().copied().collect();
788 let vel_now = (cl[n - 1] - cl[n - 1 - k]) / (cl[n - 1 - k] + 1e-10);
789 let vel_prev = (cl[n - 1 - k] - cl[n - 1 - k * 2]) / (cl[n - 1 - k * 2] + 1e-10);
790 if self.vel_buf.len() == 110 {
791 self.vel_buf.pop_front();
792 }
793 self.vel_buf.push_back(vel_now);
794 let accel = vel_now - vel_prev;
795 let vel_std = if self.vel_buf.len() > 1 {
796 let vv: Vec<f64> = self.vel_buf.iter().copied().collect();
797 let mean = vv.iter().sum::<f64>() / vv.len() as f64;
798 let var = vv.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / vv.len() as f64;
799 var.sqrt()
800 } else {
801 1.0
802 };
803 self.price_accel = (accel / (vel_std + 1e-10) / 3.0).clamp(-1.0, 1.0);
804 }
805
806 pub fn update(&mut self, candle: &Candle) -> bool {
810 let cap = self.maxlen;
811 macro_rules! push {
812 ($buf:expr, $val:expr) => {
813 if $buf.len() == cap {
814 $buf.pop_front();
815 }
816 $buf.push_back($val);
817 };
818 }
819 push!(self.opens, candle.open);
820 push!(self.highs, candle.high);
821 push!(self.lows, candle.low);
822 push!(self.closes, candle.close);
823 push!(self.volumes, candle.volume);
824 push!(self.times, candle.time);
825 self.bar += 1;
826
827 self.vwap = Some(self.upd_vwap(candle));
828
829 let k = 2.0 / (self.cfg.ema_len as f64 + 1.0);
830 self.ema9 = Some(match self.ema9 {
831 None => candle.close,
832 Some(e) => candle.close * k + e * (1.0 - k),
833 });
834 self.ema = self.ema9;
835
836 let atr_val = self.upd_atr(candle);
837 self.atr = Some(atr_val);
838
839 self.upd_trend_speed(candle);
840 self.upd_ao();
841 self.upd_hurst();
842 self.upd_accel();
843
844 if self.closes.len() < self.cfg.training_period {
845 return false;
846 }
847
848 let adaptive_atr = self.kmeans_atr(atr_val);
849 let (st, dir) = self.upd_supertrend(adaptive_atr, candle.close);
850 self.st = Some(st);
851 self.st_dir_pub = dir;
852
853 true
854 }
855
856 pub fn check_speed_exit(&self, position: i32) -> bool {
861 let Some(thr) = self.cfg.ts_speed_exit_threshold else {
862 return false;
863 };
864 if position > 0 && self.ts_speed < -thr.abs() {
865 return true;
866 }
867 position < 0 && self.ts_speed > thr.abs()
868 }
869}