1use std::collections::VecDeque;
31
32use crate::error::IndicatorError;
33use crate::types::MacdResult;
34
35pub fn ema(prices: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
40 if period == 0 {
41 return Err(IndicatorError::InvalidParameter {
42 name: "period".into(),
43 value: 0.0,
44 });
45 }
46 if prices.len() < period {
47 return Err(IndicatorError::InsufficientData {
48 required: period,
49 available: prices.len(),
50 });
51 }
52 let mut result = vec![f64::NAN; prices.len()];
53 let alpha = 2.0 / (period as f64 + 1.0);
54 let first_sma: f64 = prices.iter().take(period).sum::<f64>() / period as f64;
55 result[period - 1] = first_sma;
56 for i in period..prices.len() {
57 result[i] = prices[i] * alpha + result[i - 1] * (1.0 - alpha);
58 }
59 Ok(result)
60}
61
62pub fn ema_nan_aware(prices: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
74 if period == 0 {
75 return Err(IndicatorError::InvalidParameter {
76 name: "period".into(),
77 value: 0.0,
78 });
79 }
80 let mut result = vec![f64::NAN; prices.len()];
81 let alpha = 2.0 / (period as f64 + 1.0);
82
83 let Some(start) = prices.iter().position(|v| !v.is_nan()) else {
85 return Ok(result); };
87
88 result[start] = prices[start];
89 for i in (start + 1)..prices.len() {
90 result[i] = if prices[i].is_nan() {
91 f64::NAN
92 } else {
93 prices[i] * alpha + result[i - 1] * (1.0 - alpha)
94 };
95 }
96 Ok(result)
97}
98
99pub fn sma(prices: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
101 if period == 0 {
102 return Err(IndicatorError::InvalidParameter {
103 name: "period".into(),
104 value: 0.0,
105 });
106 }
107 if prices.len() < period {
108 return Err(IndicatorError::InsufficientData {
109 required: period,
110 available: prices.len(),
111 });
112 }
113 let mut result = vec![f64::NAN; prices.len()];
114 for i in (period - 1)..prices.len() {
115 let sum: f64 = prices[(i + 1 - period)..=i].iter().sum();
116 result[i] = sum / period as f64;
117 }
118 Ok(result)
119}
120
121pub fn true_range(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, IndicatorError> {
123 if high.len() != low.len() || high.len() != close.len() {
124 return Err(IndicatorError::InsufficientData {
125 required: high.len(),
126 available: low.len().min(close.len()),
127 });
128 }
129 let mut result = vec![f64::NAN; high.len()];
130 if !high.is_empty() {
131 result[0] = high[0] - low[0];
132 }
133 for i in 1..high.len() {
134 let tr1 = high[i] - low[i];
135 let tr2 = (high[i] - close[i - 1]).abs();
136 let tr3 = (low[i] - close[i - 1]).abs();
137 result[i] = tr1.max(tr2).max(tr3);
138 }
139 Ok(result)
140}
141
142pub fn atr(
148 high: &[f64],
149 low: &[f64],
150 close: &[f64],
151 period: usize,
152) -> Result<Vec<f64>, IndicatorError> {
153 let tr = true_range(high, low, close)?;
154 ema(&tr, period)
155}
156
157pub fn rsi(prices: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
159 if prices.len() < period + 1 {
160 return Err(IndicatorError::InsufficientData {
161 required: period + 1,
162 available: prices.len(),
163 });
164 }
165 let mut result = vec![f64::NAN; prices.len()];
166 let mut gains = vec![0.0; prices.len()];
167 let mut losses = vec![0.0; prices.len()];
168 for i in 1..prices.len() {
169 let change = prices[i] - prices[i - 1];
170 if change > 0.0 {
171 gains[i] = change;
172 } else {
173 losses[i] = -change;
174 }
175 }
176 let avg_gains = ema(&gains, period)?;
177 let avg_losses = ema(&losses, period)?;
178 for i in period..prices.len() {
179 if avg_losses[i] == 0.0 {
180 result[i] = 100.0;
181 } else {
182 let rs = avg_gains[i] / avg_losses[i];
183 result[i] = 100.0 - (100.0 / (1.0 + rs));
184 }
185 }
186 Ok(result)
187}
188
189pub fn macd(
191 prices: &[f64],
192 fast_period: usize,
193 slow_period: usize,
194 signal_period: usize,
195) -> MacdResult {
196 let fast_ema = ema_nan_aware(prices, fast_period)?;
199 let slow_ema = ema_nan_aware(prices, slow_period)?;
200 let mut macd_line = vec![f64::NAN; prices.len()];
201 for i in 0..prices.len() {
202 if !fast_ema[i].is_nan() && !slow_ema[i].is_nan() {
203 macd_line[i] = fast_ema[i] - slow_ema[i];
204 }
205 }
206 let signal_line = ema_nan_aware(&macd_line, signal_period)?;
210 let mut histogram = vec![f64::NAN; prices.len()];
211 for i in 0..prices.len() {
212 if !macd_line[i].is_nan() && !signal_line[i].is_nan() {
213 histogram[i] = macd_line[i] - signal_line[i];
214 }
215 }
216 Ok((macd_line, signal_line, histogram))
217}
218
219#[derive(Debug, Clone)]
228pub struct EMA {
229 period: usize,
230 alpha: f64,
231 value: f64,
232 initialized: bool,
233 warmup: VecDeque<f64>,
234}
235
236impl EMA {
237 pub fn new(period: usize) -> Self {
238 Self {
239 period,
240 alpha: 2.0 / (period as f64 + 1.0),
241 value: 0.0,
242 initialized: false,
243 warmup: VecDeque::with_capacity(period),
244 }
245 }
246
247 pub fn update(&mut self, price: f64) {
248 if !self.initialized {
249 self.warmup.push_back(price);
250 if self.warmup.len() >= self.period {
251 self.value = self.warmup.iter().sum::<f64>() / self.period as f64;
252 self.initialized = true;
253 self.warmup.clear();
254 }
255 } else {
256 self.value = price * self.alpha + self.value * (1.0 - self.alpha);
257 }
258 }
259
260 pub fn value(&self) -> f64 {
261 if self.initialized {
262 self.value
263 } else {
264 f64::NAN
265 }
266 }
267
268 pub fn is_ready(&self) -> bool {
269 self.initialized
270 }
271
272 pub fn reset(&mut self) {
273 self.value = 0.0;
274 self.initialized = false;
275 self.warmup.clear();
276 }
277}
278
279#[derive(Debug, Clone)]
281pub struct ATR {
282 #[allow(dead_code)]
283 period: usize,
284 ema: EMA,
285 prev_close: Option<f64>,
286}
287
288impl ATR {
289 pub fn new(period: usize) -> Self {
290 Self {
291 period,
292 ema: EMA::new(period),
293 prev_close: None,
294 }
295 }
296
297 pub fn update(&mut self, high: f64, low: f64, close: f64) {
298 let tr = if let Some(prev) = self.prev_close {
299 (high - low)
300 .max((high - prev).abs())
301 .max((low - prev).abs())
302 } else {
303 high - low
304 };
305 self.ema.update(tr);
306 self.prev_close = Some(close);
307 }
308
309 pub fn value(&self) -> f64 {
310 self.ema.value()
311 }
312
313 pub fn is_ready(&self) -> bool {
314 self.ema.is_ready()
315 }
316}
317
318#[derive(Debug, Clone)]
325pub struct RSI {
326 period: usize,
327 prev: Option<f64>,
328 avg_gain: f64,
329 avg_loss: f64,
330 seeded: usize,
331 initialized: bool,
332 value: f64,
333}
334
335impl RSI {
336 pub fn new(period: usize) -> Self {
337 Self {
338 period: period.max(1),
339 prev: None,
340 avg_gain: 0.0,
341 avg_loss: 0.0,
342 seeded: 0,
343 initialized: false,
344 value: f64::NAN,
345 }
346 }
347
348 pub fn update(&mut self, price: f64) {
349 let Some(prev) = self.prev else {
350 self.prev = Some(price);
351 return;
352 };
353 let delta = price - prev;
354 let gain = delta.max(0.0);
355 let loss = (-delta).max(0.0);
356 self.prev = Some(price);
357
358 if !self.initialized {
359 self.avg_gain += gain;
361 self.avg_loss += loss;
362 self.seeded += 1;
363 if self.seeded >= self.period {
364 self.avg_gain /= self.period as f64;
365 self.avg_loss /= self.period as f64;
366 self.initialized = true;
367 self.value = rsi_from(self.avg_gain, self.avg_loss);
368 }
369 } else {
370 let w = (self.period - 1) as f64;
371 self.avg_gain = (self.avg_gain * w + gain) / self.period as f64;
372 self.avg_loss = (self.avg_loss * w + loss) / self.period as f64;
373 self.value = rsi_from(self.avg_gain, self.avg_loss);
374 }
375 }
376
377 pub fn value(&self) -> f64 {
378 self.value
379 }
380
381 pub fn is_ready(&self) -> bool {
382 self.initialized
383 }
384
385 pub fn reset(&mut self) {
386 *self = Self::new(self.period);
387 }
388}
389
390#[inline]
393fn rsi_from(avg_gain: f64, avg_loss: f64) -> f64 {
394 if avg_loss == 0.0 {
395 if avg_gain == 0.0 { 50.0 } else { 100.0 }
396 } else {
397 100.0 - 100.0 / (1.0 + avg_gain / avg_loss)
398 }
399}
400
401#[cfg(test)]
402mod rsi_inc_tests {
403 use super::*;
404
405 #[test]
406 fn warmup_then_matches_wilder_batch() {
407 use crate::indicator::Indicator;
408 use crate::momentum::Rsi;
409 use crate::types::Candle;
410 let prices: Vec<f64> = (0..40).map(|i| 100.0 + (i as f64 * 0.4).sin() * 8.0).collect();
411 let candles: Vec<Candle> = prices
412 .iter()
413 .enumerate()
414 .map(|(i, &c)| Candle { time: i as i64, open: c, high: c, low: c, close: c, volume: 1.0 })
415 .collect();
416 let batch = Rsi::with_period(14).calculate(&candles).unwrap();
419 let batch_vals = batch.get("RSI_14").unwrap();
420
421 let mut inc = RSI::new(14);
422 for (i, &p) in prices.iter().enumerate() {
423 inc.update(p);
424 if inc.is_ready() {
425 assert!(
426 (inc.value() - batch_vals[i]).abs() < 1e-9,
427 "i={i}: {} vs {}",
428 inc.value(),
429 batch_vals[i]
430 );
431 } else {
432 assert!(inc.value().is_nan());
433 }
434 }
435 }
436
437 #[test]
438 fn constant_gains_is_100() {
439 let mut inc = RSI::new(5);
440 for i in 0..10 {
441 inc.update(i as f64);
442 }
443 assert!((inc.value() - 100.0).abs() < 1e-9);
444 }
445}
446
447#[derive(Debug, Clone)]
449pub struct StrategyIndicators {
450 pub ema_fast: Vec<f64>,
451 pub ema_slow: Vec<f64>,
452 pub atr: Vec<f64>,
453}
454
455#[derive(Debug, Clone)]
457pub struct IndicatorCalculator {
458 pub fast_ema_period: usize,
459 pub slow_ema_period: usize,
460 pub atr_period: usize,
461}
462
463impl Default for IndicatorCalculator {
464 fn default() -> Self {
465 Self {
466 fast_ema_period: 8,
467 slow_ema_period: 21,
468 atr_period: 14,
469 }
470 }
471}
472
473impl IndicatorCalculator {
474 pub fn new(fast_ema: usize, slow_ema: usize, atr_period: usize) -> Self {
475 Self {
476 fast_ema_period: fast_ema,
477 slow_ema_period: slow_ema,
478 atr_period,
479 }
480 }
481
482 pub fn calculate_all(
483 &self,
484 close: &[f64],
485 high: &[f64],
486 low: &[f64],
487 ) -> Result<StrategyIndicators, IndicatorError> {
488 Ok(StrategyIndicators {
489 ema_fast: ema(close, self.fast_ema_period)?,
490 ema_slow: ema(close, self.slow_ema_period)?,
491 atr: atr(high, low, close, self.atr_period)?,
492 })
493 }
494}
495
496#[derive(Debug, Clone)]
502pub struct IncrementalEma {
503 alpha: f64,
504 state: f64,
505 initialized: bool,
506}
507
508impl IncrementalEma {
509 pub fn new(period: usize) -> Self {
511 Self {
512 alpha: 2.0 / (period as f64 + 1.0),
513 state: 0.0,
514 initialized: false,
515 }
516 }
517
518 pub fn update(&mut self, price: f64) -> Option<f64> {
522 Some(self.step(price))
523 }
524
525 fn step(&mut self, price: f64) -> f64 {
527 if !self.initialized {
528 self.state = price;
529 self.initialized = true;
530 } else {
531 self.state = self.alpha * price + (1.0 - self.alpha) * self.state;
532 }
533 self.state
534 }
535
536 pub fn current(&self) -> Option<f64> {
538 if self.initialized {
539 Some(self.state)
540 } else {
541 None
542 }
543 }
544}
545
546pub struct IncrementalAtr {
551 ema: IncrementalEma,
552 prev_close: Option<f64>,
553}
554
555impl IncrementalAtr {
556 pub fn new(period: usize) -> Self {
558 Self {
559 ema: IncrementalEma::new(period),
560 prev_close: None,
561 }
562 }
563
564 pub fn update(&mut self, high: f64, low: f64, close: f64) -> Option<f64> {
566 let tr = if let Some(prev) = self.prev_close {
567 let tr1 = high - low;
568 let tr2 = (high - prev).abs();
569 let tr3 = (low - prev).abs();
570 tr1.max(tr2).max(tr3)
571 } else {
572 high - low
573 };
574
575 self.prev_close = Some(close);
576 Some(self.ema.step(tr))
577 }
578}
579
580#[derive(Debug, Clone)]
589pub struct IncrementalRsi {
590 gain_ema: IncrementalEma,
591 loss_ema: IncrementalEma,
592 prev_price: Option<f64>,
593}
594
595impl IncrementalRsi {
596 pub fn new(period: usize) -> Self {
598 Self {
599 gain_ema: IncrementalEma::new(period),
600 loss_ema: IncrementalEma::new(period),
601 prev_price: None,
602 }
603 }
604
605 pub fn update(&mut self, price: f64) -> Option<f64> {
608 let prev = self.prev_price.replace(price)?;
609 let change = price - prev;
610 let (gain, loss) = if change > 0.0 {
611 (change, 0.0)
612 } else {
613 (0.0, -change)
614 };
615 let avg_gain = self.gain_ema.step(gain);
616 let avg_loss = self.loss_ema.step(loss);
617 let rsi = if avg_loss == 0.0 {
618 100.0
619 } else {
620 let rs = avg_gain / avg_loss;
621 100.0 - 100.0 / (1.0 + rs)
622 };
623 Some(rsi)
624 }
625}
626
627#[derive(Debug, Clone)]
635pub struct IncrementalMacd {
636 fast: IncrementalEma,
637 slow: IncrementalEma,
638 signal: IncrementalEma,
639}
640
641impl IncrementalMacd {
642 pub fn new(fast_period: usize, slow_period: usize, signal_period: usize) -> Self {
644 Self {
645 fast: IncrementalEma::new(fast_period),
646 slow: IncrementalEma::new(slow_period),
647 signal: IncrementalEma::new(signal_period),
648 }
649 }
650
651 pub fn update(&mut self, price: f64) -> Option<(f64, f64, f64)> {
655 let macd = self.fast.step(price) - self.slow.step(price);
656 let signal = self.signal.step(macd);
657 Some((macd, signal, macd - signal))
658 }
659}
660
661#[derive(Debug, Clone, Copy, PartialEq)]
663pub struct BollingerBandsValue {
664 pub middle: f64,
666 pub upper: f64,
668 pub lower: f64,
670 pub bandwidth: f64,
672 pub percent_b: f64,
674}
675
676#[derive(Debug, Clone)]
685pub struct IncrementalBollinger {
686 window: VecDeque<f64>,
687 period: usize,
688 std_mult: f64,
689}
690
691impl IncrementalBollinger {
692 pub fn new(period: usize, std_mult: f64) -> Self {
695 Self {
696 window: VecDeque::with_capacity(period.max(1)),
697 period,
698 std_mult,
699 }
700 }
701
702 pub fn update(&mut self, price: f64) -> Option<BollingerBandsValue> {
705 self.window.push_back(price);
706 if self.window.len() > self.period {
707 self.window.pop_front();
708 }
709 if self.window.len() < self.period || self.period < 2 {
710 return None;
711 }
712
713 let mean: f64 = self.window.iter().sum::<f64>() / self.period as f64;
714 let var: f64 =
716 self.window.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / (self.period - 1) as f64;
717 let std = var.sqrt();
718
719 let upper = mean + self.std_mult * std;
720 let lower = mean - self.std_mult * std;
721 let bandwidth = if mean == 0.0 {
722 f64::NAN
723 } else {
724 (upper - lower) / mean
725 };
726 let band_range = upper - lower;
727 let percent_b = if band_range == 0.0 {
728 f64::NAN
729 } else {
730 (price - lower) / band_range
731 };
732
733 Some(BollingerBandsValue {
734 middle: mean,
735 upper,
736 lower,
737 bandwidth,
738 percent_b,
739 })
740 }
741}
742
743#[cfg(test)]
744mod tests {
745 use super::*;
746
747 #[test]
748 fn test_incremental_bollinger_warmup_then_value() {
749 let mut bb = IncrementalBollinger::new(5, 2.0);
750 for p in [10.0, 11.0, 12.0, 13.0] {
751 assert!(bb.update(p).is_none(), "no value before `period` samples");
752 }
753 assert!(bb.update(14.0).is_some(), "value once the window is full");
754 }
755
756 #[test]
757 fn test_incremental_bollinger_constant_prices_zero_width() {
758 let mut bb = IncrementalBollinger::new(4, 2.0);
759 let mut last = None;
760 for _ in 0..4 {
761 last = bb.update(10.0);
762 }
763 let v = last.unwrap();
764 assert!((v.middle - 10.0).abs() < 1e-12);
765 assert!((v.upper - 10.0).abs() < 1e-12);
766 assert!((v.lower - 10.0).abs() < 1e-12);
767 assert!((v.bandwidth - 0.0).abs() < 1e-12);
768 assert!(v.percent_b.is_nan(), "zero-width band → %b undefined");
769 }
770
771 #[test]
772 fn test_incremental_bollinger_matches_sample_stddev() {
773 let mut bb = IncrementalBollinger::new(8, 2.0);
775 let mut last = None;
776 for p in [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0] {
777 last = bb.update(p);
778 }
779 let v = last.unwrap();
780 let std = (32.0_f64 / 7.0).sqrt();
781 assert!((v.middle - 5.0).abs() < 1e-9);
782 assert!((v.upper - (5.0 + 2.0 * std)).abs() < 1e-9);
783 assert!((v.lower - (5.0 - 2.0 * std)).abs() < 1e-9);
784 }
785
786 #[test]
787 fn test_incremental_rsi_first_sample_is_none() {
788 let mut rsi = IncrementalRsi::new(14);
789 assert_eq!(rsi.update(10.0), None);
790 assert!(rsi.update(11.0).is_some());
791 }
792
793 #[test]
794 fn test_incremental_rsi_all_gains_saturates_at_100() {
795 let mut rsi = IncrementalRsi::new(14);
796 let mut last = None;
797 for p in [10.0, 11.0, 12.0, 13.0, 14.0, 15.0] {
798 last = rsi.update(p);
799 }
800 assert!((last.unwrap() - 100.0).abs() < 1e-9);
802 }
803
804 #[test]
805 fn test_incremental_rsi_stays_in_bounds() {
806 let mut rsi = IncrementalRsi::new(5);
807 let prices = [44.0, 44.3, 44.1, 44.2, 43.6, 44.3, 44.8, 45.0, 44.7, 44.9];
808 let mut produced = 0;
809 for p in prices {
810 if let Some(v) = rsi.update(p) {
811 assert!((0.0..=100.0).contains(&v), "RSI out of bounds: {v}");
812 produced += 1;
813 }
814 }
815 assert_eq!(produced, prices.len() - 1);
816 }
817
818 #[test]
819 fn test_incremental_macd_composes_like_batch() {
820 let mut m = IncrementalMacd::new(12, 26, 9);
821 let mut fast = IncrementalEma::new(12);
822 let mut slow = IncrementalEma::new(26);
823 let mut sig = IncrementalEma::new(9);
824 for p in [10.0, 11.0, 10.5, 12.0, 13.0, 12.5, 11.0, 11.5] {
825 let (macd, signal, hist) = m.update(p).unwrap();
826 let expect_macd = fast.update(p).unwrap() - slow.update(p).unwrap();
827 let expect_sig = sig.update(expect_macd).unwrap();
828 assert!((macd - expect_macd).abs() < 1e-12);
829 assert!((signal - expect_sig).abs() < 1e-12);
830 assert!((hist - (expect_macd - expect_sig)).abs() < 1e-12);
831 }
832 }
833
834 #[test]
835 fn test_ema_sma_seed() {
836 let prices = vec![22.27, 22.19, 22.08, 22.17, 22.18];
837 let result = ema(&prices, 5).unwrap();
838 let expected = (22.27 + 22.19 + 22.08 + 22.17 + 22.18) / 5.0;
839 assert!((result[4] - expected).abs() < 1e-9);
840 }
841
842 #[test]
843 fn test_true_range_first() {
844 let h = vec![50.0, 52.0];
845 let l = vec![48.0, 49.0];
846 let c = vec![49.0, 51.0];
847 let tr = true_range(&h, &l, &c).unwrap();
848 assert_eq!(tr[0], 2.0);
849 assert_eq!(tr[1], 3.0);
850 }
851
852 #[test]
853 fn test_ema_incremental() {
854 let mut e = EMA::new(3);
855 e.update(10.0);
856 assert!(!e.is_ready());
857 e.update(20.0);
858 assert!(!e.is_ready());
859 e.update(30.0);
860 assert!(e.is_ready());
861 assert!((e.value() - 20.0).abs() < 1e-9);
862 }
863
864 #[test]
865 fn test_incremental_ema_returns_value() {
866 let mut e = IncrementalEma::new(3); assert_eq!(e.current(), None);
868 assert_eq!(e.update(10.0), Some(10.0)); assert_eq!(e.current(), Some(10.0));
870 let v = e.update(20.0).unwrap(); assert!((v - 15.0).abs() < 1e-9);
872 }
873
874 #[test]
875 fn test_incremental_atr_first_is_range() {
876 let mut a = IncrementalAtr::new(3);
877 assert_eq!(a.update(12.0, 10.0, 11.0), Some(2.0));
879 }
880}