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(
144 high: &[f64],
145 low: &[f64],
146 close: &[f64],
147 period: usize,
148) -> Result<Vec<f64>, IndicatorError> {
149 let tr = true_range(high, low, close)?;
150 ema(&tr, period)
151}
152
153pub fn rsi(prices: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
155 if prices.len() < period + 1 {
156 return Err(IndicatorError::InsufficientData {
157 required: period + 1,
158 available: prices.len(),
159 });
160 }
161 let mut result = vec![f64::NAN; prices.len()];
162 let mut gains = vec![0.0; prices.len()];
163 let mut losses = vec![0.0; prices.len()];
164 for i in 1..prices.len() {
165 let change = prices[i] - prices[i - 1];
166 if change > 0.0 {
167 gains[i] = change;
168 } else {
169 losses[i] = -change;
170 }
171 }
172 let avg_gains = ema(&gains, period)?;
173 let avg_losses = ema(&losses, period)?;
174 for i in period..prices.len() {
175 if avg_losses[i] == 0.0 {
176 result[i] = 100.0;
177 } else {
178 let rs = avg_gains[i] / avg_losses[i];
179 result[i] = 100.0 - (100.0 / (1.0 + rs));
180 }
181 }
182 Ok(result)
183}
184
185pub fn macd(
187 prices: &[f64],
188 fast_period: usize,
189 slow_period: usize,
190 signal_period: usize,
191) -> MacdResult {
192 let fast_ema = ema_nan_aware(prices, fast_period)?;
195 let slow_ema = ema_nan_aware(prices, slow_period)?;
196 let mut macd_line = vec![f64::NAN; prices.len()];
197 for i in 0..prices.len() {
198 if !fast_ema[i].is_nan() && !slow_ema[i].is_nan() {
199 macd_line[i] = fast_ema[i] - slow_ema[i];
200 }
201 }
202 let signal_line = ema_nan_aware(&macd_line, signal_period)?;
206 let mut histogram = vec![f64::NAN; prices.len()];
207 for i in 0..prices.len() {
208 if !macd_line[i].is_nan() && !signal_line[i].is_nan() {
209 histogram[i] = macd_line[i] - signal_line[i];
210 }
211 }
212 Ok((macd_line, signal_line, histogram))
213}
214
215#[derive(Debug, Clone)]
224pub struct EMA {
225 period: usize,
226 alpha: f64,
227 value: f64,
228 initialized: bool,
229 warmup: VecDeque<f64>,
230}
231
232impl EMA {
233 pub fn new(period: usize) -> Self {
234 Self {
235 period,
236 alpha: 2.0 / (period as f64 + 1.0),
237 value: 0.0,
238 initialized: false,
239 warmup: VecDeque::with_capacity(period),
240 }
241 }
242
243 pub fn update(&mut self, price: f64) {
244 if !self.initialized {
245 self.warmup.push_back(price);
246 if self.warmup.len() >= self.period {
247 self.value = self.warmup.iter().sum::<f64>() / self.period as f64;
248 self.initialized = true;
249 self.warmup.clear();
250 }
251 } else {
252 self.value = price * self.alpha + self.value * (1.0 - self.alpha);
253 }
254 }
255
256 pub fn value(&self) -> f64 {
257 if self.initialized {
258 self.value
259 } else {
260 f64::NAN
261 }
262 }
263
264 pub fn is_ready(&self) -> bool {
265 self.initialized
266 }
267
268 pub fn reset(&mut self) {
269 self.value = 0.0;
270 self.initialized = false;
271 self.warmup.clear();
272 }
273}
274
275#[derive(Debug, Clone)]
277pub struct ATR {
278 #[allow(dead_code)]
279 period: usize,
280 ema: EMA,
281 prev_close: Option<f64>,
282}
283
284impl ATR {
285 pub fn new(period: usize) -> Self {
286 Self {
287 period,
288 ema: EMA::new(period),
289 prev_close: None,
290 }
291 }
292
293 pub fn update(&mut self, high: f64, low: f64, close: f64) {
294 let tr = if let Some(prev) = self.prev_close {
295 (high - low)
296 .max((high - prev).abs())
297 .max((low - prev).abs())
298 } else {
299 high - low
300 };
301 self.ema.update(tr);
302 self.prev_close = Some(close);
303 }
304
305 pub fn value(&self) -> f64 {
306 self.ema.value()
307 }
308
309 pub fn is_ready(&self) -> bool {
310 self.ema.is_ready()
311 }
312}
313
314#[derive(Debug, Clone)]
316pub struct StrategyIndicators {
317 pub ema_fast: Vec<f64>,
318 pub ema_slow: Vec<f64>,
319 pub atr: Vec<f64>,
320}
321
322#[derive(Debug, Clone)]
324pub struct IndicatorCalculator {
325 pub fast_ema_period: usize,
326 pub slow_ema_period: usize,
327 pub atr_period: usize,
328}
329
330impl Default for IndicatorCalculator {
331 fn default() -> Self {
332 Self {
333 fast_ema_period: 8,
334 slow_ema_period: 21,
335 atr_period: 14,
336 }
337 }
338}
339
340impl IndicatorCalculator {
341 pub fn new(fast_ema: usize, slow_ema: usize, atr_period: usize) -> Self {
342 Self {
343 fast_ema_period: fast_ema,
344 slow_ema_period: slow_ema,
345 atr_period,
346 }
347 }
348
349 pub fn calculate_all(
350 &self,
351 close: &[f64],
352 high: &[f64],
353 low: &[f64],
354 ) -> Result<StrategyIndicators, IndicatorError> {
355 Ok(StrategyIndicators {
356 ema_fast: ema(close, self.fast_ema_period)?,
357 ema_slow: ema(close, self.slow_ema_period)?,
358 atr: atr(high, low, close, self.atr_period)?,
359 })
360 }
361}
362
363#[derive(Debug, Clone)]
369pub struct IncrementalEma {
370 alpha: f64,
371 state: f64,
372 initialized: bool,
373}
374
375impl IncrementalEma {
376 pub fn new(period: usize) -> Self {
378 Self {
379 alpha: 2.0 / (period as f64 + 1.0),
380 state: 0.0,
381 initialized: false,
382 }
383 }
384
385 pub fn update(&mut self, price: f64) -> Option<f64> {
389 Some(self.step(price))
390 }
391
392 fn step(&mut self, price: f64) -> f64 {
394 if !self.initialized {
395 self.state = price;
396 self.initialized = true;
397 } else {
398 self.state = self.alpha * price + (1.0 - self.alpha) * self.state;
399 }
400 self.state
401 }
402
403 pub fn current(&self) -> Option<f64> {
405 if self.initialized {
406 Some(self.state)
407 } else {
408 None
409 }
410 }
411}
412
413pub struct IncrementalAtr {
418 ema: IncrementalEma,
419 prev_close: Option<f64>,
420}
421
422impl IncrementalAtr {
423 pub fn new(period: usize) -> Self {
425 Self {
426 ema: IncrementalEma::new(period),
427 prev_close: None,
428 }
429 }
430
431 pub fn update(&mut self, high: f64, low: f64, close: f64) -> Option<f64> {
433 let tr = if let Some(prev) = self.prev_close {
434 let tr1 = high - low;
435 let tr2 = (high - prev).abs();
436 let tr3 = (low - prev).abs();
437 tr1.max(tr2).max(tr3)
438 } else {
439 high - low
440 };
441
442 self.prev_close = Some(close);
443 Some(self.ema.step(tr))
444 }
445}
446
447#[derive(Debug, Clone)]
456pub struct IncrementalRsi {
457 gain_ema: IncrementalEma,
458 loss_ema: IncrementalEma,
459 prev_price: Option<f64>,
460}
461
462impl IncrementalRsi {
463 pub fn new(period: usize) -> Self {
465 Self {
466 gain_ema: IncrementalEma::new(period),
467 loss_ema: IncrementalEma::new(period),
468 prev_price: None,
469 }
470 }
471
472 pub fn update(&mut self, price: f64) -> Option<f64> {
475 let prev = self.prev_price.replace(price)?;
476 let change = price - prev;
477 let (gain, loss) = if change > 0.0 {
478 (change, 0.0)
479 } else {
480 (0.0, -change)
481 };
482 let avg_gain = self.gain_ema.step(gain);
483 let avg_loss = self.loss_ema.step(loss);
484 let rsi = if avg_loss == 0.0 {
485 100.0
486 } else {
487 let rs = avg_gain / avg_loss;
488 100.0 - 100.0 / (1.0 + rs)
489 };
490 Some(rsi)
491 }
492}
493
494#[derive(Debug, Clone)]
502pub struct IncrementalMacd {
503 fast: IncrementalEma,
504 slow: IncrementalEma,
505 signal: IncrementalEma,
506}
507
508impl IncrementalMacd {
509 pub fn new(fast_period: usize, slow_period: usize, signal_period: usize) -> Self {
511 Self {
512 fast: IncrementalEma::new(fast_period),
513 slow: IncrementalEma::new(slow_period),
514 signal: IncrementalEma::new(signal_period),
515 }
516 }
517
518 pub fn update(&mut self, price: f64) -> Option<(f64, f64, f64)> {
522 let macd = self.fast.step(price) - self.slow.step(price);
523 let signal = self.signal.step(macd);
524 Some((macd, signal, macd - signal))
525 }
526}
527
528#[derive(Debug, Clone, Copy, PartialEq)]
530pub struct BollingerBandsValue {
531 pub middle: f64,
533 pub upper: f64,
535 pub lower: f64,
537 pub bandwidth: f64,
539 pub percent_b: f64,
541}
542
543#[derive(Debug, Clone)]
552pub struct IncrementalBollinger {
553 window: VecDeque<f64>,
554 period: usize,
555 std_mult: f64,
556}
557
558impl IncrementalBollinger {
559 pub fn new(period: usize, std_mult: f64) -> Self {
562 Self {
563 window: VecDeque::with_capacity(period.max(1)),
564 period,
565 std_mult,
566 }
567 }
568
569 pub fn update(&mut self, price: f64) -> Option<BollingerBandsValue> {
572 self.window.push_back(price);
573 if self.window.len() > self.period {
574 self.window.pop_front();
575 }
576 if self.window.len() < self.period || self.period < 2 {
577 return None;
578 }
579
580 let mean: f64 = self.window.iter().sum::<f64>() / self.period as f64;
581 let var: f64 =
583 self.window.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / (self.period - 1) as f64;
584 let std = var.sqrt();
585
586 let upper = mean + self.std_mult * std;
587 let lower = mean - self.std_mult * std;
588 let bandwidth = if mean == 0.0 {
589 f64::NAN
590 } else {
591 (upper - lower) / mean
592 };
593 let band_range = upper - lower;
594 let percent_b = if band_range == 0.0 {
595 f64::NAN
596 } else {
597 (price - lower) / band_range
598 };
599
600 Some(BollingerBandsValue {
601 middle: mean,
602 upper,
603 lower,
604 bandwidth,
605 percent_b,
606 })
607 }
608}
609
610#[cfg(test)]
611mod tests {
612 use super::*;
613
614 #[test]
615 fn test_incremental_bollinger_warmup_then_value() {
616 let mut bb = IncrementalBollinger::new(5, 2.0);
617 for p in [10.0, 11.0, 12.0, 13.0] {
618 assert!(bb.update(p).is_none(), "no value before `period` samples");
619 }
620 assert!(bb.update(14.0).is_some(), "value once the window is full");
621 }
622
623 #[test]
624 fn test_incremental_bollinger_constant_prices_zero_width() {
625 let mut bb = IncrementalBollinger::new(4, 2.0);
626 let mut last = None;
627 for _ in 0..4 {
628 last = bb.update(10.0);
629 }
630 let v = last.unwrap();
631 assert!((v.middle - 10.0).abs() < 1e-12);
632 assert!((v.upper - 10.0).abs() < 1e-12);
633 assert!((v.lower - 10.0).abs() < 1e-12);
634 assert!((v.bandwidth - 0.0).abs() < 1e-12);
635 assert!(v.percent_b.is_nan(), "zero-width band → %b undefined");
636 }
637
638 #[test]
639 fn test_incremental_bollinger_matches_sample_stddev() {
640 let mut bb = IncrementalBollinger::new(8, 2.0);
642 let mut last = None;
643 for p in [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0] {
644 last = bb.update(p);
645 }
646 let v = last.unwrap();
647 let std = (32.0_f64 / 7.0).sqrt();
648 assert!((v.middle - 5.0).abs() < 1e-9);
649 assert!((v.upper - (5.0 + 2.0 * std)).abs() < 1e-9);
650 assert!((v.lower - (5.0 - 2.0 * std)).abs() < 1e-9);
651 }
652
653 #[test]
654 fn test_incremental_rsi_first_sample_is_none() {
655 let mut rsi = IncrementalRsi::new(14);
656 assert_eq!(rsi.update(10.0), None);
657 assert!(rsi.update(11.0).is_some());
658 }
659
660 #[test]
661 fn test_incremental_rsi_all_gains_saturates_at_100() {
662 let mut rsi = IncrementalRsi::new(14);
663 let mut last = None;
664 for p in [10.0, 11.0, 12.0, 13.0, 14.0, 15.0] {
665 last = rsi.update(p);
666 }
667 assert!((last.unwrap() - 100.0).abs() < 1e-9);
669 }
670
671 #[test]
672 fn test_incremental_rsi_stays_in_bounds() {
673 let mut rsi = IncrementalRsi::new(5);
674 let prices = [44.0, 44.3, 44.1, 44.2, 43.6, 44.3, 44.8, 45.0, 44.7, 44.9];
675 let mut produced = 0;
676 for p in prices {
677 if let Some(v) = rsi.update(p) {
678 assert!((0.0..=100.0).contains(&v), "RSI out of bounds: {v}");
679 produced += 1;
680 }
681 }
682 assert_eq!(produced, prices.len() - 1);
683 }
684
685 #[test]
686 fn test_incremental_macd_composes_like_batch() {
687 let mut m = IncrementalMacd::new(12, 26, 9);
688 let mut fast = IncrementalEma::new(12);
689 let mut slow = IncrementalEma::new(26);
690 let mut sig = IncrementalEma::new(9);
691 for p in [10.0, 11.0, 10.5, 12.0, 13.0, 12.5, 11.0, 11.5] {
692 let (macd, signal, hist) = m.update(p).unwrap();
693 let expect_macd = fast.update(p).unwrap() - slow.update(p).unwrap();
694 let expect_sig = sig.update(expect_macd).unwrap();
695 assert!((macd - expect_macd).abs() < 1e-12);
696 assert!((signal - expect_sig).abs() < 1e-12);
697 assert!((hist - (expect_macd - expect_sig)).abs() < 1e-12);
698 }
699 }
700
701 #[test]
702 fn test_ema_sma_seed() {
703 let prices = vec![22.27, 22.19, 22.08, 22.17, 22.18];
704 let result = ema(&prices, 5).unwrap();
705 let expected = (22.27 + 22.19 + 22.08 + 22.17 + 22.18) / 5.0;
706 assert!((result[4] - expected).abs() < 1e-9);
707 }
708
709 #[test]
710 fn test_true_range_first() {
711 let h = vec![50.0, 52.0];
712 let l = vec![48.0, 49.0];
713 let c = vec![49.0, 51.0];
714 let tr = true_range(&h, &l, &c).unwrap();
715 assert_eq!(tr[0], 2.0);
716 assert_eq!(tr[1], 3.0);
717 }
718
719 #[test]
720 fn test_ema_incremental() {
721 let mut e = EMA::new(3);
722 e.update(10.0);
723 assert!(!e.is_ready());
724 e.update(20.0);
725 assert!(!e.is_ready());
726 e.update(30.0);
727 assert!(e.is_ready());
728 assert!((e.value() - 20.0).abs() < 1e-9);
729 }
730
731 #[test]
732 fn test_incremental_ema_returns_value() {
733 let mut e = IncrementalEma::new(3); assert_eq!(e.current(), None);
735 assert_eq!(e.update(10.0), Some(10.0)); assert_eq!(e.current(), Some(10.0));
737 let v = e.update(20.0).unwrap(); assert!((v - 15.0).abs() < 1e-9);
739 }
740
741 #[test]
742 fn test_incremental_atr_first_is_range() {
743 let mut a = IncrementalAtr::new(3);
744 assert_eq!(a.update(12.0, 10.0, 11.0), Some(2.0));
746 }
747}