Skip to main content

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