1use crate::settings::BotSettings;
15use crate::types::Candle;
16use crate::vol_regime::PercentileTracker;
17use chrono::{NaiveDate, TimeZone, Utc};
18use std::collections::VecDeque;
19
20#[inline]
22#[allow(dead_code)]
23fn ema_step(prev: Option<f64>, val: f64, len: usize) -> f64 {
24 let k = 2.0 / (len as f64 + 1.0);
25 prev.map_or(val, |p| val * k + p * (1.0 - k))
26}
27
28#[inline]
29fn rma_step(prev: Option<f64>, val: f64, len: usize) -> f64 {
30 let k = 1.0 / len as f64;
31 prev.map_or(val, |p| val * k + p * (1.0 - k))
32}
33
34fn wma(arr: &[f64]) -> f64 {
35 if arr.is_empty() {
36 return 0.0;
37 }
38 let n = arr.len() as f64;
39 let weights_sum = n * (n + 1.0) / 2.0;
40 arr.iter()
41 .enumerate()
42 .map(|(i, &v)| v * (i as f64 + 1.0))
43 .sum::<f64>()
44 / weights_sum
45}
46
47fn hurst_scalar(closes: &[f64], max_lag: usize) -> f64 {
49 let n = closes.len();
50 if n < max_lag * 2 + 1 {
51 return 0.5;
52 }
53 let mut log_lags: Vec<f64> = Vec::new();
54 let mut log_rs: Vec<f64> = Vec::new();
55
56 for lag in 2..=max_lag {
57 let chunks = n / lag;
58 if chunks < 1 {
59 continue;
60 }
61 let mut rs_vals: Vec<f64> = Vec::new();
62 for ci in 0..chunks {
63 let chunk = &closes[ci * lag..(ci + 1) * lag];
64 if chunk.len() < 2 {
65 continue;
66 }
67 let _mean = chunk.iter().sum::<f64>() / chunk.len() as f64;
68 let rets: Vec<f64> = chunk.windows(2).map(|w| w[1] - w[0]).collect();
69 let ret_mean = rets.iter().sum::<f64>() / rets.len() as f64;
70 let devs: Vec<f64> = {
71 let mut cum = 0.0;
72 rets.iter()
73 .map(|&r| {
74 cum += r - ret_mean;
75 cum
76 })
77 .collect()
78 };
79 let r = devs.iter().copied().fold(f64::NEG_INFINITY, f64::max)
80 - devs.iter().copied().fold(f64::INFINITY, f64::min);
81 let ddof = rets.len() as f64 - 1.0;
82 let s = if ddof > 0.0 {
83 let var = rets.iter().map(|&x| (x - ret_mean).powi(2)).sum::<f64>() / ddof;
84 var.sqrt()
85 } else {
86 0.0
87 };
88 if s > 1e-12 {
89 rs_vals.push(r / s);
90 }
91 }
92 if !rs_vals.is_empty() {
93 log_lags.push((lag as f64).ln());
94 log_rs.push(rs_vals.iter().sum::<f64>().ln() - (rs_vals.len() as f64).ln());
95 }
96 }
97
98 if log_lags.len() < 3 {
99 return 0.5;
100 }
101 let n = log_lags.len() as f64;
103 let mx = log_lags.iter().sum::<f64>() / n;
104 let my = log_rs.iter().sum::<f64>() / n;
105 let num: f64 = log_lags
106 .iter()
107 .zip(log_rs.iter())
108 .map(|(&x, &y)| (x - mx) * (y - my))
109 .sum();
110 let den: f64 = log_lags.iter().map(|&x| (x - mx).powi(2)).sum();
111 if den < 1e-12 {
112 return 0.5;
113 }
114 (num / den).clamp(0.0, 1.0)
115}
116
117pub struct Indicators {
124 s: BotSettings,
125 maxlen: usize,
126
127 pub opens: VecDeque<f64>,
128 pub highs: VecDeque<f64>,
129 pub lows: VecDeque<f64>,
130 pub closes: VecDeque<f64>,
131 pub volumes: VecDeque<f64>,
132 pub times: VecDeque<i64>,
133 bar: usize,
134
135 vwap_vol: f64,
137 vwap_tpv: f64,
138 vwap_date: Option<NaiveDate>,
139
140 ema9: Option<f64>,
142
143 rma_atr: Option<f64>,
145 st_upper: Option<f64>,
146 st_lower: Option<f64>,
147 st_dir: i8,
148 st_value: Option<f64>,
149 kmeans_centroids: Option<[f64; 3]>,
150 kmeans_last_bar: usize,
151
152 dyn_ema: Option<f64>,
154 prev_close: Option<f64>,
155 max_abs_buf: VecDeque<f64>,
156 delta_buf: VecDeque<f64>,
157 rma_c: Option<f64>,
158 rma_o: Option<f64>,
159 wave_speed: f64,
160 wave_pos: i8,
161 speed_norm: VecDeque<f64>,
162 hma_buf: VecDeque<f64>,
163 bull_waves: VecDeque<f64>,
164 bear_waves: VecDeque<f64>,
165 wr_tracker: PercentileTracker,
166 mom_tracker: PercentileTracker,
167 cur_ratio: f64,
168
169 hurst_last_bar: usize,
171
172 vel_buf: VecDeque<f64>,
174
175 pub vwap: Option<f64>,
178 pub ema: Option<f64>,
180 pub st: Option<f64>,
182 pub st_dir_pub: i8,
184 pub atr: Option<f64>,
186 pub cluster: usize,
188 pub dyn_ema_pub: Option<f64>,
190 pub ts_speed: f64,
192 pub ts_norm: f64,
194 pub ts_bullish: bool,
196 pub bull_avg: f64,
198 pub bear_avg: f64,
200 pub dominance: f64,
202 pub ao: f64,
204 pub ao_rising: bool,
206 pub wr_pct: f64,
208 pub mom_pct: f64,
210 pub wave_ok_long: bool,
211 pub wave_ok_short: bool,
212 pub mom_ok_long: bool,
213 pub mom_ok_short: bool,
214 pub hurst: f64,
216 pub price_accel: f64,
218}
219
220impl Indicators {
221 pub fn new(s: &BotSettings) -> Self {
222 let maxlen = s.history_candles.max(s.training_period + 50).max(300);
223 let mut wr_tracker = PercentileTracker::new(200);
224 for i in 0..100 {
226 wr_tracker.push(if i % 2 == 0 { 0.5 } else { 2.0 });
227 }
228 Self {
229 s: s.clone(),
230 maxlen,
231 opens: VecDeque::with_capacity(maxlen),
232 highs: VecDeque::with_capacity(maxlen),
233 lows: VecDeque::with_capacity(maxlen),
234 closes: VecDeque::with_capacity(maxlen),
235 volumes: VecDeque::with_capacity(maxlen),
236 times: VecDeque::with_capacity(maxlen),
237 bar: 0,
238 vwap_vol: 0.0,
239 vwap_tpv: 0.0,
240 vwap_date: None,
241 ema9: None,
242 rma_atr: None,
243 st_upper: None,
244 st_lower: None,
245 st_dir: 1,
246 st_value: None,
247 kmeans_centroids: None,
248 kmeans_last_bar: 0,
249 dyn_ema: None,
250 prev_close: None,
251 max_abs_buf: VecDeque::with_capacity(200),
252 delta_buf: VecDeque::with_capacity(200),
253 rma_c: None,
254 rma_o: None,
255 wave_speed: 0.0,
256 wave_pos: 0,
257 speed_norm: VecDeque::with_capacity(s.ts_collen),
258 hma_buf: VecDeque::new(),
259 bull_waves: VecDeque::with_capacity(s.ts_lookback * 4),
260 bear_waves: VecDeque::with_capacity(s.ts_lookback * 4),
261 wr_tracker,
262 mom_tracker: PercentileTracker::seeded(200, 0.5, 0.5),
263 cur_ratio: 0.0,
264 hurst_last_bar: 0,
265 vel_buf: VecDeque::with_capacity(110),
266 vwap: None,
267 ema: None,
268 st: None,
269 st_dir_pub: 1,
270 atr: None,
271 cluster: 1,
272 dyn_ema_pub: None,
273 ts_speed: 0.0,
274 ts_norm: 0.5,
275 ts_bullish: false,
276 bull_avg: 0.0,
277 bear_avg: 0.0,
278 dominance: 0.0,
279 ao: 0.0,
280 ao_rising: false,
281 wr_pct: 0.5,
282 mom_pct: 0.5,
283 wave_ok_long: true,
284 wave_ok_short: true,
285 mom_ok_long: true,
286 mom_ok_short: true,
287 hurst: 0.5,
288 price_accel: 0.0,
289 }
290 }
291
292 fn upd_vwap(&mut self, candle: &Candle) -> f64 {
295 let dt = Utc
296 .timestamp_millis_opt(candle.time)
297 .single()
298 .unwrap_or_else(Utc::now)
299 .date_naive();
300 if Some(dt) != self.vwap_date {
301 self.vwap_vol = 0.0;
302 self.vwap_tpv = 0.0;
303 self.vwap_date = Some(dt);
304 }
305 let tp = candle.typical_price();
306 self.vwap_vol += candle.volume;
307 self.vwap_tpv += tp * candle.volume;
308 if self.vwap_vol > 0.0 {
309 self.vwap_tpv / self.vwap_vol
310 } else {
311 candle.close
312 }
313 }
314
315 fn upd_atr(&mut self, candle: &Candle) -> f64 {
318 let prev_c = self
319 .closes
320 .iter()
321 .rev()
322 .nth(1)
323 .copied()
324 .unwrap_or(candle.close);
325 let tr = (candle.high - candle.low)
326 .max((candle.high - prev_c).abs())
327 .max((candle.low - prev_c).abs());
328 self.rma_atr = Some(rma_step(self.rma_atr, tr, self.s.atr_len));
329 self.rma_atr.unwrap()
330 }
331
332 fn kmeans_atr(&mut self, atr_val: f64) -> f64 {
335 if self.kmeans_centroids.is_none() || (self.bar - self.kmeans_last_bar) >= 10 {
336 self.kmeans_centroids = Some(self.compute_kmeans_centroids());
337 self.kmeans_last_bar = self.bar;
338 }
339 let [c_h, c_m, c_l] = self.kmeans_centroids.unwrap();
340 let dists = [
341 (c_h - atr_val).abs(),
342 (c_m - atr_val).abs(),
343 (c_l - atr_val).abs(),
344 ];
345 self.cluster = dists
346 .iter()
347 .enumerate()
348 .min_by(|a, b| a.1.partial_cmp(b.1).unwrap())
349 .map_or(1, |(i, _)| i);
350 [c_h, c_m, c_l][self.cluster]
351 }
352
353 fn compute_kmeans_centroids(&self) -> [f64; 3] {
354 let n = self.s.training_period.min(self.closes.len());
355 let ha: Vec<f64> = self.highs.iter().rev().take(n).copied().collect();
356 let la: Vec<f64> = self.lows.iter().rev().take(n).copied().collect();
357 let ca: Vec<f64> = self.closes.iter().rev().take(n).copied().collect();
358
359 let mut trs = vec![ha[0] - la[0]];
361 for i in 1..n {
362 trs.push(
363 (ha[i] - la[i])
364 .max((ha[i] - ca[i - 1]).abs())
365 .max((la[i] - ca[i - 1]).abs()),
366 );
367 }
368 let alpha = 1.0 / self.s.atr_len as f64;
369 let mut atr_w = vec![trs[0]];
370 for i in 1..trs.len() {
371 atr_w.push(alpha * trs[i] + (1.0 - alpha) * atr_w[i - 1]);
372 }
373
374 let lo = atr_w.iter().copied().fold(f64::INFINITY, f64::min);
375 let hi = atr_w.iter().copied().fold(f64::NEG_INFINITY, f64::max);
376 let rng = if (hi - lo).abs() > 1e-9 {
377 hi - lo
378 } else {
379 1e-9
380 };
381
382 let mut c_h = lo + rng * self.s.highvol_pct;
383 let mut c_m = lo + rng * self.s.midvol_pct;
384 let mut c_l = lo + rng * self.s.lowvol_pct;
385
386 for _ in 0..100 {
387 let mut g: [Vec<f64>; 3] = [Vec::new(), Vec::new(), Vec::new()];
388 for &v in &atr_w {
389 let dists = [(v - c_h).abs(), (v - c_m).abs(), (v - c_l).abs()];
390 let idx = dists
391 .iter()
392 .enumerate()
393 .min_by(|a, b| a.1.partial_cmp(b.1).unwrap())
394 .map_or(1, |(i, _)| i);
395 g[idx].push(v);
396 }
397 let nh = if g[0].is_empty() {
398 c_h
399 } else {
400 g[0].iter().sum::<f64>() / g[0].len() as f64
401 };
402 let nm = if g[1].is_empty() {
403 c_m
404 } else {
405 g[1].iter().sum::<f64>() / g[1].len() as f64
406 };
407 let nl = if g[2].is_empty() {
408 c_l
409 } else {
410 g[2].iter().sum::<f64>() / g[2].len() as f64
411 };
412 if (nh - c_h).abs() < 1e-9 && (nm - c_m).abs() < 1e-9 && (nl - c_l).abs() < 1e-9 {
413 break;
414 }
415 c_h = nh;
416 c_m = nm;
417 c_l = nl;
418 }
419 [c_h, c_m, c_l]
420 }
421
422 fn upd_supertrend(&mut self, adaptive_atr: f64, close: f64) -> (f64, i8) {
425 let hl2 = (self.highs.back().copied().unwrap_or(close)
426 + self.lows.back().copied().unwrap_or(close))
427 / 2.0;
428 let factor = self.s.st_factor;
429 let raw_upper = hl2 + factor * adaptive_atr;
430 let raw_lower = hl2 - factor * adaptive_atr;
431
432 let prev_u = self.st_upper.unwrap_or(raw_upper);
433 let prev_l = self.st_lower.unwrap_or(raw_lower);
434 let prev_st = self.st_value.unwrap_or(raw_upper);
435
436 let prev_c = self.closes.iter().rev().nth(1).copied().unwrap_or(close);
437
438 let lower = if raw_lower > prev_l || prev_c < prev_l {
439 raw_lower
440 } else {
441 prev_l
442 };
443 let upper = if raw_upper < prev_u || prev_c > prev_u {
444 raw_upper
445 } else {
446 prev_u
447 };
448
449 let direction = if prev_st == prev_u {
450 if close > upper { -1 } else { 1 }
451 } else {
452 if close < lower { 1 } else { -1 }
453 };
454
455 let st_val = if direction == -1 { lower } else { upper };
456 self.st_upper = Some(upper);
457 self.st_lower = Some(lower);
458 self.st_dir = direction;
459 self.st_value = Some(st_val);
460 (st_val, direction)
461 }
462
463 fn upd_trend_speed(&mut self, candle: &Candle) {
466 let cl = candle.close;
467 let op = candle.open;
468 let prev_c = self.prev_close.unwrap_or(cl);
469
470 let abs_cd = (cl - op).abs();
472 if self.max_abs_buf.len() == 200 {
473 self.max_abs_buf.pop_front();
474 }
475 self.max_abs_buf.push_back(abs_cd);
476 let max_abs = self
477 .max_abs_buf
478 .iter()
479 .copied()
480 .fold(f64::NEG_INFINITY, f64::max)
481 .max(1.0);
482 let cd_norm = (abs_cd + max_abs) / (2.0 * max_abs);
483 let dyn_len = 5.0 + cd_norm * (self.s.ts_max_length as f64 - 5.0);
484
485 let delta = (cl - prev_c).abs();
486 if self.delta_buf.len() == 200 {
487 self.delta_buf.pop_front();
488 }
489 self.delta_buf.push_back(delta);
490 let max_d = self
491 .delta_buf
492 .iter()
493 .copied()
494 .fold(f64::NEG_INFINITY, f64::max)
495 .max(1.0);
496 let accel = delta / max_d;
497
498 let alpha = (2.0 / (dyn_len + 1.0) * (1.0 + accel * self.s.ts_accel_mult)).min(1.0);
499 self.dyn_ema = Some(match self.dyn_ema {
500 None => cl,
501 Some(prev) => alpha * cl + (1.0 - alpha) * prev,
502 });
503 self.dyn_ema_pub = self.dyn_ema;
504
505 self.rma_c = Some(rma_step(self.rma_c, cl, self.s.ts_rma_len));
506 self.rma_o = Some(rma_step(self.rma_o, op, self.s.ts_rma_len));
507
508 let trend = self.dyn_ema.unwrap();
509 let prev_cl = self.closes.iter().rev().nth(1).copied().unwrap_or(cl);
510 let c_rma = self.rma_c.unwrap_or(0.0);
511 let o_rma = self.rma_o.unwrap_or(0.0);
512
513 if cl > trend && prev_cl <= trend {
514 if self.wave_pos != 0 {
515 if self.bear_waves.len() == self.s.ts_lookback * 4 {
516 self.bear_waves.pop_front();
517 }
518 self.bear_waves.push_back(self.wave_speed);
519 }
520 self.wave_pos = 1;
521 self.wave_speed = c_rma - o_rma;
522 } else if cl < trend && prev_cl >= trend {
523 if self.wave_pos != 0 {
524 if self.bull_waves.len() == self.s.ts_lookback * 4 {
525 self.bull_waves.pop_front();
526 }
527 self.bull_waves.push_back(self.wave_speed);
528 }
529 self.wave_pos = -1;
530 self.wave_speed = c_rma - o_rma;
531 } else {
532 self.wave_speed += c_rma - o_rma;
533 }
534
535 if self.speed_norm.len() == self.s.ts_collen {
536 self.speed_norm.pop_front();
537 }
538 self.speed_norm.push_back(self.wave_speed);
539
540 self.ts_speed = self.hma_smooth(self.s.ts_hma_len);
542 self.ts_bullish = self.ts_speed > 0.0;
543
544 let sp_min = self
545 .speed_norm
546 .iter()
547 .copied()
548 .fold(f64::INFINITY, f64::min);
549 let sp_max = self
550 .speed_norm
551 .iter()
552 .copied()
553 .fold(f64::NEG_INFINITY, f64::max);
554 let sp_rng = if (sp_max - sp_min).abs() > 1e-9 {
555 sp_max - sp_min
556 } else {
557 1.0
558 };
559 self.ts_norm = (self.wave_speed - sp_min) / sp_rng;
560
561 let lb = self.s.ts_lookback;
562 let bull_r: Vec<f64> = self.bull_waves.iter().rev().take(lb).copied().collect();
563 let bear_r: Vec<f64> = self.bear_waves.iter().rev().take(lb).copied().collect();
564 self.bull_avg = if bull_r.is_empty() {
565 0.0
566 } else {
567 bull_r.iter().sum::<f64>() / bull_r.len() as f64
568 };
569 self.bear_avg = if bear_r.is_empty() {
570 0.0
571 } else {
572 bear_r.iter().sum::<f64>() / bear_r.len() as f64
573 };
574 self.dominance = self.bull_avg - self.bear_avg.abs();
575 self.prev_close = Some(cl);
576
577 let bear_abs = self.bear_avg.abs().max(1e-9);
579 let wave_ratio = if self.bull_avg > 0.0 {
580 self.bull_avg / bear_abs
581 } else {
582 1.0 / bear_abs
583 };
584 self.wr_tracker.push(wave_ratio);
585 self.wr_pct = self.wr_tracker.pct(wave_ratio);
586
587 self.cur_ratio = if self.wave_speed > 0.0 && self.bull_avg > 0.0 {
589 self.wave_speed / self.bull_avg
590 } else if self.wave_speed < 0.0 && bear_abs > 0.0 {
591 -self.wave_speed.abs() / bear_abs
592 } else {
593 0.0
594 };
595 self.mom_tracker.push(self.cur_ratio.abs());
596 self.mom_pct = self.mom_tracker.pct(self.cur_ratio.abs());
597
598 let wl = self.s.wave_pct_l.clamp(0.01, 0.99);
599 let ws = (1.0 - self.s.wave_pct_s).clamp(0.01, 0.99);
600 let ml = self.s.mom_pct_min.clamp(0.01, 0.99);
601
602 self.wave_ok_long = self.wr_pct >= wl;
603 self.wave_ok_short = self.wr_pct <= ws;
604 self.mom_ok_long = self.mom_pct >= ml && self.cur_ratio > 0.0;
605 self.mom_ok_short = self.mom_pct >= ml && self.cur_ratio < 0.0;
606 }
607
608 fn hma_smooth(&mut self, length: usize) -> f64 {
610 let sn: Vec<f64> = self.speed_norm.iter().copied().collect();
611 if sn.len() < 2 {
612 return *sn.last().unwrap_or(&0.0);
613 }
614 let half = (length / 2).max(1);
615 let sqrt_n = (length as f64).sqrt().round() as usize;
616 let raw = 2.0 * wma(&sn[sn.len().saturating_sub(half)..])
617 - wma(&sn[sn.len().saturating_sub(length)..]);
618 if self.hma_buf.len() == sqrt_n {
619 self.hma_buf.pop_front();
620 }
621 self.hma_buf.push_back(raw);
622 let hma_arr: Vec<f64> = self.hma_buf.iter().copied().collect();
623 wma(&hma_arr)
624 }
625
626 fn upd_ao(&mut self) {
629 if self.highs.len() < 34 {
630 return;
631 }
632 let hs: Vec<f64> = self.highs.iter().copied().collect();
633 let ls: Vec<f64> = self.lows.iter().copied().collect();
634 let hl2: Vec<f64> = hs
635 .iter()
636 .zip(ls.iter())
637 .map(|(h, l)| (h + l) / 2.0)
638 .collect();
639 let n = hl2.len();
640 let ao_new =
641 hl2[n - 5..].iter().sum::<f64>() / 5.0 - hl2[n - 34..].iter().sum::<f64>() / 34.0;
642 self.ao_rising = ao_new > self.ao;
643 self.ao = ao_new;
644 }
645
646 fn upd_hurst(&mut self) {
649 let lb = self.s.hurst_lookback;
650 let min_bars = lb * 2 + 1;
651 if self.closes.len() < min_bars || (self.bar - self.hurst_last_bar) < 10 {
652 return;
653 }
654 let cl_arr: Vec<f64> = self.closes.iter().rev().take(min_bars).copied().collect();
655 self.hurst = hurst_scalar(&cl_arr, lb);
656 self.hurst_last_bar = self.bar;
657 }
658
659 fn upd_accel(&mut self) {
662 let k = 3usize;
663 let n = self.closes.len();
664 if n <= k * 2 {
665 return;
666 }
667 let cl: Vec<f64> = self.closes.iter().copied().collect();
668 let vel_now = (cl[n - 1] - cl[n - 1 - k]) / (cl[n - 1 - k] + 1e-10);
669 let vel_prev = (cl[n - 1 - k] - cl[n - 1 - k * 2]) / (cl[n - 1 - k * 2] + 1e-10);
670 if self.vel_buf.len() == 110 {
671 self.vel_buf.pop_front();
672 }
673 self.vel_buf.push_back(vel_now);
674 let accel = vel_now - vel_prev;
675 let vel_std = if self.vel_buf.len() > 1 {
676 let vv: Vec<f64> = self.vel_buf.iter().copied().collect();
677 let mean = vv.iter().sum::<f64>() / vv.len() as f64;
678 let var = vv.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / vv.len() as f64;
679 var.sqrt()
680 } else {
681 1.0
682 };
683 self.price_accel = (accel / (vel_std + 1e-10) / 3.0).clamp(-1.0, 1.0);
684 }
685
686 pub fn update(&mut self, candle: &Candle) -> bool {
690 let cap = self.maxlen;
691 macro_rules! push {
692 ($buf:expr, $val:expr) => {
693 if $buf.len() == cap {
694 $buf.pop_front();
695 }
696 $buf.push_back($val);
697 };
698 }
699 push!(self.opens, candle.open);
700 push!(self.highs, candle.high);
701 push!(self.lows, candle.low);
702 push!(self.closes, candle.close);
703 push!(self.volumes, candle.volume);
704 push!(self.times, candle.time);
705 self.bar += 1;
706
707 self.vwap = Some(self.upd_vwap(candle));
708
709 let k = 2.0 / (self.s.ema_len as f64 + 1.0);
710 self.ema9 = Some(match self.ema9 {
711 None => candle.close,
712 Some(e) => candle.close * k + e * (1.0 - k),
713 });
714 self.ema = self.ema9;
715
716 let atr_val = self.upd_atr(candle);
717 self.atr = Some(atr_val);
718
719 self.upd_trend_speed(candle);
720 self.upd_ao();
721 self.upd_hurst();
722 self.upd_accel();
723
724 if self.closes.len() < self.s.training_period {
725 return false;
726 }
727
728 let adaptive_atr = self.kmeans_atr(atr_val);
729 let (st, dir) = self.upd_supertrend(adaptive_atr, candle.close);
730 self.st = Some(st);
731 self.st_dir_pub = dir;
732
733 true
734 }
735
736 pub fn check_speed_exit(&self, position: i32) -> bool {
739 let Some(thr) = self.s.ts_speed_exit_threshold else {
740 return false;
741 };
742 if position > 0 && self.ts_speed < -thr.abs() {
743 return true;
744 }
745 position < 0 && self.ts_speed > thr.abs()
746 }
747}