market_data/
indicators.rs

1use crate::{Interval, Series};
2use serde::{Deserialize, Serialize};
3use std::collections::{HashMap, VecDeque};
4use std::fmt;
5
6use self::{
7    bollinger::calculate_bollinger_bands,
8    ema::{calculate_ema, calculate_ema_slice},
9    macd::calculate_macd,
10    rsi::calculate_rsi,
11    sma::calculate_sma,
12    stochastic::calculate_stochastic,
13};
14
15pub(crate) mod bollinger;
16pub(crate) mod ema;
17pub(crate) mod macd;
18pub(crate) mod rsi;
19pub(crate) mod sma;
20pub(crate) mod stochastic;
21
22/// Holds the MarketSeries + the calculation for the supported indicators
23#[derive(Debug, Serialize, Deserialize)]
24pub struct EnhancedMarketSeries {
25    /// holds symbol like: "GOOGL"
26    pub symbol: String,
27    /// inteval from intraday to monthly
28    pub interval: Interval,
29    /// the original series downloaded and parsed from publishers
30    pub series: Vec<Series>,
31    /// the request for technical indicators
32    pub asks: Vec<Ask>,
33    /// calculated indicators
34    pub indicators: Indicators,
35}
36
37#[derive(Debug, Serialize, Deserialize)]
38pub enum Ask {
39    SMA(usize),
40    EMA(usize),
41    RSI(usize),
42    Stochastic(usize),
43    MACD(usize, usize, usize),
44    BB(usize, usize),
45}
46
47/// It is part of the EnhancedMarketSeries struct
48#[derive(Debug, Default, Serialize, Deserialize)]
49pub struct Indicators {
50    /// Simple Moving Average
51    pub sma: HashMap<String, VecDeque<f32>>,
52    /// Exponential Moving Average
53    pub ema: HashMap<String, VecDeque<f32>>,
54    /// Relative Strength Index
55    pub rsi: HashMap<String, VecDeque<f32>>,
56    ///  Stochastic Oscillator
57    pub stochastic: HashMap<String, VecDeque<f32>>,
58    /// Moving average convergence/divergence (MACD)
59    pub macd: HashMap<String, (VecDeque<f32>, VecDeque<f32>, VecDeque<f32>)>,
60    /// Bollinger Band (BB)
61    pub bb: HashMap<String, (VecDeque<f32>, VecDeque<f32>, VecDeque<f32>)>,
62}
63
64impl EnhancedMarketSeries {
65    /// Simple Moving Average, a period must be provided over which it will be calculated
66    pub fn with_sma(mut self, period: usize) -> Self {
67        self.asks.push(Ask::SMA(period));
68        self
69    }
70
71    /// Exponential Moving Average, a period must be provided over which it will be calculated
72    pub fn with_ema(mut self, period: usize) -> Self {
73        self.asks.push(Ask::EMA(period));
74        self
75    }
76
77    /// Relative Strength Index, a period must be provided over which it will be calculated
78    pub fn with_rsi(mut self, period: usize) -> Self {
79        self.asks.push(Ask::RSI(period));
80        self
81    }
82
83    /// Stochastic Oscillator, a period must be provided over which it will be calculated
84    pub fn with_stochastic(mut self, period: usize) -> Self {
85        self.asks.push(Ask::Stochastic(period));
86        self
87    }
88
89    /// Moving average convergence/divergence (MACD), a fast, slow & signal EMA values should be provided, default (12, 26, 9)
90    pub fn with_macd(mut self, fast: usize, slow: usize, signal: usize) -> Self {
91        self.asks.push(Ask::MACD(fast, slow, signal));
92        self
93    }
94
95    /// Bollinger Bands (BB), the period & standard deviation values should be provided, like (20, 2)
96    pub fn with_bb(mut self, period: usize, std_dev: usize) -> Self {
97        self.asks.push(Ask::BB(period, std_dev));
98        self
99    }
100
101    /// Calculate the indicators and populate within the EnhancedMarketSeries struct
102    pub fn calculate(mut self) -> Self {
103        self.asks.iter().for_each(|ind| match ind {
104            Ask::SMA(period) => {
105                let calc_sma = calculate_sma(&self.series, period.clone());
106                self.indicators
107                    .sma
108                    .insert(format!("SMA {}", period), calc_sma);
109            }
110
111            Ask::EMA(period) => {
112                let calc_ema = calculate_ema(&self.series, period.clone());
113                self.indicators
114                    .ema
115                    .insert(format!("EMA {}", period), calc_ema);
116            }
117
118            Ask::RSI(period) => {
119                let calc_rsi = calculate_rsi(&self.series, period.clone());
120                self.indicators
121                    .rsi
122                    .insert(format!("RSI {}", period), calc_rsi);
123            }
124
125            Ask::Stochastic(period) => {
126                let calc_stoch = calculate_stochastic(&self.series, period.clone());
127                self.indicators
128                    .stochastic
129                    .insert(format!("STO {}", period), calc_stoch);
130            }
131
132            Ask::MACD(fast, slow, signal) => {
133                let (calc_macd, calc_signal, calc_histogram) =
134                    calculate_macd(&self.series, fast.clone(), slow.clone(), signal.clone());
135
136                self.indicators.macd.insert(
137                    format!("MACD ({}, {}, {})", fast, slow, signal),
138                    (calc_macd, calc_signal, calc_histogram),
139                );
140            }
141            Ask::BB(period, std_dev) => {
142                let (upper_band, mid_band, lower_band) =
143                    calculate_bollinger_bands(&self.series, period.clone(), std_dev.clone());
144
145                self.indicators.bb.insert(
146                    format!("BB ({}, {})", period, std_dev),
147                    (upper_band, mid_band, lower_band),
148                );
149            }
150        });
151
152        self
153    }
154}
155
156impl fmt::Display for Ask {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        match self {
159            Ask::SMA(period) => write!(f, "SMA({})", period),
160            Ask::EMA(period) => write!(f, "EMA({})", period),
161            Ask::RSI(period) => write!(f, "RSI({})", period),
162            Ask::MACD(fast, slow, signal) => write!(f, "MACD({}, {}, {})", fast, slow, signal),
163            Ask::Stochastic(period) => write!(f, "STO({})", period),
164            Ask::BB(period, std_dev) => write!(f, "BB({}, {})", period, std_dev),
165        }
166    }
167}
168
169impl fmt::Display for EnhancedMarketSeries {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        write!(
172            f,
173            "EnhancedMarketSerie: Symbol = {}, Interval = {},  Requested Indicators: {:?}, Series: \n",
174            self.symbol, self.interval, self.asks
175        )?;
176        for (i, series) in self.series.iter().enumerate() {
177            write!(
178                f,
179                "Date: {}, Open: {:.2}, Close: {:.2}, High: {:.2}, Low: {:.2}, Volume: {:.2}, ",
180                series.date, series.open, series.close, series.high, series.low, series.volume
181            )?;
182
183            for (indicator_name, indicator_values) in &self.indicators.sma {
184                if let Some(value) = indicator_values.get(i) {
185                    write!(f, "{}: {:.2}, ", indicator_name, value)?;
186                }
187            }
188
189            for (indicator_name, indicator_values) in &self.indicators.ema {
190                if let Some(value) = indicator_values.get(i) {
191                    write!(f, "{}: {:.2}, ", indicator_name, value)?;
192                }
193            }
194
195            for (indicator_name, indicator_values) in &self.indicators.rsi {
196                if let Some(value) = indicator_values.get(i) {
197                    write!(f, "{}: {:.2}, ", indicator_name, value)?;
198                }
199            }
200
201            for (indicator_name, indicator_values) in &self.indicators.stochastic {
202                if let Some(value) = indicator_values.get(i) {
203                    write!(f, "{}: {:.2}, ", indicator_name, value)?;
204                }
205            }
206
207            for (indicator_name, (macd, signal, histogram)) in &self.indicators.macd {
208                if let Some(macd_value) = macd.get(i) {
209                    if let Some(signal_value) = signal.get(i) {
210                        if let Some(hist_value) = histogram.get(i) {
211                            write!(
212                                f,
213                                "{}: {:.2}, {:.2}, {:.2}, ",
214                                indicator_name, macd_value, signal_value, hist_value
215                            )?;
216                        }
217                    }
218                }
219            }
220
221            for (indicator_name, (upper_band, mid_band, lower_band)) in &self.indicators.bb {
222                if let Some(upper_band) = upper_band.get(i) {
223                    if let Some(mid_band) = mid_band.get(i) {
224                        if let Some(lower_band) = lower_band.get(i) {
225                            write!(
226                                f,
227                                "{}: {:.2}, {:.2}, {:.2}, ",
228                                indicator_name, upper_band, mid_band, lower_band
229                            )?;
230                        }
231                    }
232                }
233            }
234
235            // Remove trailing comma and space
236            //let _ = f.write_str("\b\b");
237
238            if i < self.series.len() - 1 {
239                writeln!(f, ",")?;
240            }
241        }
242
243        Ok(())
244    }
245}