1mod candles;
24pub use candles::*;
25use std::collections::VecDeque;
26
27#[derive(Debug, Clone)]
28pub struct Macd {
29 pub macd: Vec<Option<f64>>,
30 pub signal: Vec<Option<f64>>,
31 pub hist: Vec<Option<f64>>,
32}
33
34#[derive(Debug, Clone)]
35pub struct AdxFamily {
36 pub adx: Vec<Option<f64>>,
37 pub adxr: Vec<Option<f64>>,
38 pub plus_di: Vec<Option<f64>>,
39 pub minus_di: Vec<Option<f64>>,
40 pub dx: Vec<Option<f64>>,
41}
42
43#[derive(Debug, Clone)]
44pub struct Aroon {
45 pub up: Vec<Option<f64>>,
46 pub down: Vec<Option<f64>>,
47 pub oscillator: Vec<Option<f64>>,
48}
49
50#[derive(Debug, Clone)]
51pub struct PriceTransforms {
52 pub avgprice: Vec<Option<f64>>,
53 pub medprice: Vec<Option<f64>>,
54 pub typprice: Vec<Option<f64>>,
55 pub wclprice: Vec<Option<f64>>,
56}
57
58#[derive(Debug, Clone)]
59pub struct BollingerBands {
60 pub upper: Vec<Option<f64>>,
61 pub middle: Vec<Option<f64>>,
62 pub lower: Vec<Option<f64>>,
63}
64
65#[derive(Debug, Clone)]
66pub struct DirectionalMovement {
67 pub plus_dm: Vec<Option<f64>>,
68 pub minus_dm: Vec<Option<f64>>,
69}
70
71#[derive(Debug, Clone)]
72pub struct StochRsi {
73 pub k: Vec<Option<f64>>,
74 pub d: Vec<Option<f64>>,
75}
76
77#[derive(Debug, Clone)]
78pub struct LinearRegression {
79 pub line: Vec<Option<f64>>,
80 pub slope: Vec<Option<f64>>,
81 pub angle: Vec<Option<f64>>,
82 pub intercept: Vec<Option<f64>>,
83 pub tsf: Vec<Option<f64>>,
84}
85
86#[derive(Debug, Clone)]
87pub struct PriceContext {
88 pub close_vs_ath_pct: Vec<Option<f64>>,
89 pub close_vs_atl_pct: Vec<Option<f64>>,
90 pub days_since_ath: Vec<Option<f64>>,
91 pub days_since_atl: Vec<Option<f64>>,
92}
93
94#[inline]
95fn finite(value: f64) -> Option<f64> {
96 value.is_finite().then_some(value)
97}
98
99fn option_values(values: &[Option<f64>]) -> Vec<f64> {
100 values
101 .iter()
102 .map(|value| value.unwrap_or(f64::NAN))
103 .collect()
104}
105
106pub fn ema(values: &[f64], period: usize) -> Vec<Option<f64>> {
107 let mut out = vec![None; values.len()];
108 if period == 0 {
109 return out;
110 }
111 let alpha = 2.0 / (period as f64 + 1.0);
112 let mut current = None;
113 let mut warm_sum = 0.0;
114 let mut warm_count = 0usize;
115 for (idx, value) in values.iter().copied().enumerate() {
116 let Some(value) = finite(value) else {
117 continue;
118 };
119 if let Some(prev) = current {
120 let next = alpha * value + (1.0 - alpha) * prev;
121 current = Some(next);
122 out[idx] = Some(next);
123 } else {
124 warm_sum += value;
125 warm_count += 1;
126 if warm_count == period {
127 let next = warm_sum / period as f64;
128 current = Some(next);
129 out[idx] = Some(next);
130 }
131 }
132 }
133 out
134}
135
136fn ema_seeded_at(values: &[f64], period: usize, seed_end: usize) -> Vec<Option<f64>> {
140 let mut out = vec![None; values.len()];
141 if period == 0 || seed_end >= values.len() || seed_end + 1 < period {
142 return out;
143 }
144 let alpha = 2.0 / (period as f64 + 1.0);
145 let seed = values[seed_end + 1 - period..=seed_end].iter().sum::<f64>() / period as f64;
146 out[seed_end] = Some(seed);
147 let mut prev = seed;
148 for (idx, value) in values.iter().enumerate().skip(seed_end + 1) {
149 prev = alpha * value + (1.0 - alpha) * prev;
150 out[idx] = Some(prev);
151 }
152 out
153}
154
155fn ema_seeded_options(
158 series: &[Option<f64>],
159 period: usize,
160 valid_start: usize,
161) -> Vec<Option<f64>> {
162 let mut out = vec![None; series.len()];
163 let seed_end = valid_start + period.saturating_sub(1);
164 if period == 0 || seed_end >= series.len() {
165 return out;
166 }
167 let mut sum = 0.0;
168 for value in series.iter().take(seed_end + 1).skip(valid_start) {
169 match value {
170 Some(value) => sum += value,
171 None => return out,
172 }
173 }
174 let alpha = 2.0 / (period as f64 + 1.0);
175 let seed = sum / period as f64;
176 out[seed_end] = Some(seed);
177 let mut prev = seed;
178 for (idx, value) in series.iter().enumerate().skip(seed_end + 1) {
179 if let Some(value) = value {
180 prev = alpha * value + (1.0 - alpha) * prev;
181 out[idx] = Some(prev);
182 }
183 }
184 out
185}
186
187pub fn macd(values: &[f64], fast: usize, slow: usize, signal: usize) -> Macd {
188 let len = values.len();
189 let mut macd_line = vec![None; len];
190 if fast == 0 || slow == 0 || signal == 0 {
191 return Macd {
192 macd: macd_line.clone(),
193 signal: vec![None; len],
194 hist: vec![None; len],
195 };
196 }
197 let seed_end = fast.max(slow) - 1;
201 let fast_ema = ema_seeded_at(values, fast, seed_end);
202 let slow_ema = ema_seeded_at(values, slow, seed_end);
203 for idx in seed_end..len {
204 if let (Some(f), Some(s)) = (fast_ema[idx], slow_ema[idx]) {
205 macd_line[idx] = Some(f - s);
206 }
207 }
208 let signal_line = ema_seeded_options(&macd_line, signal, seed_end);
209 let hist = macd_line
210 .iter()
211 .zip(signal_line.iter())
212 .map(|(macd, signal)| Some((*macd)? - (*signal)?))
213 .collect();
214 Macd {
215 macd: macd_line,
216 signal: signal_line,
217 hist,
218 }
219}
220
221pub fn macdfix(values: &[f64], signal: usize) -> Macd {
222 macd(values, 12, 26, signal)
223}
224
225fn sma(values: &[f64], period: usize) -> Vec<Option<f64>> {
227 rolling_sum(values, period)
228 .into_iter()
229 .map(|sum| sum.map(|sum| sum / period as f64))
230 .collect()
231}
232
233fn sma_options(series: &[Option<f64>], period: usize) -> Vec<Option<f64>> {
236 let mut out = vec![None; series.len()];
237 if period == 0 {
238 return out;
239 }
240 for idx in period - 1..series.len() {
241 let window = &series[idx + 1 - period..=idx];
242 let Some(sum) = window
243 .iter()
244 .try_fold(0.0, |sum, value| value.map(|value| sum + value))
245 else {
246 continue;
247 };
248 out[idx] = Some(sum / period as f64);
249 }
250 out
251}
252
253pub fn macdext_sma(values: &[f64], fast: usize, slow: usize, signal: usize) -> Macd {
257 let len = values.len();
258 if fast == 0 || slow == 0 || signal == 0 {
259 return Macd {
260 macd: vec![None; len],
261 signal: vec![None; len],
262 hist: vec![None; len],
263 };
264 }
265 let fast_ma = sma(values, fast);
266 let slow_ma = sma(values, slow);
267 let macd_line: Vec<Option<f64>> = fast_ma
268 .iter()
269 .zip(slow_ma.iter())
270 .map(|(fast, slow)| Some((*fast)? - (*slow)?))
271 .collect();
272 let signal_line = sma_options(&macd_line, signal);
273 let hist = macd_line
274 .iter()
275 .zip(signal_line.iter())
276 .map(|(macd, signal)| Some((*macd)? - (*signal)?))
277 .collect();
278 Macd {
279 macd: macd_line,
280 signal: signal_line,
281 hist,
282 }
283}
284
285pub fn mavp(
290 values: &[f64],
291 periods: &[f64],
292 min_period: usize,
293 max_period: usize,
294) -> Vec<Option<f64>> {
295 let len = values.len().min(periods.len());
296 let mut out = vec![None; values.len()];
297 if max_period == 0 || max_period < min_period {
298 return out;
299 }
300 if values[..len].iter().all(|value| value.is_finite()) {
301 let mut sums = vec![0.0; len + 1];
302 for (idx, value) in values.iter().take(len).enumerate() {
303 sums[idx + 1] = sums[idx] + value;
304 }
305 for idx in (max_period - 1)..len {
306 let raw = periods[idx];
307 if !raw.is_finite() || raw < 0.0 {
308 continue;
309 }
310 let period = (raw as usize).clamp(min_period, max_period);
311 if period == 0 {
312 continue;
313 }
314 let start = idx + 1 - period;
315 out[idx] = Some((sums[idx + 1] - sums[start]) / period as f64);
316 }
317 return out;
318 }
319 for idx in (max_period - 1)..len {
320 let raw = periods[idx];
321 if !raw.is_finite() || raw < 0.0 {
322 continue;
323 }
324 let period = (raw as usize).clamp(min_period, max_period);
325 if period == 0 {
326 continue;
327 }
328 let window = &values[idx + 1 - period..=idx];
329 if window.iter().all(|value| value.is_finite()) {
330 let sum: f64 = window.iter().sum();
331 out[idx] = Some(sum / period as f64);
332 }
333 }
334 out
335}
336
337pub fn beta(real0: &[f64], real1: &[f64], period: usize) -> Vec<Option<f64>> {
342 let len = real0.len().min(real1.len());
343 let mut out = vec![None; real0.len()];
344 if period == 0 || len <= period {
345 return out;
346 }
347 let n = period as f64;
348 if real0[..len].iter().all(|value| value.is_finite())
349 && real1[..len].iter().all(|value| value.is_finite())
350 && real0[..len - 1].iter().all(|value| *value != 0.0)
351 && real1[..len - 1].iter().all(|value| *value != 0.0)
352 {
353 let mut sx = 0.0;
356 let mut sy = 0.0;
357 let mut sxx = 0.0;
358 let mut sxy = 0.0;
359 let ret = |values: &[f64], idx: usize| (values[idx] - values[idx - 1]) / values[idx - 1];
360 for idx in 1..=period {
361 let x = ret(real0, idx);
362 let y = ret(real1, idx);
363 sx += x;
364 sy += y;
365 sxx += x * x;
366 sxy += x * y;
367 }
368 let emit = |slot: &mut Option<f64>, sx: f64, sy: f64, sxx: f64, sxy: f64| {
369 let denom = n * sxx - sx * sx;
370 if denom != 0.0 {
371 *slot = finite((n * sxy - sx * sy) / denom);
372 }
373 };
374 emit(&mut out[period], sx, sy, sxx, sxy);
375 for (idx, slot) in out.iter_mut().enumerate().take(len).skip(period + 1) {
376 let old_x = ret(real0, idx - period);
377 let old_y = ret(real1, idx - period);
378 let new_x = ret(real0, idx);
379 let new_y = ret(real1, idx);
380 sx += new_x - old_x;
381 sy += new_y - old_y;
382 sxx += new_x * new_x - old_x * old_x;
383 sxy += new_x * new_y - old_x * old_y;
384 emit(slot, sx, sy, sxx, sxy);
385 }
386 return out;
387 }
388 for (t, slot) in out.iter_mut().enumerate().take(len).skip(period) {
389 let (mut sx, mut sy, mut sxx, mut sxy) = (0.0, 0.0, 0.0, 0.0);
390 let mut ok = true;
391 for j in (t + 1 - period)..=t {
392 let (px, py) = (real0[j - 1], real1[j - 1]);
393 if !px.is_finite()
394 || !py.is_finite()
395 || !real0[j].is_finite()
396 || !real1[j].is_finite()
397 || px == 0.0
398 || py == 0.0
399 {
400 ok = false;
401 break;
402 }
403 let x = (real0[j] - px) / px;
404 let y = (real1[j] - py) / py;
405 sx += x;
406 sy += y;
407 sxx += x * x;
408 sxy += x * y;
409 }
410 if !ok {
411 continue;
412 }
413 let denom = n * sxx - sx * sx;
414 if denom == 0.0 {
415 continue;
416 }
417 *slot = finite((n * sxy - sx * sy) / denom);
418 }
419 out
420}
421
422pub fn correl(real0: &[f64], real1: &[f64], period: usize) -> Vec<Option<f64>> {
426 let len = real0.len().min(real1.len());
427 let mut out = vec![None; real0.len()];
428 if period == 0 {
429 return out;
430 }
431 if period > len {
432 return out;
433 }
434 let n = period as f64;
435 if real0[..len].iter().all(|value| value.is_finite())
436 && real1[..len].iter().all(|value| value.is_finite())
437 {
438 let mut sx = 0.0;
441 let mut sy = 0.0;
442 let mut sxx = 0.0;
443 let mut syy = 0.0;
444 let mut sxy = 0.0;
445 for idx in 0..period {
446 let x = real0[idx];
447 let y = real1[idx];
448 sx += x;
449 sy += y;
450 sxx += x * x;
451 syy += y * y;
452 sxy += x * y;
453 }
454 let emit = |slot: &mut Option<f64>, sx: f64, sy: f64, sxx: f64, syy: f64, sxy: f64| {
455 let numerator = n * sxy - sx * sy;
456 let denominator = ((n * sxx - sx * sx) * (n * syy - sy * sy)).sqrt();
457 *slot = if denominator > 0.0 {
458 finite(numerator / denominator)
459 } else {
460 Some(0.0)
461 };
462 };
463 emit(&mut out[period - 1], sx, sy, sxx, syy, sxy);
464 for idx in period..len {
465 let old_x = real0[idx - period];
466 let old_y = real1[idx - period];
467 let new_x = real0[idx];
468 let new_y = real1[idx];
469 sx += new_x - old_x;
470 sy += new_y - old_y;
471 sxx += new_x * new_x - old_x * old_x;
472 syy += new_y * new_y - old_y * old_y;
473 sxy += new_x * new_y - old_x * old_y;
474 emit(&mut out[idx], sx, sy, sxx, syy, sxy);
475 }
476 return out;
477 }
478 for (t, slot) in out.iter_mut().enumerate().take(len).skip(period - 1) {
479 let (mut sx, mut sy, mut sxx, mut syy, mut sxy) = (0.0, 0.0, 0.0, 0.0, 0.0);
480 let mut ok = true;
481 for j in (t + 1 - period)..=t {
482 let (x, y) = (real0[j], real1[j]);
483 if !x.is_finite() || !y.is_finite() {
484 ok = false;
485 break;
486 }
487 sx += x;
488 sy += y;
489 sxx += x * x;
490 syy += y * y;
491 sxy += x * y;
492 }
493 if !ok {
494 continue;
495 }
496 let var_product = (sxx - sx * sx / n) * (syy - sy * sy / n);
497 *slot = if var_product > 0.0 {
498 finite((sxy - sx * sy / n) / var_product.sqrt())
499 } else {
500 Some(0.0)
501 };
502 }
503 out
504}
505
506pub fn apo(values: &[f64], fast: usize, slow: usize) -> Vec<Option<f64>> {
507 let fast_ema = ema(values, fast);
508 let slow_ema = ema(values, slow);
509 fast_ema
510 .iter()
511 .zip(slow_ema.iter())
512 .map(|(fast, slow)| Some((*fast)? - (*slow)?))
513 .collect()
514}
515
516pub fn ppo(values: &[f64], fast: usize, slow: usize) -> Vec<Option<f64>> {
517 let fast_ema = ema(values, fast);
518 let slow_ema = ema(values, slow);
519 fast_ema
520 .iter()
521 .zip(slow_ema.iter())
522 .map(|(fast, slow)| {
523 let slow = slow.filter(|slow| slow.abs() > f64::EPSILON)?;
524 Some(((*fast)? / slow - 1.0) * 100.0)
525 })
526 .collect()
527}
528
529pub fn rsi(values: &[f64], period: usize) -> Vec<Option<f64>> {
530 let mut out = vec![None; values.len()];
531 if period == 0 || values.len() <= period {
532 return out;
533 }
534 let mut gains = 0.0;
535 let mut losses = 0.0;
536 for idx in 1..values.len() {
537 let change = values[idx] - values[idx - 1];
538 if !change.is_finite() {
539 continue;
540 }
541 let gain = change.max(0.0);
542 let loss = (-change).max(0.0);
543 if idx <= period {
544 gains += gain;
545 losses += loss;
546 if idx == period {
547 gains /= period as f64;
548 losses /= period as f64;
549 out[idx] = Some(rsi_value(gains, losses));
550 }
551 } else {
552 gains = (gains * (period as f64 - 1.0) + gain) / period as f64;
553 losses = (losses * (period as f64 - 1.0) + loss) / period as f64;
554 out[idx] = Some(rsi_value(gains, losses));
555 }
556 }
557 out
558}
559
560fn rsi_value(avg_gain: f64, avg_loss: f64) -> f64 {
561 if avg_loss <= f64::EPSILON {
562 100.0
563 } else {
564 100.0 - 100.0 / (1.0 + avg_gain / avg_loss)
565 }
566}
567
568pub fn stochrsi(values: &[f64], rsi_period: usize, k_period: usize, d_period: usize) -> StochRsi {
569 let rsi = rsi(values, rsi_period);
570 let mut k = vec![None; values.len()];
571 if k_period > 0 {
572 let mut valid_count = 0usize;
573 let mut lows: VecDeque<(usize, f64)> = VecDeque::new();
574 let mut highs: VecDeque<(usize, f64)> = VecDeque::new();
575 for idx in 0..rsi.len() {
576 if let Some(value) = rsi[idx] {
577 valid_count += 1;
578 while lows.back().is_some_and(|(_, prior)| *prior >= value) {
579 lows.pop_back();
580 }
581 lows.push_back((idx, value));
582 while highs.back().is_some_and(|(_, prior)| *prior <= value) {
583 highs.pop_back();
584 }
585 highs.push_back((idx, value));
586 }
587 if idx >= k_period {
588 let expired = idx - k_period;
589 if rsi[expired].is_some() {
590 valid_count -= 1;
591 }
592 while lows.front().is_some_and(|(low_idx, _)| *low_idx <= expired) {
593 lows.pop_front();
594 }
595 while highs
596 .front()
597 .is_some_and(|(high_idx, _)| *high_idx <= expired)
598 {
599 highs.pop_front();
600 }
601 }
602 if idx + 1 >= k_period
603 && valid_count == k_period
604 && let (Some((_, low)), Some((_, high)), Some(current)) =
605 (lows.front(), highs.front(), rsi[idx])
606 {
607 let range = high - low;
608 if range.abs() > f64::EPSILON {
609 k[idx] = Some((current - low) / range * 100.0);
610 }
611 }
612 }
613 }
614 let d = option_mean(&k, d_period);
615 StochRsi { k, d }
616}
617
618fn option_mean(values: &[Option<f64>], period: usize) -> Vec<Option<f64>> {
619 let mut out = vec![None; values.len()];
620 if period == 0 {
621 return out;
622 }
623 let mut sum = 0.0;
624 let mut valid_count = 0usize;
625 for idx in 0..values.len() {
626 if let Some(value) = values[idx] {
627 sum += value;
628 valid_count += 1;
629 }
630 if idx >= period
631 && let Some(value) = values[idx - period]
632 {
633 sum -= value;
634 valid_count -= 1;
635 }
636 if idx + 1 >= period && valid_count == period {
637 out[idx] = Some(sum / period as f64);
638 }
639 }
640 out
641}
642
643pub fn kama(values: &[f64], period: usize) -> Vec<Option<f64>> {
644 let mut out = vec![None; values.len()];
645 if period == 0 || values.len() <= period {
646 return out;
647 }
648 let fast_sc = 2.0 / (2.0 + 1.0);
649 let slow_sc = 2.0 / (30.0 + 1.0);
650 if values.iter().all(|value| value.is_finite()) {
653 let mut current = values[period - 1];
656 let mut volatility = 0.0;
657 for idx in 1..=period {
658 volatility += (values[idx] - values[idx - 1]).abs();
659 }
660 for idx in period..values.len() {
661 if idx > period {
662 volatility += (values[idx] - values[idx - 1]).abs()
663 - (values[idx - period] - values[idx - period - 1]).abs();
664 }
665 let change = (values[idx] - values[idx - period]).abs();
666 let er = if volatility > f64::EPSILON {
667 change / volatility
668 } else {
669 0.0
670 };
671 let smoothing_base = er * (fast_sc - slow_sc) + slow_sc;
672 let smoothing = smoothing_base * smoothing_base;
673 current += smoothing * (values[idx] - current);
674 out[idx] = Some(current);
675 }
676 return out;
677 }
678 let mut current = finite(values[period - 1]);
679 for idx in period..values.len() {
680 let Some(price) = finite(values[idx]) else {
681 continue;
682 };
683 let Some(prior) = finite(values[idx - period]) else {
684 continue;
685 };
686 let change = (price - prior).abs();
687 let mut volatility = 0.0;
688 let mut valid = true;
689 for offset in idx + 1 - period..=idx {
690 let diff = values[offset] - values[offset - 1];
691 if !diff.is_finite() {
692 valid = false;
693 break;
694 }
695 volatility += diff.abs();
696 }
697 if !valid {
698 continue;
699 }
700 let er = if volatility > f64::EPSILON {
701 change / volatility
702 } else {
703 0.0
704 };
705 let smoothing_base = er * (fast_sc - slow_sc) + slow_sc;
706 let smoothing = smoothing_base * smoothing_base;
707 let next = if let Some(prev) = current {
708 prev + smoothing * (price - prev)
709 } else {
710 price
711 };
712 current = Some(next);
713 out[idx] = Some(next);
714 }
715 out
716}
717
718pub fn bollinger_bands(values: &[f64], period: usize, deviations: f64) -> BollingerBands {
719 let mut upper = vec![None; values.len()];
720 let mut middle = vec![None; values.len()];
721 let mut lower = vec![None; values.len()];
722 if period == 0 {
723 return BollingerBands {
724 upper,
725 middle,
726 lower,
727 };
728 }
729 if period <= values.len() && values.iter().all(|value| value.is_finite()) {
730 let inv_period = 1.0 / period as f64;
733 let mut sum = 0.0;
734 let mut sum_sq = 0.0;
735 for value in values.iter().take(period) {
736 sum += *value;
737 sum_sq += value * value;
738 }
739 let emit = |idx: usize,
740 sum: f64,
741 sum_sq: f64,
742 middle: &mut [Option<f64>],
743 upper: &mut [Option<f64>],
744 lower: &mut [Option<f64>]| {
745 let mean = sum * inv_period;
746 let variance = (sum_sq * inv_period - mean * mean).max(0.0);
747 let std = variance.sqrt();
748 middle[idx] = Some(mean);
749 upper[idx] = Some(mean + deviations * std);
750 lower[idx] = Some(mean - deviations * std);
751 };
752 emit(period - 1, sum, sum_sq, &mut middle, &mut upper, &mut lower);
753 for idx in period..values.len() {
754 let old = values[idx - period];
755 let new = values[idx];
756 sum += new - old;
757 sum_sq += new * new - old * old;
758 emit(idx, sum, sum_sq, &mut middle, &mut upper, &mut lower);
759 }
760 return BollingerBands {
761 upper,
762 middle,
763 lower,
764 };
765 }
766 for idx in period - 1..values.len() {
767 let start = idx + 1 - period;
768 let window = &values[start..=idx];
769 if window.iter().any(|value| !value.is_finite()) {
770 continue;
771 }
772 let mean = window.iter().sum::<f64>() / period as f64;
773 let variance = window
774 .iter()
775 .map(|value| {
776 let delta = value - mean;
777 delta * delta
778 })
779 .sum::<f64>()
780 / period as f64;
781 let std = variance.sqrt();
782 middle[idx] = Some(mean);
783 upper[idx] = Some(mean + deviations * std);
784 lower[idx] = Some(mean - deviations * std);
785 }
786 BollingerBands {
787 upper,
788 middle,
789 lower,
790 }
791}
792
793pub fn bop(opens: &[f64], highs: &[f64], lows: &[f64], closes: &[f64]) -> Vec<Option<f64>> {
794 let len = opens
795 .len()
796 .min(highs.len())
797 .min(lows.len())
798 .min(closes.len());
799 let mut out = Vec::with_capacity(len);
800 for idx in 0..len {
801 let range = highs[idx] - lows[idx];
802 out.push(
803 (range.abs() > f64::EPSILON)
804 .then_some((closes[idx] - opens[idx]) / range)
805 .filter(|value| value.is_finite()),
806 );
807 }
808 out
809}
810
811pub fn cmo(values: &[f64], period: usize) -> Vec<Option<f64>> {
812 let mut out = vec![None; values.len()];
813 if period == 0 || values.len() <= period {
814 return out;
815 }
816 let mut gains = 0.0;
819 let mut losses = 0.0;
820 for idx in 1..values.len() {
821 let change = values[idx] - values[idx - 1];
822 if !change.is_finite() {
823 continue;
824 }
825 let gain = change.max(0.0);
826 let loss = (-change).max(0.0);
827 if idx <= period {
828 gains += gain;
829 losses += loss;
830 if idx == period {
831 gains /= period as f64;
832 losses /= period as f64;
833 }
834 } else {
835 gains = (gains * (period as f64 - 1.0) + gain) / period as f64;
836 losses = (losses * (period as f64 - 1.0) + loss) / period as f64;
837 }
838 if idx >= period {
839 let denom = gains + losses;
840 if denom > f64::EPSILON {
841 out[idx] = Some(100.0 * (gains - losses) / denom);
842 }
843 }
844 }
845 out
846}
847
848pub fn ultosc(
849 highs: &[f64],
850 lows: &[f64],
851 closes: &[f64],
852 short: usize,
853 medium: usize,
854 long: usize,
855) -> Vec<Option<f64>> {
856 let len = highs.len().min(lows.len()).min(closes.len());
857 let mut out = vec![None; len];
858 if short == 0 || medium == 0 || long == 0 || len <= long {
859 return out;
860 }
861 if short <= long
862 && medium <= long
863 && highs[..len].iter().all(|value| value.is_finite())
864 && lows[..len].iter().all(|value| value.is_finite())
865 && closes[..len].iter().all(|value| value.is_finite())
866 {
867 let mut buying_pressure = vec![0.0; len];
870 let mut true_range = vec![0.0; len];
871 for idx in 1..len {
872 let prev_close = closes[idx - 1];
873 let true_low = lows[idx].min(prev_close);
874 buying_pressure[idx] = closes[idx] - true_low;
875 true_range[idx] = highs[idx].max(prev_close) - true_low;
876 }
877 let mut short_bp: f64 = buying_pressure[(long + 1 - short)..=long].iter().sum();
878 let mut short_tr: f64 = true_range[(long + 1 - short)..=long].iter().sum();
879 let mut medium_bp: f64 = buying_pressure[(long + 1 - medium)..=long].iter().sum();
880 let mut medium_tr: f64 = true_range[(long + 1 - medium)..=long].iter().sum();
881 let mut long_bp: f64 = buying_pressure[1..=long].iter().sum();
882 let mut long_tr: f64 = true_range[1..=long].iter().sum();
883 let emit = |idx: usize,
884 short_bp: f64,
885 short_tr: f64,
886 medium_bp: f64,
887 medium_tr: f64,
888 long_bp: f64,
889 long_tr: f64,
890 out: &mut [Option<f64>]| {
891 if short_tr.abs() > f64::EPSILON
892 && medium_tr.abs() > f64::EPSILON
893 && long_tr.abs() > f64::EPSILON
894 {
895 let short_avg = short_bp / short_tr;
896 let medium_avg = medium_bp / medium_tr;
897 let long_avg = long_bp / long_tr;
898 out[idx] = Some(100.0 * (4.0 * short_avg + 2.0 * medium_avg + long_avg) / 7.0);
899 }
900 };
901 emit(
902 long, short_bp, short_tr, medium_bp, medium_tr, long_bp, long_tr, &mut out,
903 );
904 for idx in (long + 1)..len {
905 short_bp += buying_pressure[idx] - buying_pressure[idx - short];
906 short_tr += true_range[idx] - true_range[idx - short];
907 medium_bp += buying_pressure[idx] - buying_pressure[idx - medium];
908 medium_tr += true_range[idx] - true_range[idx - medium];
909 long_bp += buying_pressure[idx] - buying_pressure[idx - long];
910 long_tr += true_range[idx] - true_range[idx - long];
911 emit(
912 idx, short_bp, short_tr, medium_bp, medium_tr, long_bp, long_tr, &mut out,
913 );
914 }
915 return out;
916 }
917 let mut buying_pressure = vec![0.0; len];
918 let mut true_range = vec![0.0; len];
919 for idx in 1..len {
920 let prev_close = closes[idx - 1];
921 buying_pressure[idx] = closes[idx] - lows[idx].min(prev_close);
922 true_range[idx] = highs[idx].max(prev_close) - lows[idx].min(prev_close);
923 }
924 for idx in long..len {
925 let avg = |period: usize| -> Option<f64> {
926 let start = idx + 1 - period;
927 let bp = buying_pressure[start..=idx].iter().sum::<f64>();
928 let tr = true_range[start..=idx].iter().sum::<f64>();
929 (tr.abs() > f64::EPSILON).then_some(bp / tr)
930 };
931 if let (Some(short_avg), Some(medium_avg), Some(long_avg)) =
932 (avg(short), avg(medium), avg(long))
933 {
934 out[idx] = Some(100.0 * (4.0 * short_avg + 2.0 * medium_avg + long_avg) / 7.0);
935 }
936 }
937 out
938}
939
940pub fn dema(values: &[f64], period: usize) -> Vec<Option<f64>> {
941 let ema1 = ema(values, period);
942 let ema2 = ema(&option_values(&ema1), period);
943 ema1.iter()
944 .zip(ema2.iter())
945 .map(|(ema1, ema2)| Some(2.0 * (*ema1)? - (*ema2)?))
946 .collect()
947}
948
949pub fn tema(values: &[f64], period: usize) -> Vec<Option<f64>> {
950 let ema1 = ema(values, period);
951 let ema2 = ema(&option_values(&ema1), period);
952 let ema3 = ema(&option_values(&ema2), period);
953 ema1.iter()
954 .zip(ema2.iter().zip(ema3.iter()))
955 .map(|(ema1, (ema2, ema3))| Some(3.0 * (*ema1)? - 3.0 * (*ema2)? + (*ema3)?))
956 .collect()
957}
958
959pub fn t3(values: &[f64], period: usize, vfactor: f64) -> Vec<Option<f64>> {
960 let ema1 = ema(values, period);
961 let ema2 = ema(&option_values(&ema1), period);
962 let ema3 = ema(&option_values(&ema2), period);
963 let ema4 = ema(&option_values(&ema3), period);
964 let ema5 = ema(&option_values(&ema4), period);
965 let ema6 = ema(&option_values(&ema5), period);
966 let v2 = vfactor * vfactor;
967 let v3 = v2 * vfactor;
968 let c1 = -v3;
969 let c2 = 3.0 * v2 + 3.0 * v3;
970 let c3 = -6.0 * v2 - 3.0 * vfactor - 3.0 * v3;
971 let c4 = 1.0 + 3.0 * vfactor + 3.0 * v2 + v3;
972 ema3.iter()
973 .zip(ema4.iter().zip(ema5.iter().zip(ema6.iter())))
974 .map(|(ema3, (ema4, (ema5, ema6)))| {
975 Some(c1 * (*ema6)? + c2 * (*ema5)? + c3 * (*ema4)? + c4 * (*ema3)?)
976 })
977 .collect()
978}
979
980pub fn trix(values: &[f64], period: usize) -> Vec<Option<f64>> {
981 let ema1 = ema(values, period);
982 let ema2 = ema(&option_values(&ema1), period);
983 let ema3 = ema(&option_values(&ema2), period);
984 let mut out = vec![None; values.len()];
985 for idx in 1..ema3.len() {
986 let prior = ema3[idx - 1].filter(|prior| prior.abs() > f64::EPSILON);
987 if let (Some(current), Some(prior)) = (ema3[idx], prior) {
988 out[idx] = Some((current / prior - 1.0) * 100.0);
989 }
990 }
991 out
992}
993
994pub fn price_transforms(
995 opens: &[f64],
996 highs: &[f64],
997 lows: &[f64],
998 closes: &[f64],
999) -> PriceTransforms {
1000 let len = opens
1001 .len()
1002 .min(highs.len())
1003 .min(lows.len())
1004 .min(closes.len());
1005 let mut avgprice = Vec::with_capacity(len);
1006 let mut medprice = Vec::with_capacity(len);
1007 let mut typprice = Vec::with_capacity(len);
1008 let mut wclprice = Vec::with_capacity(len);
1009 for idx in 0..len {
1010 let open = opens[idx];
1011 let high = highs[idx];
1012 let low = lows[idx];
1013 let close = closes[idx];
1014 avgprice.push(finite((open + high + low + close) * 0.25));
1015 medprice.push(finite((high + low) * 0.5));
1016 typprice.push(finite((high + low + close) / 3.0));
1017 wclprice.push(finite((high + low + 2.0 * close) * 0.25));
1018 }
1019 PriceTransforms {
1020 avgprice,
1021 medprice,
1022 typprice,
1023 wclprice,
1024 }
1025}
1026
1027pub fn rolling_sum(values: &[f64], period: usize) -> Vec<Option<f64>> {
1028 let mut out = vec![None; values.len()];
1029 if period == 0 {
1030 return out;
1031 }
1032 let mut sum = 0.0;
1033 let mut count = 0usize;
1034 for idx in 0..values.len() {
1035 if values[idx].is_finite() {
1036 sum += values[idx];
1037 count += 1;
1038 }
1039 if idx >= period && values[idx - period].is_finite() {
1040 sum -= values[idx - period];
1041 count -= 1;
1042 }
1043 if idx + 1 >= period && count == period {
1044 out[idx] = Some(sum);
1045 }
1046 }
1047 out
1048}
1049
1050pub fn directional_movement(highs: &[f64], lows: &[f64]) -> DirectionalMovement {
1051 let len = highs.len().min(lows.len());
1052 let mut plus_dm = vec![None; len];
1053 let mut minus_dm = vec![None; len];
1054 for idx in 1..len {
1055 let high_diff = highs[idx] - highs[idx - 1];
1056 let low_diff = lows[idx - 1] - lows[idx];
1057 if !high_diff.is_finite() || !low_diff.is_finite() {
1058 continue;
1059 }
1060 plus_dm[idx] = Some(if high_diff > low_diff && high_diff > 0.0 {
1061 high_diff
1062 } else {
1063 0.0
1064 });
1065 minus_dm[idx] = Some(if low_diff > high_diff && low_diff > 0.0 {
1066 low_diff
1067 } else {
1068 0.0
1069 });
1070 }
1071 DirectionalMovement { plus_dm, minus_dm }
1072}
1073
1074pub fn price_context(closes: &[f64]) -> PriceContext {
1075 let mut close_vs_ath_pct = vec![None; closes.len()];
1076 let mut close_vs_atl_pct = vec![None; closes.len()];
1077 let mut days_since_ath = vec![None; closes.len()];
1078 let mut days_since_atl = vec![None; closes.len()];
1079 let mut ath = f64::NEG_INFINITY;
1080 let mut atl = f64::INFINITY;
1081 let mut ath_idx = 0usize;
1082 let mut atl_idx = 0usize;
1083 for (idx, close) in closes.iter().copied().enumerate() {
1084 let Some(close) = finite(close) else {
1085 continue;
1086 };
1087 if close >= ath {
1088 ath = close;
1089 ath_idx = idx;
1090 }
1091 if close <= atl {
1092 atl = close;
1093 atl_idx = idx;
1094 }
1095 if ath.abs() > f64::EPSILON {
1096 close_vs_ath_pct[idx] = Some((close / ath - 1.0) * 100.0);
1097 }
1098 if atl.abs() > f64::EPSILON {
1099 close_vs_atl_pct[idx] = Some((close / atl - 1.0) * 100.0);
1100 }
1101 days_since_ath[idx] = Some((idx - ath_idx) as f64);
1102 days_since_atl[idx] = Some((idx - atl_idx) as f64);
1103 }
1104 PriceContext {
1105 close_vs_ath_pct,
1106 close_vs_atl_pct,
1107 days_since_ath,
1108 days_since_atl,
1109 }
1110}
1111
1112pub fn linear_regression(values: &[f64], period: usize) -> LinearRegression {
1113 let mut line = vec![None; values.len()];
1114 let mut slope_out = vec![None; values.len()];
1115 let mut angle = vec![None; values.len()];
1116 let mut intercept_out = vec![None; values.len()];
1117 let mut tsf = vec![None; values.len()];
1118 if period == 0 {
1119 return LinearRegression {
1120 line,
1121 slope: slope_out,
1122 angle,
1123 intercept: intercept_out,
1124 tsf,
1125 };
1126 }
1127 let x_mean = (period - 1) as f64 * 0.5;
1128 let x_var = (0..period)
1129 .map(|idx| {
1130 let delta = idx as f64 - x_mean;
1131 delta * delta
1132 })
1133 .sum::<f64>();
1134 if x_var <= f64::EPSILON {
1135 return LinearRegression {
1136 line,
1137 slope: slope_out,
1138 angle,
1139 intercept: intercept_out,
1140 tsf,
1141 };
1142 }
1143 if period <= values.len() && values.iter().all(|value| value.is_finite()) {
1144 let n = period as f64;
1147 let sum_x = n * (n - 1.0) * 0.5;
1148 let sum_x2 = n * (n - 1.0) * (2.0 * n - 1.0) / 6.0;
1149 let denom = n * sum_x2 - sum_x * sum_x;
1150 if denom <= f64::EPSILON {
1151 return LinearRegression {
1152 line,
1153 slope: slope_out,
1154 angle,
1155 intercept: intercept_out,
1156 tsf,
1157 };
1158 }
1159
1160 let mut sum_y = 0.0;
1161 let mut weighted_sum = 0.0;
1162 for (offset, value) in values.iter().take(period).enumerate() {
1163 sum_y += *value;
1164 weighted_sum += offset as f64 * *value;
1165 }
1166 let mut emit = |idx: usize, sum_y: f64, weighted_sum: f64| {
1167 let slope = (n * weighted_sum - sum_x * sum_y) / denom;
1168 let intercept = (sum_y - slope * sum_x) / n;
1169 slope_out[idx] = Some(slope);
1170 angle[idx] = Some(slope.atan().to_degrees());
1171 intercept_out[idx] = Some(intercept);
1172 line[idx] = Some(intercept + slope * (period - 1) as f64);
1173 tsf[idx] = Some(intercept + slope * period as f64);
1174 };
1175 emit(period - 1, sum_y, weighted_sum);
1176 for idx in period..values.len() {
1177 let old = values[idx - period];
1178 let new = values[idx];
1179 weighted_sum = weighted_sum - sum_y + old + (n - 1.0) * new;
1180 sum_y += new - old;
1181 emit(idx, sum_y, weighted_sum);
1182 }
1183 return LinearRegression {
1184 line,
1185 slope: slope_out,
1186 angle,
1187 intercept: intercept_out,
1188 tsf,
1189 };
1190 }
1191 for idx in period - 1..values.len() {
1192 let start = idx + 1 - period;
1193 let mut y_sum = 0.0;
1194 let mut valid = true;
1195 for value in &values[start..=idx] {
1196 let Some(value) = finite(*value) else {
1197 valid = false;
1198 break;
1199 };
1200 y_sum += value;
1201 }
1202 if !valid {
1203 continue;
1204 }
1205 let y_mean = y_sum / period as f64;
1206 let mut covariance = 0.0;
1207 for (offset, value) in values[start..=idx].iter().enumerate() {
1208 covariance += (offset as f64 - x_mean) * (*value - y_mean);
1209 }
1210 let slope = covariance / x_var;
1211 let intercept = y_mean - slope * x_mean;
1212 slope_out[idx] = Some(slope);
1213 angle[idx] = Some(slope.atan().to_degrees());
1214 intercept_out[idx] = Some(intercept);
1215 line[idx] = Some(intercept + slope * (period - 1) as f64);
1216 tsf[idx] = Some(intercept + slope * period as f64);
1217 }
1218 LinearRegression {
1219 line,
1220 slope: slope_out,
1221 angle,
1222 intercept: intercept_out,
1223 tsf,
1224 }
1225}
1226
1227pub fn sar(highs: &[f64], lows: &[f64], acceleration: f64, maximum: f64) -> Vec<Option<f64>> {
1228 let len = highs.len().min(lows.len());
1229 let mut out = vec![None; len];
1230 if len < 2 || acceleration <= 0.0 || maximum <= 0.0 {
1231 return out;
1232 }
1233 let dm_plus = highs[1] - highs[0];
1237 let dm_minus = lows[0] - lows[1];
1238 let mut long = !(dm_minus > 0.0 && dm_minus > dm_plus);
1239 let (mut sar, mut ep) = if long {
1240 (lows[0], highs[1])
1241 } else {
1242 (highs[0], lows[1])
1243 };
1244 let mut af = acceleration;
1245 out[1] = finite(sar);
1246
1247 for idx in 2..len {
1248 sar += af * (ep - sar);
1249 if long {
1250 sar = sar.min(lows[idx - 1]);
1251 if idx >= 3 {
1252 sar = sar.min(lows[idx - 2]);
1253 }
1254 if lows[idx] < sar {
1255 long = false;
1256 sar = ep;
1257 ep = lows[idx];
1258 af = acceleration;
1259 } else if highs[idx] > ep {
1260 ep = highs[idx];
1261 af = (af + acceleration).min(maximum);
1262 }
1263 } else {
1264 sar = sar.max(highs[idx - 1]);
1265 if idx >= 3 {
1266 sar = sar.max(highs[idx - 2]);
1267 }
1268 if highs[idx] > sar {
1269 long = true;
1270 sar = ep;
1271 ep = highs[idx];
1272 af = acceleration;
1273 } else if lows[idx] < ep {
1274 ep = lows[idx];
1275 af = (af + acceleration).min(maximum);
1276 }
1277 }
1278 out[idx] = finite(sar);
1279 }
1280 out
1281}
1282
1283#[allow(clippy::too_many_arguments)]
1289pub fn sarext(
1290 highs: &[f64],
1291 lows: &[f64],
1292 start_value: f64,
1293 offset_on_reverse: f64,
1294 accel_init_long: f64,
1295 accel_long: f64,
1296 accel_max_long: f64,
1297 accel_init_short: f64,
1298 accel_short: f64,
1299 accel_max_short: f64,
1300) -> Vec<Option<f64>> {
1301 let len = highs.len().min(lows.len());
1302 let mut out = vec![None; len];
1303 if len < 2 {
1304 return out;
1305 }
1306 let mut long = if start_value == 0.0 {
1307 let dm_plus = highs[1] - highs[0];
1308 let dm_minus = lows[0] - lows[1];
1309 !(dm_minus > 0.0 && dm_minus > dm_plus)
1310 } else {
1311 start_value > 0.0
1312 };
1313 let (mut sar, mut ep, mut af) = if long {
1314 let seed = if start_value == 0.0 {
1315 lows[0]
1316 } else {
1317 start_value.abs()
1318 };
1319 (seed, highs[1], accel_init_long)
1320 } else {
1321 let seed = if start_value == 0.0 {
1322 highs[0]
1323 } else {
1324 start_value.abs()
1325 };
1326 (seed, lows[1], accel_init_short)
1327 };
1328 out[1] = finite(if long { sar } else { -sar });
1329
1330 for idx in 2..len {
1331 sar += af * (ep - sar);
1332 if long {
1333 sar = sar.min(lows[idx - 1]);
1334 if idx >= 3 {
1335 sar = sar.min(lows[idx - 2]);
1336 }
1337 if lows[idx] < sar {
1338 long = false;
1339 sar = ep;
1340 if offset_on_reverse != 0.0 {
1341 sar += sar * offset_on_reverse;
1342 }
1343 ep = lows[idx];
1344 af = accel_init_short;
1345 } else if highs[idx] > ep {
1346 ep = highs[idx];
1347 af = (af + accel_long).min(accel_max_long);
1348 }
1349 } else {
1350 sar = sar.max(highs[idx - 1]);
1351 if idx >= 3 {
1352 sar = sar.max(highs[idx - 2]);
1353 }
1354 if highs[idx] > sar {
1355 long = true;
1356 sar = ep;
1357 if offset_on_reverse != 0.0 {
1358 sar -= sar * offset_on_reverse;
1359 }
1360 ep = highs[idx];
1361 af = accel_init_long;
1362 } else if lows[idx] < ep {
1363 ep = lows[idx];
1364 af = (af + accel_short).min(accel_max_short);
1365 }
1366 }
1367 out[idx] = finite(if long { sar } else { -sar });
1368 }
1369 out
1370}
1371
1372pub fn ad(highs: &[f64], lows: &[f64], closes: &[f64], volumes: &[f64]) -> Vec<Option<f64>> {
1373 let mut out = vec![None; closes.len()];
1374 let mut current = 0.0;
1375 for idx in 0..closes.len() {
1376 let high = highs[idx];
1377 let low = lows[idx];
1378 let close = closes[idx];
1379 let volume = volumes[idx];
1380 let range = high - low;
1381 if !range.is_finite() || range.abs() <= f64::EPSILON || !volume.is_finite() {
1382 out[idx] = Some(current);
1383 continue;
1384 }
1385 let multiplier = ((close - low) - (high - close)) / range;
1386 if multiplier.is_finite() {
1387 current += multiplier * volume;
1388 }
1389 out[idx] = Some(current);
1390 }
1391 out
1392}
1393
1394pub fn adosc(
1395 highs: &[f64],
1396 lows: &[f64],
1397 closes: &[f64],
1398 volumes: &[f64],
1399 fast: usize,
1400 slow: usize,
1401) -> Vec<Option<f64>> {
1402 let mut ad_line = vec![0.0; closes.len()];
1403 let mut current = 0.0;
1404 for idx in 0..closes.len() {
1405 let high = highs[idx];
1406 let low = lows[idx];
1407 let close = closes[idx];
1408 let volume = volumes[idx];
1409 let range = high - low;
1410 if range.is_finite() && range.abs() > f64::EPSILON && volume.is_finite() {
1411 let multiplier = ((close - low) - (high - close)) / range;
1412 if multiplier.is_finite() {
1413 current += multiplier * volume;
1414 }
1415 }
1416 ad_line[idx] = current;
1417 }
1418 let len = ad_line.len();
1419 let mut out = vec![None; len];
1420 if fast == 0 || slow == 0 || len == 0 {
1421 return out;
1422 }
1423 let fast_k = 2.0 / (fast as f64 + 1.0);
1426 let slow_k = 2.0 / (slow as f64 + 1.0);
1427 let mut fast_ema = ad_line[0];
1428 let mut slow_ema = ad_line[0];
1429 let first_out = slow - 1;
1430 for idx in 1..len {
1431 let value = ad_line[idx];
1432 fast_ema = fast_k * value + (1.0 - fast_k) * fast_ema;
1433 slow_ema = slow_k * value + (1.0 - slow_k) * slow_ema;
1434 if idx >= first_out {
1435 out[idx] = Some(fast_ema - slow_ema);
1436 }
1437 }
1438 out
1439}
1440
1441pub fn adx_family(highs: &[f64], lows: &[f64], closes: &[f64], period: usize) -> AdxFamily {
1442 let len = highs.len().min(lows.len()).min(closes.len());
1443 let mut plus_di = vec![None; len];
1444 let mut minus_di = vec![None; len];
1445 let mut dx = vec![None; len];
1446 let mut adx = vec![None; len];
1447 let mut adxr = vec![None; len];
1448 if period == 0 || len <= period {
1449 return AdxFamily {
1450 adx,
1451 adxr,
1452 plus_di,
1453 minus_di,
1454 dx,
1455 };
1456 }
1457
1458 let mut tr = vec![0.0; len];
1459 let mut plus_dm = vec![0.0; len];
1460 let mut minus_dm = vec![0.0; len];
1461 for idx in 1..len {
1462 let high_diff = highs[idx] - highs[idx - 1];
1463 let low_diff = lows[idx - 1] - lows[idx];
1464 plus_dm[idx] = if high_diff > low_diff && high_diff > 0.0 {
1465 high_diff
1466 } else {
1467 0.0
1468 };
1469 minus_dm[idx] = if low_diff > high_diff && low_diff > 0.0 {
1470 low_diff
1471 } else {
1472 0.0
1473 };
1474 tr[idx] = (highs[idx] - lows[idx])
1475 .max((highs[idx] - closes[idx - 1]).abs())
1476 .max((lows[idx] - closes[idx - 1]).abs());
1477 }
1478
1479 let mut smooth_tr = tr[1..period].iter().sum::<f64>();
1484 let mut smooth_plus = plus_dm[1..period].iter().sum::<f64>();
1485 let mut smooth_minus = minus_dm[1..period].iter().sum::<f64>();
1486 for idx in period..len {
1487 smooth_tr = smooth_tr - smooth_tr / period as f64 + tr[idx];
1488 smooth_plus = smooth_plus - smooth_plus / period as f64 + plus_dm[idx];
1489 smooth_minus = smooth_minus - smooth_minus / period as f64 + minus_dm[idx];
1490 if smooth_tr > f64::EPSILON {
1491 let plus = 100.0 * smooth_plus / smooth_tr;
1492 let minus = 100.0 * smooth_minus / smooth_tr;
1493 plus_di[idx] = Some(plus);
1494 minus_di[idx] = Some(minus);
1495 let sum = plus + minus;
1496 dx[idx] = if sum > f64::EPSILON {
1497 Some(100.0 * (plus - minus).abs() / sum)
1498 } else {
1499 Some(0.0)
1500 };
1501 }
1502 }
1503
1504 let mut warm_dx = Vec::with_capacity(period);
1505 let mut smooth_adx = None;
1506 for idx in period..len {
1507 let Some(current_dx) = dx[idx] else {
1508 continue;
1509 };
1510 if let Some(prev) = smooth_adx {
1511 let next = (prev * (period as f64 - 1.0) + current_dx) / period as f64;
1512 smooth_adx = Some(next);
1513 adx[idx] = Some(next);
1514 } else {
1515 warm_dx.push(current_dx);
1516 if warm_dx.len() == period {
1517 let next = warm_dx.iter().sum::<f64>() / period as f64;
1518 smooth_adx = Some(next);
1519 adx[idx] = Some(next);
1520 }
1521 }
1522 }
1523
1524 let adxr_lag = period - 1;
1527 for idx in adxr_lag..len {
1528 if let (Some(current), Some(prior)) = (adx[idx], adx[idx - adxr_lag]) {
1529 adxr[idx] = Some((current + prior) * 0.5);
1530 }
1531 }
1532
1533 AdxFamily {
1534 adx,
1535 adxr,
1536 plus_di,
1537 minus_di,
1538 dx,
1539 }
1540}
1541
1542pub fn aroon(highs: &[f64], lows: &[f64], period: usize) -> Aroon {
1543 let len = highs.len().min(lows.len());
1544 let mut up = vec![None; len];
1545 let mut down = vec![None; len];
1546 let mut oscillator = vec![None; len];
1547 if period == 0 {
1548 return Aroon {
1549 up,
1550 down,
1551 oscillator,
1552 };
1553 }
1554 if period < len
1555 && highs[..len].iter().all(|value| value.is_finite())
1556 && lows[..len].iter().all(|value| value.is_finite())
1557 {
1558 let scale = 100.0 / period as f64;
1561 let window = period + 1;
1562 let mut high_value = highs[0];
1563 let mut high_idx = 0usize;
1564 let mut low_value = lows[0];
1565 let mut low_idx = 0usize;
1566 for idx in 1..window {
1567 if highs[idx] >= high_value {
1568 high_value = highs[idx];
1569 high_idx = idx;
1570 }
1571 if lows[idx] <= low_value {
1572 low_value = lows[idx];
1573 low_idx = idx;
1574 }
1575 }
1576 let emit = |idx: usize,
1577 high_idx: usize,
1578 low_idx: usize,
1579 up: &mut [Option<f64>],
1580 down: &mut [Option<f64>],
1581 oscillator: &mut [Option<f64>]| {
1582 let up_value = (period - (idx - high_idx)) as f64 * scale;
1583 let down_value = (period - (idx - low_idx)) as f64 * scale;
1584 up[idx] = Some(up_value);
1585 down[idx] = Some(down_value);
1586 oscillator[idx] = Some(up_value - down_value);
1587 };
1588 emit(
1589 period,
1590 high_idx,
1591 low_idx,
1592 &mut up,
1593 &mut down,
1594 &mut oscillator,
1595 );
1596
1597 let mut trailing_idx = 1usize;
1598 for idx in (period + 1)..len {
1599 if high_idx < trailing_idx {
1600 high_idx = trailing_idx;
1601 high_value = highs[trailing_idx];
1602 for (offset, value) in highs[trailing_idx + 1..=idx].iter().enumerate() {
1603 if *value >= high_value {
1604 high_value = *value;
1605 high_idx = trailing_idx + 1 + offset;
1606 }
1607 }
1608 } else if highs[idx] >= high_value {
1609 high_value = highs[idx];
1610 high_idx = idx;
1611 }
1612
1613 if low_idx < trailing_idx {
1614 low_idx = trailing_idx;
1615 low_value = lows[trailing_idx];
1616 for (offset, value) in lows[trailing_idx + 1..=idx].iter().enumerate() {
1617 if *value <= low_value {
1618 low_value = *value;
1619 low_idx = trailing_idx + 1 + offset;
1620 }
1621 }
1622 } else if lows[idx] <= low_value {
1623 low_value = lows[idx];
1624 low_idx = idx;
1625 }
1626
1627 emit(idx, high_idx, low_idx, &mut up, &mut down, &mut oscillator);
1628 trailing_idx += 1;
1629 }
1630 return Aroon {
1631 up,
1632 down,
1633 oscillator,
1634 };
1635 }
1636 for idx in period..len {
1639 let start = idx - period;
1640 let mut high_value = f64::NEG_INFINITY;
1641 let mut low_value = f64::INFINITY;
1642 let mut high_idx = start;
1643 let mut low_idx = start;
1644 let mut valid = true;
1645 for offset in start..=idx {
1646 let high = highs[offset];
1647 let low = lows[offset];
1648 if !high.is_finite() || !low.is_finite() {
1649 valid = false;
1650 break;
1651 }
1652 if high >= high_value {
1653 high_value = high;
1654 high_idx = offset;
1655 }
1656 if low <= low_value {
1657 low_value = low;
1658 low_idx = offset;
1659 }
1660 }
1661 if valid {
1662 let scale = 100.0 / period as f64;
1663 let up_value = (period - (idx - high_idx)) as f64 * scale;
1664 let down_value = (period - (idx - low_idx)) as f64 * scale;
1665 up[idx] = Some(up_value);
1666 down[idx] = Some(down_value);
1667 oscillator[idx] = Some(up_value - down_value);
1668 }
1669 }
1670 Aroon {
1671 up,
1672 down,
1673 oscillator,
1674 }
1675}
1676
1677const HT_A: f64 = 0.0962;
1690const HT_B: f64 = 0.5769;
1691
1692#[inline]
1693fn ht_rad2deg() -> f64 {
1694 45.0 / (1.0_f64).atan()
1696}
1697
1698struct HtWma {
1700 sub: f64,
1701 sum: f64,
1702 trailing_value: f64,
1703 trailing_idx: usize,
1704}
1705
1706impl HtWma {
1707 #[inline]
1708 fn next(&mut self, new_price: f64, close: &[f64]) -> f64 {
1709 self.sub += new_price;
1710 self.sub -= self.trailing_value;
1711 self.sum += new_price * 4.0;
1712 self.trailing_value = close[self.trailing_idx];
1713 self.trailing_idx += 1;
1714 let smoothed = self.sum * 0.1;
1715 self.sum -= self.sub;
1716 smoothed
1717 }
1718}
1719
1720#[derive(Default)]
1723struct HtChannel {
1724 odd: [f64; 3],
1725 even: [f64; 3],
1726 prev_odd: f64,
1727 prev_even: f64,
1728 prev_in_odd: f64,
1729 prev_in_even: f64,
1730}
1731
1732impl HtChannel {
1733 #[inline]
1734 fn transform(&mut self, input: f64, adjusted_prev_period: f64, idx: usize, even: bool) -> f64 {
1735 let temp = HT_A * input;
1736 let mut value;
1737 if even {
1738 value = -self.even[idx];
1739 self.even[idx] = temp;
1740 value += temp;
1741 value -= self.prev_even;
1742 self.prev_even = HT_B * self.prev_in_even;
1743 value += self.prev_even;
1744 self.prev_in_even = input;
1745 } else {
1746 value = -self.odd[idx];
1747 self.odd[idx] = temp;
1748 value += temp;
1749 value -= self.prev_odd;
1750 self.prev_odd = HT_B * self.prev_in_odd;
1751 value += self.prev_odd;
1752 self.prev_in_odd = input;
1753 }
1754 value * adjusted_prev_period
1755 }
1756}
1757
1758struct Ht32 {
1759 dcperiod: Vec<Option<f64>>,
1760 inphase: Vec<Option<f64>>,
1761 quadrature: Vec<Option<f64>>,
1762 mama: Vec<Option<f64>>,
1763 fama: Vec<Option<f64>>,
1764}
1765
1766fn ht32(close: &[f64], fast_limit: f64, slow_limit: f64) -> Ht32 {
1769 let n = close.len();
1770 let lookback = 32usize;
1771 let mut dcperiod = vec![None; n];
1772 let mut inphase = vec![None; n];
1773 let mut quadrature = vec![None; n];
1774 let mut mama_out = vec![None; n];
1775 let mut fama_out = vec![None; n];
1776 if n <= lookback {
1777 return Ht32 {
1778 dcperiod,
1779 inphase,
1780 quadrature,
1781 mama: mama_out,
1782 fama: fama_out,
1783 };
1784 }
1785 let rad2deg = ht_rad2deg();
1786 let start_idx = lookback;
1787 let end_idx = n - 1;
1788
1789 let mut today = 0usize; let mut t = close[today];
1791 today += 1;
1792 let mut sub = t;
1793 let mut sum = t;
1794 t = close[today];
1795 today += 1;
1796 sub += t;
1797 sum += t * 2.0;
1798 t = close[today];
1799 today += 1;
1800 sub += t;
1801 sum += t * 3.0;
1802 let mut wma = HtWma {
1803 sub,
1804 sum,
1805 trailing_value: 0.0,
1806 trailing_idx: 0,
1807 };
1808 for _ in 0..9 {
1809 let p = close[today];
1810 today += 1;
1811 wma.next(p, close);
1812 }
1813
1814 let mut hilbert_idx = 0usize;
1815 let mut detrender = HtChannel::default();
1816 let mut q1c = HtChannel::default();
1817 let mut jic = HtChannel::default();
1818 let mut jqc = HtChannel::default();
1819 let mut period = 0.0;
1820 let mut smooth_period = 0.0;
1821 let mut prev_i2 = 0.0;
1822 let mut prev_q2 = 0.0;
1823 let mut re = 0.0;
1824 let mut im = 0.0;
1825 let mut i1_odd_prev3 = 0.0;
1826 let mut i1_odd_prev2 = 0.0;
1827 let mut i1_even_prev3 = 0.0;
1828 let mut i1_even_prev2 = 0.0;
1829 let mut mama = 0.0;
1830 let mut fama = 0.0;
1831 let mut prev_phase = 0.0;
1832
1833 while today <= end_idx {
1834 let adjusted_prev_period = 0.075 * period + 0.54;
1835 let today_value = close[today];
1836 let smoothed = wma.next(today_value, close);
1837
1838 let q1v;
1839 let i1;
1840 let q2;
1841 let i2;
1842 if today % 2 == 0 {
1843 let det = detrender.transform(smoothed, adjusted_prev_period, hilbert_idx, true);
1844 let q1n = q1c.transform(det, adjusted_prev_period, hilbert_idx, true);
1845 let jin = jic.transform(i1_even_prev3, adjusted_prev_period, hilbert_idx, true);
1846 let jqn = jqc.transform(q1n, adjusted_prev_period, hilbert_idx, true);
1847 hilbert_idx += 1;
1848 if hilbert_idx == 3 {
1849 hilbert_idx = 0;
1850 }
1851 q2 = 0.2 * (q1n + jin) + 0.8 * prev_q2;
1852 i2 = 0.2 * (i1_even_prev3 - jqn) + 0.8 * prev_i2;
1853 i1_odd_prev3 = i1_odd_prev2;
1854 i1_odd_prev2 = det;
1855 q1v = q1n;
1856 i1 = i1_even_prev3;
1857 } else {
1858 let det = detrender.transform(smoothed, adjusted_prev_period, hilbert_idx, false);
1859 let q1n = q1c.transform(det, adjusted_prev_period, hilbert_idx, false);
1860 let jin = jic.transform(i1_odd_prev3, adjusted_prev_period, hilbert_idx, false);
1861 let jqn = jqc.transform(q1n, adjusted_prev_period, hilbert_idx, false);
1862 q2 = 0.2 * (q1n + jin) + 0.8 * prev_q2;
1863 i2 = 0.2 * (i1_odd_prev3 - jqn) + 0.8 * prev_i2;
1864 i1_even_prev3 = i1_even_prev2;
1865 i1_even_prev2 = det;
1866 q1v = q1n;
1867 i1 = i1_odd_prev3;
1868 }
1869
1870 let alpha_phase = if i1 != 0.0 {
1872 (q1v / i1).atan() * rad2deg
1873 } else {
1874 0.0
1875 };
1876 let mut delta = prev_phase - alpha_phase;
1877 prev_phase = alpha_phase;
1878 if delta < 1.0 {
1879 delta = 1.0;
1880 }
1881 let alpha = if delta > 1.0 {
1882 (fast_limit / delta).max(slow_limit)
1883 } else {
1884 fast_limit
1885 };
1886 mama = alpha * today_value + (1.0 - alpha) * mama;
1887 let alpha_half = alpha * 0.5;
1888 fama = alpha_half * mama + (1.0 - alpha_half) * fama;
1889
1890 re = 0.2 * ((i2 * prev_i2) + (q2 * prev_q2)) + 0.8 * re;
1892 im = 0.2 * ((i2 * prev_q2) - (q2 * prev_i2)) + 0.8 * im;
1893 prev_q2 = q2;
1894 prev_i2 = i2;
1895 let prev_period = period;
1896 if im != 0.0 && re != 0.0 {
1897 period = 360.0 / ((im / re).atan() * rad2deg);
1898 }
1899 let hi = 1.5 * prev_period;
1900 if period > hi {
1901 period = hi;
1902 }
1903 let lo = 0.67 * prev_period;
1904 if period < lo {
1905 period = lo;
1906 }
1907 period = period.clamp(6.0, 50.0);
1908 period = 0.2 * period + 0.8 * prev_period;
1909 smooth_period = 0.33 * period + 0.67 * smooth_period;
1910
1911 if today >= start_idx {
1912 dcperiod[today] = Some(smooth_period);
1913 inphase[today] = Some(i1);
1914 quadrature[today] = Some(q1v);
1915 mama_out[today] = Some(mama);
1916 fama_out[today] = Some(fama);
1917 }
1918 today += 1;
1919 }
1920
1921 Ht32 {
1922 dcperiod,
1923 inphase,
1924 quadrature,
1925 mama: mama_out,
1926 fama: fama_out,
1927 }
1928}
1929
1930struct Ht63 {
1931 dcphase: Vec<Option<f64>>,
1932 sine: Vec<Option<f64>>,
1933 leadsine: Vec<Option<f64>>,
1934 trendline: Vec<Option<f64>>,
1935 trendmode: Vec<Option<f64>>,
1936}
1937
1938fn ht63(close: &[f64]) -> Ht63 {
1940 const SMOOTH_PRICE_SIZE: usize = 50;
1941 let n = close.len();
1942 let lookback = 63usize;
1943 let mut dcphase = vec![None; n];
1944 let mut sine = vec![None; n];
1945 let mut leadsine = vec![None; n];
1946 let mut trendline = vec![None; n];
1947 let mut trendmode = vec![Some(0.0); n];
1949 if n <= lookback {
1950 return Ht63 {
1951 dcphase,
1952 sine,
1953 leadsine,
1954 trendline,
1955 trendmode,
1956 };
1957 }
1958 let rad2deg = ht_rad2deg();
1959 let deg2rad = 1.0 / rad2deg;
1960 let const_deg2rad_by360 = (1.0_f64).atan() * 8.0; let start_idx = lookback;
1962 let end_idx = n - 1;
1963
1964 let mut today = 0usize;
1965 let mut t = close[today];
1966 today += 1;
1967 let mut sub = t;
1968 let mut sum = t;
1969 t = close[today];
1970 today += 1;
1971 sub += t;
1972 sum += t * 2.0;
1973 t = close[today];
1974 today += 1;
1975 sub += t;
1976 sum += t * 3.0;
1977 let mut wma = HtWma {
1978 sub,
1979 sum,
1980 trailing_value: 0.0,
1981 trailing_idx: 0,
1982 };
1983 for _ in 0..34 {
1984 let p = close[today];
1985 today += 1;
1986 wma.next(p, close);
1987 }
1988
1989 let mut hilbert_idx = 0usize;
1990 let mut detrender = HtChannel::default();
1991 let mut q1c = HtChannel::default();
1992 let mut jic = HtChannel::default();
1993 let mut jqc = HtChannel::default();
1994 let mut period = 0.0;
1995 let mut smooth_period = 0.0;
1996 let mut prev_i2 = 0.0;
1997 let mut prev_q2 = 0.0;
1998 let mut re = 0.0;
1999 let mut im = 0.0;
2000 let mut i1_odd_prev3 = 0.0;
2001 let mut i1_odd_prev2 = 0.0;
2002 let mut i1_even_prev3 = 0.0;
2003 let mut i1_even_prev2 = 0.0;
2004
2005 let mut smooth_price = [0.0f64; SMOOTH_PRICE_SIZE];
2006 let mut smooth_price_idx = 0usize;
2007 let mut dc_phase = 0.0;
2008 let mut sine_val = 0.0;
2009 let mut lead_sine_val = 0.0;
2010 let mut i_trend1 = 0.0;
2011 let mut i_trend2 = 0.0;
2012 let mut i_trend3 = 0.0;
2013 let mut days_in_trend = 0i32;
2014
2015 while today <= end_idx {
2016 let adjusted_prev_period = 0.075 * period + 0.54;
2017 let today_value = close[today];
2018 let smoothed = wma.next(today_value, close);
2019 smooth_price[smooth_price_idx] = smoothed;
2020
2021 let q2;
2022 let i2;
2023 if today % 2 == 0 {
2024 let det = detrender.transform(smoothed, adjusted_prev_period, hilbert_idx, true);
2025 let q1n = q1c.transform(det, adjusted_prev_period, hilbert_idx, true);
2026 let jin = jic.transform(i1_even_prev3, adjusted_prev_period, hilbert_idx, true);
2027 let jqn = jqc.transform(q1n, adjusted_prev_period, hilbert_idx, true);
2028 hilbert_idx += 1;
2029 if hilbert_idx == 3 {
2030 hilbert_idx = 0;
2031 }
2032 q2 = 0.2 * (q1n + jin) + 0.8 * prev_q2;
2033 i2 = 0.2 * (i1_even_prev3 - jqn) + 0.8 * prev_i2;
2034 i1_odd_prev3 = i1_odd_prev2;
2035 i1_odd_prev2 = det;
2036 } else {
2037 let det = detrender.transform(smoothed, adjusted_prev_period, hilbert_idx, false);
2038 let q1n = q1c.transform(det, adjusted_prev_period, hilbert_idx, false);
2039 let jin = jic.transform(i1_odd_prev3, adjusted_prev_period, hilbert_idx, false);
2040 let jqn = jqc.transform(q1n, adjusted_prev_period, hilbert_idx, false);
2041 q2 = 0.2 * (q1n + jin) + 0.8 * prev_q2;
2042 i2 = 0.2 * (i1_odd_prev3 - jqn) + 0.8 * prev_i2;
2043 i1_even_prev3 = i1_even_prev2;
2044 i1_even_prev2 = det;
2045 }
2046
2047 re = 0.2 * ((i2 * prev_i2) + (q2 * prev_q2)) + 0.8 * re;
2048 im = 0.2 * ((i2 * prev_q2) - (q2 * prev_i2)) + 0.8 * im;
2049 prev_q2 = q2;
2050 prev_i2 = i2;
2051 let prev_period = period;
2052 if im != 0.0 && re != 0.0 {
2053 period = 360.0 / ((im / re).atan() * rad2deg);
2054 }
2055 let hi = 1.5 * prev_period;
2056 if period > hi {
2057 period = hi;
2058 }
2059 let lo = 0.67 * prev_period;
2060 if period < lo {
2061 period = lo;
2062 }
2063 period = period.clamp(6.0, 50.0);
2064 period = 0.2 * period + 0.8 * prev_period;
2065 smooth_period = 0.33 * period + 0.67 * smooth_period;
2066
2067 let prev_dc_phase = dc_phase;
2068
2069 let dc_period_int = (smooth_period + 0.5) as i32;
2071 let mut real_part = 0.0;
2072 let mut imag_part = 0.0;
2073 let mut idx = smooth_price_idx;
2074 for i in 0..dc_period_int {
2075 let angle = (i as f64 * const_deg2rad_by360) / dc_period_int as f64;
2076 let value = smooth_price[idx];
2077 real_part += angle.sin() * value;
2078 imag_part += angle.cos() * value;
2079 if idx == 0 {
2080 idx = SMOOTH_PRICE_SIZE - 1;
2081 } else {
2082 idx -= 1;
2083 }
2084 }
2085 let abs_imag = imag_part.abs();
2086 if abs_imag > 0.0 {
2087 dc_phase = (real_part / imag_part).atan() * rad2deg;
2088 } else if abs_imag <= 0.01 {
2089 if real_part < 0.0 {
2090 dc_phase -= 90.0;
2091 } else if real_part > 0.0 {
2092 dc_phase += 90.0;
2093 }
2094 }
2095 dc_phase += 90.0;
2096 dc_phase += 360.0 / smooth_period;
2097 if imag_part < 0.0 {
2098 dc_phase += 180.0;
2099 }
2100 if dc_phase > 315.0 {
2101 dc_phase -= 360.0;
2102 }
2103
2104 let prev_sine = sine_val;
2105 let prev_lead_sine = lead_sine_val;
2106 sine_val = (dc_phase * deg2rad).sin();
2107 lead_sine_val = ((dc_phase + 45.0) * deg2rad).sin();
2108
2109 let dc_period_int2 = (smooth_period + 0.5) as i32;
2112 let mut sum_close = 0.0;
2113 let mut k = today as i64;
2114 for _ in 0..dc_period_int2 {
2115 if k < 0 {
2116 break;
2117 }
2118 sum_close += close[k as usize];
2119 k -= 1;
2120 }
2121 if dc_period_int2 > 0 {
2122 sum_close /= dc_period_int2 as f64;
2123 }
2124 let trendline_val = (4.0 * sum_close + 3.0 * i_trend1 + 2.0 * i_trend2 + i_trend3) / 10.0;
2125 i_trend3 = i_trend2;
2126 i_trend2 = i_trend1;
2127 i_trend1 = sum_close;
2128
2129 let mut trend = 1i32;
2131 if (sine_val > lead_sine_val && prev_sine <= prev_lead_sine)
2132 || (sine_val < lead_sine_val && prev_sine >= prev_lead_sine)
2133 {
2134 days_in_trend = 0;
2135 trend = 0;
2136 }
2137 days_in_trend += 1;
2138 if (days_in_trend as f64) < 0.5 * smooth_period {
2139 trend = 0;
2140 }
2141 let dphase_delta = dc_phase - prev_dc_phase;
2142 if smooth_period != 0.0
2143 && dphase_delta > 0.67 * 360.0 / smooth_period
2144 && dphase_delta < 1.5 * 360.0 / smooth_period
2145 {
2146 trend = 0;
2147 }
2148 let cur_smooth = smooth_price[smooth_price_idx];
2149 if trendline_val != 0.0 && ((cur_smooth - trendline_val) / trendline_val).abs() >= 0.015 {
2150 trend = 1;
2151 }
2152
2153 if today >= start_idx {
2154 dcphase[today] = Some(dc_phase);
2155 sine[today] = Some(sine_val);
2156 leadsine[today] = Some(lead_sine_val);
2157 trendline[today] = Some(trendline_val);
2158 trendmode[today] = Some(trend as f64);
2159 }
2160
2161 smooth_price_idx += 1;
2162 if smooth_price_idx == SMOOTH_PRICE_SIZE {
2163 smooth_price_idx = 0;
2164 }
2165 today += 1;
2166 }
2167
2168 Ht63 {
2169 dcphase,
2170 sine,
2171 leadsine,
2172 trendline,
2173 trendmode,
2174 }
2175}
2176
2177pub fn ht_dcperiod(close: &[f64]) -> Vec<Option<f64>> {
2179 ht32(close, 0.5, 0.05).dcperiod
2180}
2181
2182pub fn ht_phasor(close: &[f64]) -> (Vec<Option<f64>>, Vec<Option<f64>>) {
2184 let out = ht32(close, 0.5, 0.05);
2185 (out.inphase, out.quadrature)
2186}
2187
2188pub fn mama(
2190 close: &[f64],
2191 fast_limit: f64,
2192 slow_limit: f64,
2193) -> (Vec<Option<f64>>, Vec<Option<f64>>) {
2194 let out = ht32(close, fast_limit, slow_limit);
2195 (out.mama, out.fama)
2196}
2197
2198pub fn ht_dcphase(close: &[f64]) -> Vec<Option<f64>> {
2200 ht63(close).dcphase
2201}
2202
2203pub fn ht_sine(close: &[f64]) -> (Vec<Option<f64>>, Vec<Option<f64>>) {
2205 let out = ht63(close);
2206 (out.sine, out.leadsine)
2207}
2208
2209pub fn ht_trendline(close: &[f64]) -> Vec<Option<f64>> {
2211 ht63(close).trendline
2212}
2213
2214pub fn ht_trendmode(close: &[f64]) -> Vec<Option<f64>> {
2216 ht63(close).trendmode
2217}
2218
2219#[cfg(test)]
2220mod tests {
2221 use super::*;
2222
2223 fn assert_close(actual: Option<f64>, expected: f64) {
2224 let actual = actual.expect("expected Some value");
2225 assert!(
2226 (actual - expected).abs() < 1e-9,
2227 "actual {actual} expected {expected}"
2228 );
2229 }
2230
2231 #[test]
2232 fn macd_hist_is_difference_between_macd_and_signal() {
2233 let values = (1..40).map(|value| value as f64).collect::<Vec<_>>();
2234 let out = macd(&values, 3, 6, 3);
2235 let idx = out.hist.iter().position(Option::is_some).unwrap();
2236 assert_close(
2237 out.hist[idx],
2238 out.macd[idx].unwrap() - out.signal[idx].unwrap(),
2239 );
2240 let fixed = macdfix(&values, 9);
2241 assert!(fixed.hist.iter().any(Option::is_some));
2242 }
2243
2244 #[test]
2245 fn dema_and_tema_warm_after_nested_emas() {
2246 let values = (1..30).map(|value| value as f64).collect::<Vec<_>>();
2247 assert!(dema(&values, 5).iter().any(Option::is_some));
2248 assert!(tema(&values, 5).iter().any(Option::is_some));
2249 assert!(t3(&values, 5, 0.7).iter().any(Option::is_some));
2250 assert!(trix(&values, 5).iter().any(Option::is_some));
2251 assert!(kama(&values, 5).iter().any(Option::is_some));
2252 }
2253
2254 #[test]
2255 fn bollinger_bands_match_population_std_window() {
2256 let values = [1.0, 2.0, 3.0];
2257 let out = bollinger_bands(&values, 3, 2.0);
2258 let std = (2.0_f64 / 3.0).sqrt();
2259 assert_close(out.middle[2], 2.0);
2260 assert_close(out.upper[2], 2.0 + 2.0 * std);
2261 assert_close(out.lower[2], 2.0 - 2.0 * std);
2262 }
2263
2264 #[test]
2265 fn price_transform_formulas_match_talib_names() {
2266 let out = price_transforms(&[1.0], &[4.0], &[2.0], &[3.0]);
2267 assert_close(out.avgprice[0], 2.5);
2268 assert_close(out.medprice[0], 3.0);
2269 assert_close(out.typprice[0], 3.0);
2270 assert_close(out.wclprice[0], 3.0);
2271 }
2272
2273 #[test]
2274 fn bop_cmo_and_ultosc_emit_bounded_momentum_values() {
2275 let opens = [9.0, 10.0, 11.0, 10.0, 9.0, 10.0, 11.0, 12.0];
2276 let highs = [10.0, 11.0, 12.0, 11.0, 10.0, 11.0, 12.0, 13.0];
2277 let lows = [8.0, 9.0, 10.0, 9.0, 8.0, 9.0, 10.0, 11.0];
2278 let closes = [10.0, 11.0, 10.0, 9.0, 10.0, 11.0, 12.0, 13.0];
2279 assert_close(bop(&opens, &highs, &lows, &closes)[0], 0.5);
2280 assert_close(cmo(&closes, 3)[3], -100.0 / 3.0);
2281 assert!(
2282 ultosc(&highs, &lows, &closes, 2, 3, 4)[4]
2283 .is_some_and(|value| (0.0..=100.0).contains(&value))
2284 );
2285 }
2286
2287 #[test]
2288 fn rsi_and_stochrsi_warm_then_emit_bounded_values() {
2289 let values = [
2290 10.0, 11.0, 12.0, 11.0, 13.0, 14.0, 13.0, 15.0, 16.0, 15.0, 17.0, 18.0, 19.0, 18.0,
2291 20.0, 21.0,
2292 ];
2293 let rsi_out = rsi(&values, 5);
2294 assert_eq!(rsi_out[4], None);
2295 assert!(
2296 rsi_out
2297 .iter()
2298 .flatten()
2299 .all(|value| (0.0..=100.0).contains(value))
2300 );
2301
2302 let stoch = stochrsi(&values, 5, 5, 3);
2303 assert!(stoch.k.iter().any(Option::is_some));
2304 assert!(stoch.d.iter().any(Option::is_some));
2305 assert!(
2306 stoch
2307 .k
2308 .iter()
2309 .flatten()
2310 .all(|value| (0.0..=100.0).contains(value))
2311 );
2312 assert!(
2313 stoch
2314 .d
2315 .iter()
2316 .flatten()
2317 .all(|value| (0.0..=100.0).contains(value))
2318 );
2319 }
2320
2321 #[test]
2322 fn linear_regression_family_matches_simple_line() {
2323 let values = [2.0, 4.0, 6.0, 8.0, 10.0];
2324 let out = linear_regression(&values, 3);
2325 assert_close(out.intercept[2], 2.0);
2326 assert_close(out.slope[2], 2.0);
2327 assert_close(out.line[2], 6.0);
2328 assert_close(out.tsf[2], 8.0);
2329 assert_close(out.angle[2], 2.0_f64.atan().to_degrees());
2330 }
2331
2332 #[test]
2333 fn rolling_sum_and_sar_have_explicit_warmup() {
2334 let values = [1.0, 2.0, 3.0, 4.0];
2335 assert_eq!(rolling_sum(&values, 3)[1], None);
2336 assert_close(rolling_sum(&values, 3)[2], 6.0);
2337 let highs = [10.0, 11.0, 12.0, 13.0];
2338 let lows = [9.0, 10.0, 11.0, 12.0];
2339 let out = sar(&highs, &lows, 0.02, 0.2);
2340 assert_eq!(out[0], None);
2341 assert!(out.iter().skip(1).all(Option::is_some));
2342 }
2343
2344 #[test]
2345 fn directional_movement_and_price_context_are_point_in_time() {
2346 let highs = [10.0, 12.0, 11.0, 14.0];
2347 let lows = [9.0, 10.0, 8.0, 11.0];
2348 let dm = directional_movement(&highs, &lows);
2349 assert_eq!(dm.plus_dm[0], None);
2350 assert_close(dm.plus_dm[1], 2.0);
2351 assert_close(dm.minus_dm[2], 2.0);
2352
2353 let closes = [10.0, 12.0, 9.0, 15.0, 14.0];
2354 let context = price_context(&closes);
2355 assert_close(context.close_vs_ath_pct[2], -25.0);
2356 assert_close(context.close_vs_atl_pct[3], 66.66666666666666);
2357 assert_close(context.days_since_ath[4], 1.0);
2358 assert_close(context.days_since_atl[4], 2.0);
2359 }
2360
2361 #[test]
2362 fn aroon_detects_recent_high_and_old_low() {
2363 let highs = [1.0, 2.0, 3.0, 4.0, 5.0];
2366 let lows = [1.0, 2.0, 3.0, 4.0, 5.0];
2367 let out = aroon(&highs, &lows, 4);
2368 assert_close(out.up[4], 100.0);
2369 assert_close(out.down[4], 0.0);
2370 assert_close(out.oscillator[4], 100.0);
2371 }
2372
2373 #[test]
2374 fn ad_line_accumulates_close_location_volume() {
2375 let high = [10.0, 10.0];
2376 let low = [0.0, 0.0];
2377 let close = [10.0, 0.0];
2378 let volume = [2.0, 3.0];
2379 let out = ad(&high, &low, &close, &volume);
2380 assert_close(out[0], 2.0);
2381 assert_close(out[1], -1.0);
2382 }
2383
2384 #[test]
2385 fn adx_family_exposes_di_dx_and_adx() {
2386 let highs = [10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0];
2387 let lows = [9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0];
2388 let closes = [9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, 17.5, 18.5];
2389 let out = adx_family(&highs, &lows, &closes, 3);
2390 assert!(out.plus_di.iter().any(Option::is_some));
2391 assert!(out.dx.iter().any(Option::is_some));
2392 assert!(out.adx.iter().any(Option::is_some));
2393 assert!(out.adxr.iter().any(Option::is_some));
2394 }
2395}