1use crate::traits::Next;
6
7const RAD2DEG: f64 = 180.0 / std::f64::consts::PI;
8const DEG2RAD: f64 = std::f64::consts::PI / 180.0;
9const CONST_DEG2RAD_BY360: f64 = 2.0 * std::f64::consts::PI;
10const A: f64 = 0.0962;
11const B: f64 = 0.5769;
12const SMOOTH_PRICE_SIZE: usize = 50;
13
14#[derive(Debug, Clone, Default)]
15struct HilbertVars {
16 odd: [f64; 3],
17 even: [f64; 3],
18 prev_odd: f64,
19 prev_even: f64,
20 prev_input_odd: f64,
21 prev_input_even: f64,
22}
23
24#[inline(always)]
25fn do_hilbert_even(vars: &mut HilbertVars, input: f64, hilbert_idx: usize, adj: f64) -> f64 {
26 let t = A * input;
27 let mut result = -vars.even[hilbert_idx];
28 vars.even[hilbert_idx] = t;
29 result += t;
30 result -= vars.prev_even;
31 vars.prev_even = B * vars.prev_input_even;
32 result += vars.prev_even;
33 vars.prev_input_even = input;
34 result * adj
35}
36
37#[inline(always)]
38fn do_hilbert_odd(vars: &mut HilbertVars, input: f64, hilbert_idx: usize, adj: f64) -> f64 {
39 let t = A * input;
40 let mut result = -vars.odd[hilbert_idx];
41 vars.odd[hilbert_idx] = t;
42 result += t;
43 result -= vars.prev_odd;
44 vars.prev_odd = B * vars.prev_input_odd;
45 result += vars.prev_odd;
46 vars.prev_input_odd = input;
47 result * adj
48}
49
50#[derive(Debug, Clone)]
51struct HtWma {
52 period_wma_sub: f64,
53 period_wma_sum: f64,
54 trailing_wma_value: f64,
55 prices: Vec<f64>,
56 trailing_idx: usize,
57}
58
59impl HtWma {
60 fn from_first_three(p0: f64, p1: f64, p2: f64) -> Self {
61 Self {
62 period_wma_sub: p0 + p1 + p2,
63 period_wma_sum: p0 + p1 * 2.0 + p2 * 3.0,
64 trailing_wma_value: 0.0,
65 prices: vec![p0, p1, p2],
66 trailing_idx: 0,
67 }
68 }
69
70 fn next(&mut self, new_price: f64) -> f64 {
71 self.prices.push(new_price);
72 self.period_wma_sub += new_price;
73 self.period_wma_sub -= self.trailing_wma_value;
74 self.period_wma_sum += new_price * 4.0;
75 self.trailing_wma_value = self.prices[self.trailing_idx];
76 self.trailing_idx += 1;
77 let smoothed = self.period_wma_sum * 0.1;
78 self.period_wma_sum -= self.period_wma_sub;
79 smoothed
80 }
81}
82
83#[derive(Debug, Clone)]
84struct HilbertPeriodState {
85 hilbert_idx: usize,
86 detrender_vars: HilbertVars,
87 q1_vars: HilbertVars,
88 ji_vars: HilbertVars,
89 jq_vars: HilbertVars,
90 period: f64,
91 smooth_period: f64,
92 prev_i2: f64,
93 prev_q2: f64,
94 re: f64,
95 im: f64,
96 i1_for_odd_prev2: f64,
97 i1_for_odd_prev3: f64,
98 i1_for_even_prev2: f64,
99 i1_for_even_prev3: f64,
100}
101
102impl Default for HilbertPeriodState {
103 fn default() -> Self {
104 Self {
105 hilbert_idx: 0,
106 detrender_vars: HilbertVars::default(),
107 q1_vars: HilbertVars::default(),
108 ji_vars: HilbertVars::default(),
109 jq_vars: HilbertVars::default(),
110 period: 0.0,
111 smooth_period: 0.0,
112 prev_i2: 0.0,
113 prev_q2: 0.0,
114 re: 0.0,
115 im: 0.0,
116 i1_for_odd_prev2: 0.0,
117 i1_for_odd_prev3: 0.0,
118 i1_for_even_prev2: 0.0,
119 i1_for_even_prev3: 0.0,
120 }
121 }
122}
123
124impl HilbertPeriodState {
125 fn adjust_period(&mut self) {
126 let temp_real = self.period;
127 if self.im != 0.0 && self.re != 0.0 {
128 self.period = 360.0 / ((self.im / self.re).atan() * RAD2DEG);
129 }
130 let mut temp_real2 = 1.5 * temp_real;
131 if self.period > temp_real2 {
132 self.period = temp_real2;
133 }
134 temp_real2 = 0.67 * temp_real;
135 if self.period < temp_real2 {
136 self.period = temp_real2;
137 }
138 self.period = self.period.clamp(6.0, 50.0);
139 self.period = 0.2 * self.period + 0.8 * temp_real;
140 self.smooth_period = 0.33 * self.period + 0.67 * self.smooth_period;
141 }
142
143 fn step_hilbert(&mut self, today: usize, smoothed: f64, adj: f64) -> (f64, f64, f64, f64) {
144 let (detrender, q1, i2, q2);
145 if today.is_multiple_of(2) {
146 detrender = do_hilbert_even(&mut self.detrender_vars, smoothed, self.hilbert_idx, adj);
147 q1 = do_hilbert_even(&mut self.q1_vars, detrender, self.hilbert_idx, adj);
148 let ji = do_hilbert_even(
149 &mut self.ji_vars,
150 self.i1_for_even_prev3,
151 self.hilbert_idx,
152 adj,
153 );
154 let jq = do_hilbert_even(&mut self.jq_vars, q1, self.hilbert_idx, adj);
155 self.hilbert_idx += 1;
156 if self.hilbert_idx == 3 {
157 self.hilbert_idx = 0;
158 }
159 q2 = 0.2 * (q1 + ji) + 0.8 * self.prev_q2;
160 i2 = 0.2 * (self.i1_for_even_prev3 - jq) + 0.8 * self.prev_i2;
161 self.i1_for_odd_prev3 = self.i1_for_odd_prev2;
162 self.i1_for_odd_prev2 = detrender;
163 } else {
164 detrender = do_hilbert_odd(&mut self.detrender_vars, smoothed, self.hilbert_idx, adj);
165 q1 = do_hilbert_odd(&mut self.q1_vars, detrender, self.hilbert_idx, adj);
166 let ji = do_hilbert_odd(
167 &mut self.ji_vars,
168 self.i1_for_odd_prev3,
169 self.hilbert_idx,
170 adj,
171 );
172 let jq = do_hilbert_odd(&mut self.jq_vars, q1, self.hilbert_idx, adj);
173 q2 = 0.2 * (q1 + ji) + 0.8 * self.prev_q2;
174 i2 = 0.2 * (self.i1_for_odd_prev3 - jq) + 0.8 * self.prev_i2;
175 self.i1_for_even_prev3 = self.i1_for_even_prev2;
176 self.i1_for_even_prev2 = detrender;
177 }
178 self.re = 0.2 * (i2 * self.prev_i2 + q2 * self.prev_q2) + 0.8 * self.re;
179 self.im = 0.2 * (i2 * self.prev_q2 - q2 * self.prev_i2) + 0.8 * self.im;
180 self.prev_q2 = q2;
181 self.prev_i2 = i2;
182 (detrender, q1, i2, q2)
183 }
184}
185
186#[derive(Debug, Clone)]
187struct HtEngine32 {
188 prices: Vec<f64>,
189 wma: Option<HtWma>,
190 warmup_left: u8,
191 hs: HilbertPeriodState,
192}
193
194impl HtEngine32 {
195 const LOOKBACK: usize = 32;
196 const WARMUP: u8 = 9;
197
198 fn new() -> Self {
199 Self {
200 prices: Vec::new(),
201 wma: None,
202 warmup_left: Self::WARMUP,
203 hs: HilbertPeriodState::default(),
204 }
205 }
206
207 fn push(&mut self, price: f64) {
208 self.prices.push(price);
209 }
210
211 fn today(&self) -> usize {
212 self.prices.len().saturating_sub(1)
213 }
214
215 fn step_wma(&mut self) -> Option<f64> {
216 let n = self.prices.len();
217 if n < 3 {
218 return None;
219 }
220 if self.wma.is_none() {
221 self.wma = Some(HtWma::from_first_three(
222 self.prices[0],
223 self.prices[1],
224 self.prices[2],
225 ));
226 return None;
227 }
228 let wma = self.wma.as_mut()?;
229 let price = *self.prices.last().unwrap_or(&0.0);
230 if self.warmup_left > 0 {
231 self.warmup_left -= 1;
232 let _ = wma.next(price);
233 return None;
234 }
235 Some(wma.next(price))
236 }
237}
238
239#[derive(Debug, Clone)]
241#[allow(non_camel_case_types)]
242pub struct HT_DCPERIOD {
243 eng: HtEngine32,
244}
245
246impl Default for HT_DCPERIOD {
247 fn default() -> Self {
248 Self::new()
249 }
250}
251
252impl HT_DCPERIOD {
253 pub fn new() -> Self {
254 Self {
255 eng: HtEngine32::new(),
256 }
257 }
258}
259
260impl Next<f64> for HT_DCPERIOD {
261 type Output = f64;
262
263 fn next(&mut self, input: f64) -> Self::Output {
264 self.eng.push(input);
265 let Some(smoothed) = self.eng.step_wma() else {
266 return f64::NAN;
267 };
268 let today = self.eng.today();
269 let adj = 0.075 * self.eng.hs.period + 0.54;
270 self.eng.hs.step_hilbert(today, smoothed, adj);
271 self.eng.hs.adjust_period();
272 if today >= HtEngine32::LOOKBACK {
273 self.eng.hs.smooth_period
274 } else {
275 f64::NAN
276 }
277 }
278}
279
280#[derive(Debug, Clone)]
282#[allow(non_camel_case_types)]
283pub struct HT_PHASOR {
284 eng: HtEngine32,
285}
286
287impl Default for HT_PHASOR {
288 fn default() -> Self {
289 Self::new()
290 }
291}
292
293impl HT_PHASOR {
294 pub fn new() -> Self {
295 Self {
296 eng: HtEngine32::new(),
297 }
298 }
299}
300
301impl Next<f64> for HT_PHASOR {
302 type Output = (f64, f64);
303
304 fn next(&mut self, input: f64) -> Self::Output {
305 self.eng.push(input);
306 let Some(smoothed) = self.eng.step_wma() else {
307 return (f64::NAN, f64::NAN);
308 };
309 let today = self.eng.today();
310 let adj = 0.075 * self.eng.hs.period + 0.54;
311 let inphase = if today.is_multiple_of(2) {
312 self.eng.hs.i1_for_even_prev3
313 } else {
314 self.eng.hs.i1_for_odd_prev3
315 };
316 let (_, q1, _, _) = self.eng.hs.step_hilbert(today, smoothed, adj);
317 self.eng.hs.adjust_period();
318 if today >= HtEngine32::LOOKBACK {
319 (inphase, q1)
320 } else {
321 (f64::NAN, f64::NAN)
322 }
323 }
324}
325
326#[derive(Debug, Clone)]
334pub struct TalibMama {
335 eng: HtEngine32,
336 fast_limit: f64,
337 slow_limit: f64,
338 prev_phase: f64,
339 prev_mama: f64,
340 prev_fama: f64,
341}
342
343impl Default for TalibMama {
344 fn default() -> Self {
345 Self::new(0.5, 0.05)
346 }
347}
348
349impl TalibMama {
350 pub fn new(fast_limit: f64, slow_limit: f64) -> Self {
351 Self {
352 eng: HtEngine32::new(),
353 fast_limit,
354 slow_limit,
355 prev_phase: 0.0,
356 prev_mama: 0.0,
357 prev_fama: 0.0,
358 }
359 }
360}
361
362impl Next<f64> for TalibMama {
363 type Output = (f64, f64);
365
366 fn next(&mut self, input: f64) -> Self::Output {
367 self.eng.push(input);
368 let Some(smoothed) = self.eng.step_wma() else {
369 return (f64::NAN, f64::NAN);
370 };
371 let today = self.eng.today();
372 let adj = 0.075 * self.eng.hs.period + 0.54;
373 let i1 = if today.is_multiple_of(2) {
376 self.eng.hs.i1_for_even_prev3
377 } else {
378 self.eng.hs.i1_for_odd_prev3
379 };
380 let (_, q1, _, _) = self.eng.hs.step_hilbert(today, smoothed, adj);
381 self.eng.hs.adjust_period();
382
383 let phase = if i1 != 0.0 {
384 (q1 / i1).atan() * RAD2DEG
385 } else {
386 0.0
387 };
388 let delta_phase = (self.prev_phase - phase).max(1.0);
389 let mut alpha = self.fast_limit / delta_phase;
392 if alpha < self.slow_limit {
393 alpha = self.slow_limit;
394 }
395 if alpha > self.fast_limit {
396 alpha = self.fast_limit;
397 }
398
399 self.prev_mama = alpha * input + (1.0 - alpha) * self.prev_mama;
400 self.prev_fama = 0.5 * alpha * self.prev_mama + (1.0 - 0.5 * alpha) * self.prev_fama;
401 self.prev_phase = phase;
402
403 if today >= HtEngine32::LOOKBACK {
404 (self.prev_mama, self.prev_fama)
405 } else {
406 (f64::NAN, f64::NAN)
407 }
408 }
409}
410
411#[derive(Debug, Clone)]
412struct HtEngine63 {
413 prices: Vec<f64>,
414 wma: Option<HtWma>,
415 warmup_left: u8,
416 hs: HilbertPeriodState,
417 smooth_price: [f64; SMOOTH_PRICE_SIZE],
418 smooth_price_idx: usize,
419 dc_phase: f64,
420 prev_dc_phase: f64,
421 i_trend1: f64,
422 i_trend2: f64,
423 i_trend3: f64,
424 days_in_trend: i32,
425 prev_sine: f64,
426 prev_lead_sine: f64,
427 sine: f64,
428 lead_sine: f64,
429 last_smoothed: f64,
430 last_trendline: f64,
431 last_trend: f64,
432}
433
434impl HtEngine63 {
435 const LOOKBACK: usize = 63;
436 const WARMUP: u8 = 34;
437
438 fn new() -> Self {
439 Self {
440 prices: Vec::new(),
441 wma: None,
442 warmup_left: Self::WARMUP,
443 hs: HilbertPeriodState::default(),
444 smooth_price: [0.0; SMOOTH_PRICE_SIZE],
445 smooth_price_idx: 0,
446 dc_phase: 0.0,
447 prev_dc_phase: 0.0,
448 i_trend1: 0.0,
449 i_trend2: 0.0,
450 i_trend3: 0.0,
451 days_in_trend: 0,
452 prev_sine: 0.0,
453 prev_lead_sine: 0.0,
454 sine: 0.0,
455 lead_sine: 0.0,
456 last_smoothed: 0.0,
457 last_trendline: 0.0,
458 last_trend: 0.0,
459 }
460 }
461
462 fn push(&mut self, price: f64) {
463 self.prices.push(price);
464 }
465
466 fn today(&self) -> usize {
467 self.prices.len().saturating_sub(1)
468 }
469
470 fn step_wma(&mut self) -> Option<f64> {
471 let n = self.prices.len();
472 if n < 3 {
473 return None;
474 }
475 if self.wma.is_none() {
476 self.wma = Some(HtWma::from_first_three(
477 self.prices[0],
478 self.prices[1],
479 self.prices[2],
480 ));
481 return None;
482 }
483 let wma = self.wma.as_mut()?;
484 let price = *self.prices.last().unwrap_or(&0.0);
485 if self.warmup_left > 0 {
486 self.warmup_left -= 1;
487 let _ = wma.next(price);
488 return None;
489 }
490 Some(wma.next(price))
491 }
492
493 fn compute_dc_phase(&mut self) {
494 self.prev_dc_phase = self.dc_phase;
495 let dc_period = self.hs.smooth_period + 0.5;
496 let dc_period_int = dc_period as i32;
497 let mut real_part = 0.0_f64;
498 let mut imag_part = 0.0_f64;
499 let mut idx = self.smooth_price_idx;
500 for i in 0..dc_period_int {
501 let angle = (i as f64 * CONST_DEG2RAD_BY360) / dc_period_int as f64;
502 let price = self.smooth_price[idx];
503 real_part += angle.sin() * price;
504 imag_part += angle.cos() * price;
505 if idx == 0 {
506 idx = SMOOTH_PRICE_SIZE - 1;
507 } else {
508 idx -= 1;
509 }
510 }
511 let abs_imag = imag_part.abs();
512 if abs_imag > 0.0 {
513 self.dc_phase = (real_part / imag_part).atan() * RAD2DEG;
514 } else if abs_imag <= 0.01 {
515 if real_part < 0.0 {
516 self.dc_phase -= 90.0;
517 } else if real_part > 0.0 {
518 self.dc_phase += 90.0;
519 }
520 }
521 self.dc_phase += 90.0;
522 self.dc_phase += 360.0 / self.hs.smooth_period;
523 if imag_part < 0.0 {
524 self.dc_phase += 180.0;
525 }
526 if self.dc_phase > 315.0 {
527 self.dc_phase -= 360.0;
528 }
529 }
530
531 fn sum_prices_back(&self, count: i32) -> f64 {
532 let mut temp = 0.0_f64;
533 let mut price_idx = self.today();
534 for _ in 0..count {
535 temp += self.prices[price_idx];
536 if price_idx == 0 {
537 break;
538 }
539 price_idx -= 1;
540 }
541 if count > 0 { temp / count as f64 } else { temp }
542 }
543
544 fn step_core(&mut self) -> bool {
545 let Some(smoothed) = self.step_wma() else {
546 return false;
547 };
548 let today = self.today();
549 let adj = 0.075 * self.hs.period + 0.54;
550 self.last_smoothed = smoothed;
551 self.smooth_price[self.smooth_price_idx] = smoothed;
552 self.hs.step_hilbert(today, smoothed, adj);
553 self.hs.adjust_period();
554 self.compute_dc_phase();
555 self.prev_sine = self.sine;
556 self.prev_lead_sine = self.lead_sine;
557 self.sine = (self.dc_phase * DEG2RAD).sin();
558 self.lead_sine = ((self.dc_phase + 45.0) * DEG2RAD).sin();
559 self.smooth_price_idx += 1;
560 if self.smooth_price_idx >= SMOOTH_PRICE_SIZE {
561 self.smooth_price_idx = 0;
562 }
563 self.update_trend_mode();
564 true
565 }
566
567 fn trendline(&mut self) -> f64 {
568 let dc_period_int = (self.hs.smooth_period + 0.5) as i32;
569 let temp = self.sum_prices_back(dc_period_int);
570 let trendline =
571 (4.0 * temp + 3.0 * self.i_trend1 + 2.0 * self.i_trend2 + self.i_trend3) / 10.0;
572 self.i_trend3 = self.i_trend2;
573 self.i_trend2 = self.i_trend1;
574 self.i_trend1 = temp;
575 self.last_trendline = trendline;
576 trendline
577 }
578
579 fn update_trend_mode(&mut self) {
581 let trendline = self.trendline();
582 let mut trend = 1.0_f64;
583 if (self.sine > self.lead_sine && self.prev_sine <= self.prev_lead_sine)
584 || (self.sine < self.lead_sine && self.prev_sine >= self.prev_lead_sine)
585 {
586 self.days_in_trend = 0;
587 trend = 0.0;
588 }
589 self.days_in_trend += 1;
590 if (self.days_in_trend as f64) < 0.5 * self.hs.smooth_period {
591 trend = 0.0;
592 }
593 let phase_change = self.dc_phase - self.prev_dc_phase;
594 if self.hs.smooth_period != 0.0
595 && phase_change > 0.67 * 360.0 / self.hs.smooth_period
596 && phase_change < 1.5 * 360.0 / self.hs.smooth_period
597 {
598 trend = 0.0;
599 }
600 if trendline != 0.0 && ((self.last_smoothed - trendline) / trendline).abs() >= 0.015 {
601 trend = 1.0;
602 }
603 self.last_trend = trend;
604 }
605}
606
607#[derive(Debug, Clone)]
608#[allow(non_camel_case_types)]
609pub struct HT_DCPHASE {
610 eng: HtEngine63,
611}
612
613impl Default for HT_DCPHASE {
614 fn default() -> Self {
615 Self::new()
616 }
617}
618
619impl HT_DCPHASE {
620 pub fn new() -> Self {
621 Self {
622 eng: HtEngine63::new(),
623 }
624 }
625}
626
627impl Next<f64> for HT_DCPHASE {
628 type Output = f64;
629
630 fn next(&mut self, input: f64) -> Self::Output {
631 self.eng.push(input);
632 if !self.eng.step_core() {
633 return f64::NAN;
634 }
635 if self.eng.today() >= HtEngine63::LOOKBACK {
636 self.eng.dc_phase
637 } else {
638 f64::NAN
639 }
640 }
641}
642
643#[derive(Debug, Clone)]
644#[allow(non_camel_case_types)]
645pub struct HT_SINE {
646 eng: HtEngine63,
647}
648
649impl Default for HT_SINE {
650 fn default() -> Self {
651 Self::new()
652 }
653}
654
655impl HT_SINE {
656 pub fn new() -> Self {
657 Self {
658 eng: HtEngine63::new(),
659 }
660 }
661}
662
663impl Next<f64> for HT_SINE {
664 type Output = (f64, f64);
665
666 fn next(&mut self, input: f64) -> Self::Output {
667 self.eng.push(input);
668 if !self.eng.step_core() {
669 return (f64::NAN, f64::NAN);
670 }
671 if self.eng.today() >= HtEngine63::LOOKBACK {
672 (self.eng.sine, self.eng.lead_sine)
673 } else {
674 (f64::NAN, f64::NAN)
675 }
676 }
677}
678
679#[derive(Debug, Clone)]
680#[allow(non_camel_case_types)]
681pub struct HT_TRENDMODE {
682 eng: HtEngine63,
683}
684
685impl Default for HT_TRENDMODE {
686 fn default() -> Self {
687 Self::new()
688 }
689}
690
691impl HT_TRENDMODE {
692 pub fn new() -> Self {
693 Self {
694 eng: HtEngine63::new(),
695 }
696 }
697}
698
699impl Next<f64> for HT_TRENDMODE {
700 type Output = f64;
701
702 fn next(&mut self, input: f64) -> Self::Output {
703 self.eng.push(input);
704 if !self.eng.step_core() {
705 return f64::NAN;
706 }
707 if self.eng.today() >= HtEngine63::LOOKBACK {
708 self.eng.last_trend
709 } else {
710 f64::NAN
711 }
712 }
713}
714
715#[derive(Debug, Clone)]
716#[allow(non_camel_case_types)]
717pub struct HT_TRENDLINE {
718 eng: HtEngine63,
719}
720
721impl Default for HT_TRENDLINE {
722 fn default() -> Self {
723 Self::new()
724 }
725}
726
727impl HT_TRENDLINE {
728 pub fn new() -> Self {
729 Self {
730 eng: HtEngine63::new(),
731 }
732 }
733}
734
735impl Next<f64> for HT_TRENDLINE {
736 type Output = f64;
737
738 fn next(&mut self, input: f64) -> Self::Output {
739 self.eng.push(input);
740 if !self.eng.step_core() {
741 return f64::NAN;
742 }
743 if self.eng.today() >= HtEngine63::LOOKBACK {
744 self.eng.last_trendline
745 } else {
746 f64::NAN
747 }
748 }
749}
750
751#[cfg(test)]
752mod tests {
753 use super::*;
754 use proptest::prelude::*;
755
756 proptest! {
757 #[test]
758 fn test_ht_dcperiod_parity(input in prop::collection::vec(0.1..100.0, 33..100)) {
759 let mut ht = HT_DCPERIOD::new();
760 let streaming: Vec<f64> = input.iter().map(|&x| ht.next(x)).collect();
761 let batch = talib_rs::cycle::ht_dcperiod(&input).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
762 for (s, b) in streaming.iter().zip(batch.iter()) {
763 if s.is_nan() { assert!(b.is_nan()); }
764 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
765 }
766 }
767
768 #[test]
769 fn test_ht_phasor_parity(input in prop::collection::vec(0.1..100.0, 33..100)) {
770 let mut ht = HT_PHASOR::new();
771 let streaming: Vec<_> = input.iter().map(|&x| ht.next(x)).collect();
772 let (bi, bq) = talib_rs::cycle::ht_phasor(&input).unwrap_or_else(|_| {
773 (vec![f64::NAN; input.len()], vec![f64::NAN; input.len()])
774 });
775 for (i, &(s_i, s_q)) in streaming.iter().enumerate() {
776 if s_i.is_nan() { assert!(bi[i].is_nan()); }
777 else { approx::assert_relative_eq!(s_i, bi[i], epsilon = 1e-6); }
778 if s_q.is_nan() { assert!(bq[i].is_nan()); }
779 else { approx::assert_relative_eq!(s_q, bq[i], epsilon = 1e-6); }
780 }
781 }
782
783 #[test]
784 fn test_talib_mama_parity(input in prop::collection::vec(0.1..100.0, 33..120)) {
785 let mut mama = TalibMama::new(0.5, 0.05);
786 let streaming: Vec<_> = input.iter().map(|&x| mama.next(x)).collect();
787 let (bm, bf) = talib_rs::overlap::mama(&input, 0.5, 0.05).unwrap_or_else(|_| {
788 (vec![f64::NAN; input.len()], vec![f64::NAN; input.len()])
789 });
790 for (i, &(s_m, s_f)) in streaming.iter().enumerate() {
791 if s_m.is_nan() { assert!(bm[i].is_nan()); }
792 else { approx::assert_relative_eq!(s_m, bm[i], epsilon = 1e-6); }
793 if s_f.is_nan() { assert!(bf[i].is_nan()); }
794 else { approx::assert_relative_eq!(s_f, bf[i], epsilon = 1e-6); }
795 }
796 }
797
798 #[test]
799 fn test_ht_dcphase_parity(input in prop::collection::vec(0.1..100.0, 64..100)) {
800 let mut ht = HT_DCPHASE::new();
801 let streaming: Vec<f64> = input.iter().map(|&x| ht.next(x)).collect();
802 let batch = talib_rs::cycle::ht_dcphase(&input).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
803 for (s, b) in streaming.iter().zip(batch.iter()) {
804 if s.is_nan() { assert!(b.is_nan()); }
805 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
806 }
807 }
808
809 #[test]
810 fn test_ht_sine_parity(input in prop::collection::vec(0.1..100.0, 64..100)) {
811 let mut ht = HT_SINE::new();
812 let streaming: Vec<_> = input.iter().map(|&x| ht.next(x)).collect();
813 let (bs, bl) = talib_rs::cycle::ht_sine(&input).unwrap_or_else(|_| {
814 (vec![f64::NAN; input.len()], vec![f64::NAN; input.len()])
815 });
816 for (i, &(s_s, s_l)) in streaming.iter().enumerate() {
817 if s_s.is_nan() { assert!(bs[i].is_nan()); }
818 else { approx::assert_relative_eq!(s_s, bs[i], epsilon = 1e-6); }
819 if s_l.is_nan() { assert!(bl[i].is_nan()); }
820 else { approx::assert_relative_eq!(s_l, bl[i], epsilon = 1e-6); }
821 }
822 }
823
824 #[test]
825 fn test_ht_trendmode_parity(input in prop::collection::vec(0.1..100.0, 64..100)) {
826 let mut ht = HT_TRENDMODE::new();
827 let streaming: Vec<f64> = input.iter().map(|&x| ht.next(x)).collect();
828 let batch = talib_rs::cycle::ht_trendmode(&input).unwrap_or_else(|_| vec![0; input.len()]);
829 for (s, b) in streaming.iter().zip(batch.iter()) {
830 assert_eq!(*s as i32, *b);
831 }
832 }
833
834 #[test]
835 fn test_ht_trendline_parity(input in prop::collection::vec(0.1..100.0, 64..100)) {
836 let mut ht = HT_TRENDLINE::new();
837 let streaming: Vec<f64> = input.iter().map(|&x| ht.next(x)).collect();
838 let batch = talib_rs::overlap::ht_trendline(&input).unwrap_or_else(|_| vec![f64::NAN; input.len()]);
839 for (s, b) in streaming.iter().zip(batch.iter()) {
840 if s.is_nan() { assert!(b.is_nan()); }
841 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
842 }
843 }
844 }
845}