indexes_rs/v1/ma/
main.rs

1//! # Moving Averages Module
2//!
3//! This module provides a unified interface for calculating multiple moving averages
4//! along with a MACD indicator. It includes:
5//!
6//! - **SMA (Simple Moving Average):** Calculated for short, medium, and long periods.
7//! - **EMA (Exponential Moving Average):** Calculated for short, medium, and long periods.
8//! - **MACD (Moving Average Convergence Divergence):**
9//!
10//! ## Example
11//!
12//! ```rust
13//! use indexes_rs::v1::ma::main::{MovingAverages, MovingAverageResults};
14//!
15//! // Create a new moving averages calculator with default parameters.
16//! let mut ma = MovingAverages::default();
17//!
18//! // Or supply custom parameters:
19//! // SMA: 10, 30, 100; EMA: 10, 30, 100; MACD: fast=8, slow=17, signal=9
20//! // let mut ma = MovingAverages::with_params(
21//! //     Some(10), Some(30), Some(100),
22//! //     Some(10), Some(30), Some(100),
23//! //     Some(8), Some(17), Some(9)
24//! // ).unwrap();
25//!
26//! // Simulate a stream of prices.
27//! let prices = vec![10.0, 10.5, 11.0, 10.8, 11.2, 11.5, 11.3];
28//!
29//! for price in prices {
30//!     let results = ma.calculate(price);
31//!     println!("SMA: {:?}, EMA: {:?}, MACD: {:?}", results.sma, results.ema, results.macd);
32//! }
33//! ```
34
35use serde::Serialize;
36
37use crate::v1::{
38    ema::main::ExponentialMovingAverage,
39    macd::{main::MACD, types::MACDResult},
40    sma::main::{SMAError, SMAResult, SimpleMovingAverage},
41};
42
43/// A container for the different moving average indicators.
44pub struct MovingAverages {
45    pub sma: SMAPeriods,
46    pub ema: EMAPeriods,
47    pub macd: MACD,
48}
49
50/// Holds SMA indicators for different periods.
51pub struct SMAPeriods {
52    /// Short period SMA.
53    pub short: SimpleMovingAverage,
54    /// Medium period SMA.
55    pub medium: SimpleMovingAverage,
56    /// Long period SMA.
57    pub long: SimpleMovingAverage,
58}
59
60/// Holds EMA indicators for different periods.
61pub struct EMAPeriods {
62    /// Short period EMA.
63    pub short: ExponentialMovingAverage,
64    /// Medium period EMA.
65    pub medium: ExponentialMovingAverage,
66    /// Long period EMA.
67    pub long: ExponentialMovingAverage,
68}
69
70/// The consolidated results for moving averages.
71#[derive(Debug)]
72pub struct MovingAverageResults {
73    /// The Simple Moving Average values.
74    pub sma: SMAValues,
75    /// The Exponential Moving Average values.
76    pub ema: EMAValues,
77    /// The MACD result (if available).
78    pub macd: Option<MACDResult>,
79}
80
81/// SMA values for different periods.
82#[derive(Debug, Clone, Serialize, PartialEq)]
83pub struct SMAValues {
84    /// Short period SMA value.
85    pub short: Option<SMAResult>,
86    /// Medium period SMA value.
87    pub medium: Option<SMAResult>,
88    /// Long period SMA value.
89    pub long: Option<SMAResult>,
90}
91
92/// EMA values for different periods.
93#[derive(Debug, Clone, Serialize, PartialEq)]
94pub struct EMAValues {
95    /// Short period EMA value.
96    pub short: Option<f64>,
97    /// Medium period EMA value.
98    pub medium: Option<f64>,
99    /// Long period EMA value.
100    pub long: Option<f64>,
101}
102
103impl Default for MovingAverages {
104    /// Creates a new `MovingAverages` instance using default parameters.
105    ///
106    /// - SMA periods: short = 20, medium = 50, long = 200
107    /// - EMA periods: short = 20, medium = 50, long = 200
108    /// - MACD parameters: fast = 12, slow = 26, signal = 9
109    fn default() -> Self {
110        Self::with_params(None, None, None, None, None, None, None, None, None).expect("Default parameters should always be valid")
111    }
112}
113
114impl MovingAverages {
115    /// Creates a new `MovingAverages` instance with custom parameters.
116    ///
117    /// # Arguments
118    ///
119    /// * `sma_short` - Optional SMA short period (default: 20).
120    /// * `sma_medium` - Optional SMA medium period (default: 50).
121    /// * `sma_long` - Optional SMA long period (default: 200).
122    /// * `ema_short` - Optional EMA short period (default: 20).
123    /// * `ema_medium` - Optional EMA medium period (default: 50).
124    /// * `ema_long` - Optional EMA long period (default: 200).
125    /// * `macd_fast` - Optional MACD fast period (default: 12).
126    /// * `macd_slow` - Optional MACD slow period (default: 26).
127    /// * `macd_signal` - Optional MACD signal period (default: 9).
128    pub fn with_params(
129        sma_short: Option<usize>,
130        sma_medium: Option<usize>,
131        sma_long: Option<usize>,
132        ema_short: Option<usize>,
133        ema_medium: Option<usize>,
134        ema_long: Option<usize>,
135        macd_fast: Option<usize>,
136        macd_slow: Option<usize>,
137        macd_signal: Option<usize>,
138    ) -> Result<Self, SMAError> {
139        Ok(MovingAverages {
140            sma: SMAPeriods::new(sma_short, sma_medium, sma_long)?,
141            ema: EMAPeriods::new_with_params(ema_short, ema_medium, ema_long),
142            macd: MACD::new(macd_fast.unwrap_or(12), macd_slow.unwrap_or(26), macd_signal.unwrap_or(9)),
143        })
144    }
145
146    /// Updates all moving averages with the new price and returns the consolidated results.
147    ///
148    /// # Arguments
149    ///
150    /// * `price` - The latest price value.
151    pub fn calculate(&mut self, price: f64) -> MovingAverageResults {
152        self.sma.update(price);
153        self.ema.update(price);
154
155        MovingAverageResults {
156            sma: self.sma.get_values(),
157            ema: self.ema.get_values(),
158            macd: self.macd.calculate(price),
159        }
160    }
161}
162
163impl SMAPeriods {
164    /// Creates a new set of SMA indicators with the following periods:
165    /// - Short: defaults to 20 if not provided.
166    /// - Medium: defaults to 50 if not provided.
167    /// - Long: defaults to 200 if not provided.
168    pub fn new(short: Option<usize>, medium: Option<usize>, long: Option<usize>) -> Result<Self, SMAError> {
169        Ok(SMAPeriods {
170            short: SimpleMovingAverage::new(short.unwrap_or(20))?,
171            medium: SimpleMovingAverage::new(medium.unwrap_or(50))?,
172            long: SimpleMovingAverage::new(long.unwrap_or(200))?,
173        })
174    }
175
176    /// Updates each SMA indicator with the new price.
177    ///
178    /// # Arguments
179    ///
180    /// * `price` - The latest price value.
181    pub fn update(&mut self, price: f64) {
182        self.short.add_value(price);
183        self.medium.add_value(price);
184        self.long.add_value(price);
185    }
186
187    /// Retrieves the current SMA values.
188    pub fn get_values(&mut self) -> SMAValues {
189        SMAValues {
190            short: self.short.calculate(),
191            medium: self.medium.calculate(),
192            long: self.long.calculate(),
193        }
194    }
195}
196
197impl EMAPeriods {
198    /// Creates a new set of EMA indicators with the following periods:
199    /// - Short: defaults to 20 if not provided.
200    /// - Medium: defaults to 50 if not provided.
201    /// - Long: defaults to 200 if not provided.
202    pub fn new_with_params(short: Option<usize>, medium: Option<usize>, long: Option<usize>) -> Self {
203        EMAPeriods {
204            short: ExponentialMovingAverage::new(short.unwrap_or(20)),
205            medium: ExponentialMovingAverage::new(medium.unwrap_or(50)),
206            long: ExponentialMovingAverage::new(long.unwrap_or(200)),
207        }
208    }
209
210    /// Updates each EMA indicator with the new price.
211    ///
212    /// # Arguments
213    ///
214    /// * `price` - The latest price value.
215    pub fn update(&mut self, price: f64) {
216        self.short.add_value(price);
217        self.medium.add_value(price);
218        self.long.add_value(price);
219    }
220
221    /// Retrieves the current EMA values.
222    pub fn get_values(&self) -> EMAValues {
223        EMAValues {
224            short: self.short.get_current_value(),
225            medium: self.medium.get_current_value(),
226            long: self.long.get_current_value(),
227        }
228    }
229}