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}