1use std::collections::VecDeque;
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12#[cfg_attr(
13 feature = "serde",
14 derive(Serialize, Deserialize),
15 serde(rename_all = "snake_case")
16)]
17pub enum EmaInit {
18 #[default]
21 FirstSample,
22 Sma,
25}
26
27#[derive(Debug, Clone, Copy, Default)]
36pub struct Ema {
37 len: usize,
38 init: EmaInit,
39 state: Option<f64>,
40 seed_sum: f64,
41 seed_count: usize,
42}
43
44impl Ema {
45 pub fn new(len: usize) -> Self {
46 Self {
47 len,
48 init: EmaInit::FirstSample,
49 state: None,
50 seed_sum: 0.0,
51 seed_count: 0,
52 }
53 }
54
55 pub fn with_init(mut self, init: EmaInit) -> Self {
61 self.init = init;
62 self
63 }
64
65 pub fn init(&self) -> EmaInit {
66 self.init
67 }
68
69 pub fn update(&mut self, src: f64) -> Option<f64> {
70 let alpha = 2.0 / (self.len as f64 + 1.0);
71 if let Some(prev) = self.state {
72 let next = alpha * src + (1.0 - alpha) * prev;
73 self.state = Some(next);
74 return Some(next);
75 }
76
77 let seed = match self.init {
78 EmaInit::FirstSample => src,
79 EmaInit::Sma => {
80 self.seed_sum += src;
81 self.seed_count += 1;
82 if self.seed_count < self.len {
83 return None;
84 }
85 self.seed_sum / self.len as f64
86 }
87 };
88 self.state = Some(seed);
89 Some(seed)
90 }
91
92 pub fn warmup_period(&self) -> usize {
94 match self.init {
95 EmaInit::FirstSample => 0,
96 EmaInit::Sma => self.len,
97 }
98 }
99
100 pub fn reset(&mut self) {
101 self.state = None;
102 self.seed_sum = 0.0;
103 self.seed_count = 0;
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct Wma {
110 len: usize,
111 window: VecDeque<f64>,
112}
113
114impl Wma {
115 pub fn new(len: usize) -> Self {
116 Self {
117 len: len.max(1),
118 window: VecDeque::with_capacity(len),
119 }
120 }
121
122 pub fn update(&mut self, src: f64) -> Option<f64> {
123 self.window.push_back(src);
124 if self.window.len() > self.len {
125 self.window.pop_front();
126 }
127 if self.window.len() < self.len {
128 return None;
129 }
130
131 let denom = (self.len * (self.len + 1)) as f64 / 2.0;
132 let mut sum = 0.0;
133 for (i, &val) in self.window.iter().enumerate() {
134 sum += val * (i + 1) as f64;
135 }
136 Some(sum / denom)
137 }
138
139 pub fn reset(&mut self) {
140 self.window.clear();
141 }
142}
143
144#[derive(Debug, Clone)]
146pub struct Rma {
147 len: usize,
148 seed: VecDeque<f64>,
149 state: Option<f64>,
150}
151
152impl Rma {
153 pub fn new(len: usize) -> Self {
154 Self {
155 len,
156 seed: VecDeque::with_capacity(len),
157 state: None,
158 }
159 }
160
161 pub fn update(&mut self, src: f64) -> Option<f64> {
162 if let Some(prev) = self.state {
163 let alpha = 1.0 / self.len as f64;
164 let next = alpha * src + (1.0 - alpha) * prev;
165 self.state = Some(next);
166 return Some(next);
167 }
168 self.seed.push_back(src);
169 if self.seed.len() < self.len {
170 return None;
171 }
172 let sma = self.seed.iter().sum::<f64>() / self.len as f64;
173 self.state = Some(sma);
174 Some(sma)
175 }
176
177 pub fn reset(&mut self) {
178 self.seed.clear();
179 self.state = None;
180 }
181}
182
183#[derive(Debug, Clone)]
185pub struct Sma {
186 len: usize,
187 window: VecDeque<f64>,
188 sum: f64,
189}
190
191impl Sma {
192 pub fn new(len: usize) -> Self {
193 Self {
194 len,
195 window: VecDeque::with_capacity(len),
196 sum: 0.0,
197 }
198 }
199
200 pub fn update(&mut self, src: f64) -> Option<f64> {
201 self.window.push_back(src);
202 self.sum += src;
203 if self.window.len() > self.len {
204 self.sum -= self.window.pop_front().unwrap();
205 }
206 if self.window.len() < self.len {
207 return None;
208 }
209 Some(self.sum / self.len as f64)
210 }
211
212 pub fn reset(&mut self) {
213 self.window.clear();
214 self.sum = 0.0;
215 }
216}
217
218#[derive(Debug, Clone)]
220pub struct ExtremeWindow {
221 len: usize,
222 window: VecDeque<f64>,
223}
224
225impl ExtremeWindow {
226 pub fn new(len: usize) -> Self {
227 Self {
228 len,
229 window: VecDeque::with_capacity(len),
230 }
231 }
232
233 pub fn push(&mut self, value: f64) -> Option<(f64, f64)> {
234 if self.window.len() == self.len {
235 self.window.pop_front();
236 }
237 self.window.push_back(value);
238 if self.window.len() < self.len {
239 return None;
240 }
241 let lowest = self.window.iter().cloned().fold(f64::INFINITY, f64::min);
242 let highest = self
243 .window
244 .iter()
245 .cloned()
246 .fold(f64::NEG_INFINITY, f64::max);
247 Some((lowest, highest))
248 }
249
250 pub fn reset(&mut self) {
251 self.window.clear();
252 }
253}
254
255pub fn crossed_over(prev_a: f64, prev_b: f64, a: f64, b: f64) -> bool {
256 prev_a <= prev_b && a > b
257}
258
259pub fn crossed_under(prev_a: f64, prev_b: f64, a: f64, b: f64) -> bool {
260 prev_a >= prev_b && a < b
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
264#[cfg_attr(
265 feature = "serde",
266 derive(Serialize, Deserialize),
267 serde(rename_all = "snake_case")
268)]
269pub enum SmootherKind {
270 #[default]
271 Ema,
272 Sma,
273 Rma,
274 Alma,
275 Jma,
276 SuperSmoother,
277 Kama,
278}
279
280impl SmootherKind {
281 pub fn build(self, len: usize) -> Box<dyn Smoother> {
286 match self {
287 SmootherKind::Ema => Box::new(Ema::new(len)),
288 SmootherKind::Sma => Box::new(Sma::new(len)),
289 SmootherKind::Rma => Box::new(Rma::new(len)),
290 SmootherKind::Alma => Box::new(Alma::new(len, 0.85, 6.0)),
291 SmootherKind::Jma => Box::new(Jma::new(len, 0.0, 2.0)),
292 SmootherKind::SuperSmoother => Box::new(SuperSmoother::new(len)),
293 SmootherKind::Kama => Box::new(Kama::with_defaults(len)),
294 }
295 }
296}
297
298pub trait Smoother: Send + Sync {
301 fn update(&mut self, src: f64) -> Option<f64>;
303 fn reset(&mut self);
304 fn warmup_period(&self) -> usize {
307 0
308 }
309}
310
311impl Smoother for Ema {
312 fn update(&mut self, src: f64) -> Option<f64> {
313 Ema::update(self, src)
314 }
315 fn reset(&mut self) {
316 Ema::reset(self)
317 }
318 fn warmup_period(&self) -> usize {
319 Ema::warmup_period(self)
320 }
321}
322
323impl Smoother for Sma {
324 fn update(&mut self, src: f64) -> Option<f64> {
325 Sma::update(self, src)
326 }
327 fn reset(&mut self) {
328 Sma::reset(self)
329 }
330 fn warmup_period(&self) -> usize {
331 self.len
332 }
333}
334
335impl Smoother for Rma {
336 fn update(&mut self, src: f64) -> Option<f64> {
337 Rma::update(self, src)
338 }
339 fn reset(&mut self) {
340 Rma::reset(self)
341 }
342 fn warmup_period(&self) -> usize {
343 self.len
344 }
345}
346
347impl Smoother for Wma {
348 fn update(&mut self, src: f64) -> Option<f64> {
349 Wma::update(self, src)
350 }
351 fn reset(&mut self) {
352 Wma::reset(self)
353 }
354 fn warmup_period(&self) -> usize {
355 self.len
356 }
357}
358
359impl Smoother for Alma {
360 fn update(&mut self, src: f64) -> Option<f64> {
361 Alma::update(self, src)
362 }
363 fn reset(&mut self) {
364 Alma::reset(self)
365 }
366 fn warmup_period(&self) -> usize {
367 self.len
368 }
369}
370
371impl Smoother for Jma {
372 fn update(&mut self, src: f64) -> Option<f64> {
373 Some(Jma::update(self, src))
374 }
375 fn reset(&mut self) {
376 Jma::reset(self)
377 }
378}
379
380impl Smoother for SuperSmoother {
381 fn update(&mut self, src: f64) -> Option<f64> {
382 Some(SuperSmoother::update(self, src))
383 }
384 fn reset(&mut self) {
385 SuperSmoother::reset(self)
386 }
387}
388
389impl Smoother for Kama {
390 fn update(&mut self, src: f64) -> Option<f64> {
391 Kama::update(self, src)
392 }
393 fn reset(&mut self) {
394 Kama::reset(self)
395 }
396 fn warmup_period(&self) -> usize {
397 self.period + 1
398 }
399}
400
401pub struct SmootherChain {
408 stages: Vec<Box<dyn Smoother>>,
409}
410
411impl SmootherChain {
412 pub fn new(stages: Vec<Box<dyn Smoother>>) -> Self {
413 Self { stages }
414 }
415
416 pub fn warmup_period(&self) -> usize {
417 self.stages.iter().map(|s| s.warmup_period()).sum()
418 }
419
420 pub fn reset(&mut self) {
421 for stage in &mut self.stages {
422 stage.reset();
423 }
424 }
425
426 pub fn update(&mut self, src: f64) -> Option<f64> {
429 let mut value = src;
430 for stage in &mut self.stages {
431 value = stage.update(value)?;
432 }
433 Some(value)
434 }
435}
436
437impl Smoother for SmootherChain {
441 fn update(&mut self, src: f64) -> Option<f64> {
442 SmootherChain::update(self, src)
443 }
444 fn reset(&mut self) {
445 SmootherChain::reset(self)
446 }
447 fn warmup_period(&self) -> usize {
448 SmootherChain::warmup_period(self)
449 }
450}
451
452#[derive(Debug, Clone)]
454pub struct Alma {
455 len: usize,
456 offset: f64,
457 sigma: f64,
458 window: VecDeque<f64>,
459 weights: Vec<f64>,
460 sum_weights: f64,
461}
462
463impl Alma {
464 pub fn new(len: usize, offset: f64, sigma: f64) -> Self {
465 let len = len.max(1);
466 let m = offset * (len - 1) as f64;
467 let s = (len as f64 / sigma).max(1e-6);
468
469 let mut weights = Vec::with_capacity(len);
470 let mut sum_weights = 0.0;
471 for i in 0..len {
472 let w = (-(i as f64 - m).powi(2) / (2.0 * s * s)).exp();
473 weights.push(w);
474 sum_weights += w;
475 }
476
477 Self {
478 len,
479 offset,
480 sigma,
481 window: VecDeque::with_capacity(len),
482 weights,
483 sum_weights,
484 }
485 }
486
487 pub fn offset(&self) -> f64 {
488 self.offset
489 }
490
491 pub fn sigma(&self) -> f64 {
492 self.sigma
493 }
494
495 pub fn update(&mut self, src: f64) -> Option<f64> {
496 self.window.push_back(src);
497 if self.window.len() > self.len {
498 self.window.pop_front();
499 }
500 if self.window.len() < self.len {
501 return None;
502 }
503
504 let mut weighted_sum = 0.0;
505 for (i, &val) in self.window.iter().enumerate() {
506 weighted_sum += val * self.weights[i];
507 }
508 Some(weighted_sum / self.sum_weights)
509 }
510
511 pub fn reset(&mut self) {
512 self.window.clear();
513 }
514}
515
516#[derive(Debug, Clone)]
518pub struct Jma {
519 len: usize,
520 phase: f64,
521 power: f64,
522 e0: f64,
523 e1: f64,
524 e2: f64,
525 jma: f64,
526 initialized: bool,
527}
528
529impl Jma {
530 pub fn new(len: usize, phase: f64, power: f64) -> Self {
531 Self {
532 len: len.max(1),
533 phase: phase.clamp(-100.0, 100.0),
534 power: power.max(1.0),
535 e0: 0.0,
536 e1: 0.0,
537 e2: 0.0,
538 jma: 0.0,
539 initialized: false,
540 }
541 }
542
543 pub fn phase(&self) -> f64 {
544 self.phase
545 }
546
547 pub fn update(&mut self, src: f64) -> f64 {
548 if !self.initialized {
549 self.e0 = src;
550 self.e1 = 0.0;
551 self.e2 = 0.0;
552 self.jma = src;
553 self.initialized = true;
554 return src;
555 }
556
557 let phase_ratio = self.phase / 100.0 + 1.5;
558 let length_term = 0.45 * (self.len.saturating_sub(1)) as f64;
559 let beta = length_term / (length_term + 2.0);
560 let alpha = beta.powf(self.power);
561 self.e0 = (1.0 - alpha) * src + alpha * self.e0;
562 self.e1 = (src - self.e0) * (1.0 - beta) + beta * self.e1;
563 self.e2 = (self.e0 + phase_ratio * self.e1 - self.jma) * (1.0 - alpha).powi(2)
564 + alpha.powi(2) * self.e2;
565 self.jma += self.e2;
566 self.jma
567 }
568
569 pub fn reset(&mut self) {
570 self.e0 = 0.0;
571 self.e1 = 0.0;
572 self.e2 = 0.0;
573 self.jma = 0.0;
574 self.initialized = false;
575 }
576}
577
578#[derive(Debug, Clone)]
584pub struct SuperSmoother {
585 c1: f64,
586 c2: f64,
587 c3: f64,
588 prev_src: f64,
589 prev1: f64,
590 prev2: f64,
591}
592
593impl SuperSmoother {
594 pub fn new(len: usize) -> Self {
595 let len = len.max(1) as f64;
596 let a1 = (-1.414 * std::f64::consts::PI / len).exp();
597 let b1 = 2.0 * a1 * (1.414 * std::f64::consts::PI / len).cos();
598 let c2 = b1;
599 let c3 = -(a1 * a1);
600 let c1 = 1.0 - c2 - c3;
601 Self {
602 c1,
603 c2,
604 c3,
605 prev_src: 0.0,
606 prev1: 0.0,
607 prev2: 0.0,
608 }
609 }
610
611 pub fn update(&mut self, src: f64) -> f64 {
612 let ss =
613 self.c1 * (src + self.prev_src) / 2.0 + self.c2 * self.prev1 + self.c3 * self.prev2;
614 self.prev2 = self.prev1;
615 self.prev1 = ss;
616 self.prev_src = src;
617 ss
618 }
619
620 pub fn reset(&mut self) {
621 self.prev_src = 0.0;
622 self.prev1 = 0.0;
623 self.prev2 = 0.0;
624 }
625}
626
627#[derive(Debug, Clone)]
634pub struct Kama {
635 period: usize,
636 fast_period: usize,
637 slow_period: usize,
638 window: VecDeque<f64>,
639 state: Option<f64>,
640}
641
642impl Kama {
643 pub fn new(period: usize, fast_period: usize, slow_period: usize) -> Self {
644 let period = period.max(1);
645 Self {
646 period,
647 fast_period,
648 slow_period,
649 window: VecDeque::with_capacity(period + 1),
650 state: None,
651 }
652 }
653
654 pub fn with_defaults(period: usize) -> Self {
657 Self::new(period, 2, 30)
658 }
659
660 pub fn update(&mut self, src: f64) -> Option<f64> {
661 self.window.push_back(src);
662 if self.window.len() > self.period + 1 {
663 self.window.pop_front();
664 }
665 if self.window.len() < self.period + 1 {
666 return None;
667 }
668
669 let change = (self.window.back().unwrap() - self.window.front().unwrap()).abs();
670 let mut volatility = 0.0f64;
671 for pair in self.window.iter().collect::<Vec<_>>().windows(2) {
672 volatility += (*pair[1] - *pair[0]).abs();
673 }
674 let er = if volatility > 0.0 {
675 change / volatility
676 } else {
677 0.0
678 };
679
680 let fast_sc = 2.0 / (self.fast_period as f64 + 1.0);
681 let slow_sc = 2.0 / (self.slow_period as f64 + 1.0);
682 let sc = (er * (fast_sc - slow_sc) + slow_sc).powi(2);
683
684 let next = match self.state {
685 Some(prev) => prev + sc * (src - prev),
686 None => src,
687 };
688 self.state = Some(next);
689 Some(next)
690 }
691
692 pub fn reset(&mut self) {
693 self.window.clear();
694 self.state = None;
695 }
696}
697
698#[cfg(test)]
699mod jma_tests {
700 use super::Jma;
701
702 #[test]
703 fn phase_changes_the_open_jurik_approximation() {
704 let mut leading = Jma::new(7, 100.0, 2.0);
705 let mut lagging = Jma::new(7, -100.0, 2.0);
706 let input = [10.0, 11.0, 13.0, 12.0, 15.0];
707 let leading_value = input.into_iter().map(|v| leading.update(v)).last().unwrap();
708 let lagging_value = input.into_iter().map(|v| lagging.update(v)).last().unwrap();
709 assert!(leading_value > lagging_value);
710 }
711
712 #[test]
713 fn matches_reference_formula_fixture() {
714 let mut jma = Jma::new(3, 0.0, 2.0);
715 let actual: Vec<_> = [1.0, 2.0, 3.0, 4.0]
716 .into_iter()
717 .map(|value| jma.update(value))
718 .collect();
719 let expected = [
720 1.0,
721 1.819_360_773_771_215_6,
722 2.819_354_006_908_239,
723 3.831_322_396_018_062,
724 ];
725 for (actual, expected) in actual.iter().zip(expected) {
726 assert!((actual - expected).abs() < 1e-12, "{actual} != {expected}");
727 }
728 }
729}
730
731#[cfg(test)]
732mod supersmoother_tests {
733 use super::SuperSmoother;
734
735 #[test]
754 fn matches_independently_derived_reference_formula_fixture() {
755 let mut ss = SuperSmoother::new(3);
756 let actual: Vec<_> = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
757 .into_iter()
758 .map(|value| ss.update(value))
759 .collect();
760 let expected = [
761 0.505_413_249_748_865_4,
762 1.536_919_267_057_587,
763 2.563_799_552_420_708,
764 3.563_269_185_823_832_3,
765 4.561_856_630_604_048,
766 5.561_826_276_936_932,
767 ];
768 for (actual, expected) in actual.iter().zip(expected) {
769 assert!((actual - expected).abs() < 1e-12, "{actual} != {expected}");
770 }
771 }
772
773 #[test]
774 fn reset_clears_transient_state() {
775 let mut ss = SuperSmoother::new(5);
776 ss.update(100.0);
777 ss.update(110.0);
778 ss.reset();
779 let mut fresh = SuperSmoother::new(5);
780 assert_eq!(ss.update(50.0), fresh.update(50.0));
781 }
782}
783
784#[cfg(test)]
785mod kama_smoother_tests {
786 use super::Kama;
787
788 #[test]
797 fn matches_already_confirmed_kama_engine_golden_value() {
798 const CLOSES: [f64; 10] = [10.0, 11.0, 12.0, 11.0, 13.0, 14.0, 13.0, 15.0, 16.0, 15.0];
799 let mut kama = Kama::new(5, 2, 30);
800 let mut last = None;
801 for &c in &CLOSES {
802 if let Some(value) = kama.update(c) {
803 last = Some(value);
804 }
805 }
806 let last = last.expect("kama produced no output");
807 assert!(
808 (last - 14.554_043_488_814_198).abs() < 1e-9,
809 "{last} != 14.554043488814198"
810 );
811 }
812
813 #[test]
814 fn warmup_returns_none_until_period_plus_one_samples() {
815 let mut kama = Kama::new(3, 2, 30);
816 assert_eq!(kama.update(1.0), None);
817 assert_eq!(kama.update(2.0), None);
818 assert_eq!(kama.update(3.0), None);
819 assert!(kama.update(4.0).is_some());
820 }
821}
822
823#[cfg(test)]
824mod chain_tests {
825 use super::*;
826
827 #[test]
828 fn test_chain_warmup_is_sum_of_stage_warmups() {
829 let chain =
830 SmootherChain::new(vec![SmootherKind::Sma.build(3), SmootherKind::Rma.build(4)]);
831 assert_eq!(chain.warmup_period(), 3 + 4);
832 }
833
834 #[test]
835 fn test_chain_none_until_every_stage_warm() {
836 let mut chain =
837 SmootherChain::new(vec![SmootherKind::Sma.build(2), SmootherKind::Sma.build(2)]);
838 assert_eq!(chain.update(1.0), None); assert_eq!(chain.update(2.0), None); let value = chain.update(3.0).unwrap();
842 assert!((value - 2.0).abs() < 1e-9);
843 let value = chain.update(4.0).unwrap();
844 assert!((value - 3.0).abs() < 1e-9);
845 }
846
847 #[test]
848 fn test_chain_reset_clears_every_stage() {
849 let mut chain = SmootherChain::new(vec![SmootherKind::Sma.build(2)]);
850 chain.update(1.0);
851 assert!(chain.update(2.0).is_some());
852 chain.reset();
853 assert_eq!(chain.update(5.0), None, "reset stage must re-enter warmup");
854 }
855
856 #[test]
857 fn test_smoother_kind_build_matches_direct_construction() {
858 let mut via_kind = SmootherKind::Ema.build(5);
859 let mut direct = Ema::new(5);
860 for v in [10.0, 11.0, 12.0, 9.0] {
861 assert_eq!(via_kind.update(v), Ema::update(&mut direct, v));
862 }
863 }
864}