1use std::collections::VecDeque;
7
8use crate::error::IndicatorError;
9use crate::types::MacdResult;
10
11pub fn ema(prices: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
16 if period == 0 {
17 return Err(IndicatorError::InvalidParameter {
18 name: "period".into(),
19 value: 0.0,
20 });
21 }
22 if prices.len() < period {
23 return Err(IndicatorError::InsufficientData {
24 required: period,
25 available: prices.len(),
26 });
27 }
28 let mut result = vec![f64::NAN; prices.len()];
29 let alpha = 2.0 / (period as f64 + 1.0);
30 let first_sma: f64 = prices.iter().take(period).sum::<f64>() / period as f64;
31 result[period - 1] = first_sma;
32 for i in period..prices.len() {
33 result[i] = prices[i] * alpha + result[i - 1] * (1.0 - alpha);
34 }
35 Ok(result)
36}
37
38pub fn sma(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 for i in (period - 1)..prices.len() {
54 let sum: f64 = prices[(i + 1 - period)..=i].iter().sum();
55 result[i] = sum / period as f64;
56 }
57 Ok(result)
58}
59
60pub fn true_range(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, IndicatorError> {
62 if high.len() != low.len() || high.len() != close.len() {
63 return Err(IndicatorError::InsufficientData {
64 required: high.len(),
65 available: low.len().min(close.len()),
66 });
67 }
68 let mut result = vec![f64::NAN; high.len()];
69 if !high.is_empty() {
70 result[0] = high[0] - low[0];
71 }
72 for i in 1..high.len() {
73 let tr1 = high[i] - low[i];
74 let tr2 = (high[i] - close[i - 1]).abs();
75 let tr3 = (low[i] - close[i - 1]).abs();
76 result[i] = tr1.max(tr2).max(tr3);
77 }
78 Ok(result)
79}
80
81pub fn atr(
83 high: &[f64],
84 low: &[f64],
85 close: &[f64],
86 period: usize,
87) -> Result<Vec<f64>, IndicatorError> {
88 let tr = true_range(high, low, close)?;
89 ema(&tr, period)
90}
91
92pub fn rsi(prices: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
94 if prices.len() < period + 1 {
95 return Err(IndicatorError::InsufficientData {
96 required: period + 1,
97 available: prices.len(),
98 });
99 }
100 let mut result = vec![f64::NAN; prices.len()];
101 let mut gains = vec![0.0; prices.len()];
102 let mut losses = vec![0.0; prices.len()];
103 for i in 1..prices.len() {
104 let change = prices[i] - prices[i - 1];
105 if change > 0.0 {
106 gains[i] = change;
107 } else {
108 losses[i] = -change;
109 }
110 }
111 let avg_gains = ema(&gains, period)?;
112 let avg_losses = ema(&losses, period)?;
113 for i in period..prices.len() {
114 if avg_losses[i] == 0.0 {
115 result[i] = 100.0;
116 } else {
117 let rs = avg_gains[i] / avg_losses[i];
118 result[i] = 100.0 - (100.0 / (1.0 + rs));
119 }
120 }
121 Ok(result)
122}
123
124pub fn macd(
126 prices: &[f64],
127 fast_period: usize,
128 slow_period: usize,
129 signal_period: usize,
130) -> MacdResult {
131 let fast_ema = ema(prices, fast_period)?;
132 let slow_ema = ema(prices, slow_period)?;
133 let mut macd_line = vec![f64::NAN; prices.len()];
134 for i in 0..prices.len() {
135 if !fast_ema[i].is_nan() && !slow_ema[i].is_nan() {
136 macd_line[i] = fast_ema[i] - slow_ema[i];
137 }
138 }
139 let signal_line = ema(&macd_line, signal_period)?;
140 let mut histogram = vec![f64::NAN; prices.len()];
141 for i in 0..prices.len() {
142 if !macd_line[i].is_nan() && !signal_line[i].is_nan() {
143 histogram[i] = macd_line[i] - signal_line[i];
144 }
145 }
146 Ok((macd_line, signal_line, histogram))
147}
148
149#[derive(Debug, Clone)]
158pub struct EMA {
159 period: usize,
160 alpha: f64,
161 value: f64,
162 initialized: bool,
163 warmup: VecDeque<f64>,
164}
165
166impl EMA {
167 pub fn new(period: usize) -> Self {
168 Self {
169 period,
170 alpha: 2.0 / (period as f64 + 1.0),
171 value: 0.0,
172 initialized: false,
173 warmup: VecDeque::with_capacity(period),
174 }
175 }
176
177 pub fn update(&mut self, price: f64) {
178 if !self.initialized {
179 self.warmup.push_back(price);
180 if self.warmup.len() >= self.period {
181 self.value = self.warmup.iter().sum::<f64>() / self.period as f64;
182 self.initialized = true;
183 self.warmup.clear();
184 }
185 } else {
186 self.value = price * self.alpha + self.value * (1.0 - self.alpha);
187 }
188 }
189
190 pub fn value(&self) -> f64 {
191 if self.initialized {
192 self.value
193 } else {
194 f64::NAN
195 }
196 }
197
198 pub fn is_ready(&self) -> bool {
199 self.initialized
200 }
201
202 pub fn reset(&mut self) {
203 self.value = 0.0;
204 self.initialized = false;
205 self.warmup.clear();
206 }
207}
208
209#[derive(Debug, Clone)]
211pub struct ATR {
212 #[allow(dead_code)]
213 period: usize,
214 ema: EMA,
215 prev_close: Option<f64>,
216}
217
218impl ATR {
219 pub fn new(period: usize) -> Self {
220 Self {
221 period,
222 ema: EMA::new(period),
223 prev_close: None,
224 }
225 }
226
227 pub fn update(&mut self, high: f64, low: f64, close: f64) {
228 let tr = if let Some(prev) = self.prev_close {
229 (high - low)
230 .max((high - prev).abs())
231 .max((low - prev).abs())
232 } else {
233 high - low
234 };
235 self.ema.update(tr);
236 self.prev_close = Some(close);
237 }
238
239 pub fn value(&self) -> f64 {
240 self.ema.value()
241 }
242
243 pub fn is_ready(&self) -> bool {
244 self.ema.is_ready()
245 }
246}
247
248#[derive(Debug, Clone)]
250pub struct StrategyIndicators {
251 pub ema_fast: Vec<f64>,
252 pub ema_slow: Vec<f64>,
253 pub atr: Vec<f64>,
254}
255
256#[derive(Debug, Clone)]
258pub struct IndicatorCalculator {
259 pub fast_ema_period: usize,
260 pub slow_ema_period: usize,
261 pub atr_period: usize,
262}
263
264impl Default for IndicatorCalculator {
265 fn default() -> Self {
266 Self {
267 fast_ema_period: 8,
268 slow_ema_period: 21,
269 atr_period: 14,
270 }
271 }
272}
273
274impl IndicatorCalculator {
275 pub fn new(fast_ema: usize, slow_ema: usize, atr_period: usize) -> Self {
276 Self {
277 fast_ema_period: fast_ema,
278 slow_ema_period: slow_ema,
279 atr_period,
280 }
281 }
282
283 pub fn calculate_all(
284 &self,
285 close: &[f64],
286 high: &[f64],
287 low: &[f64],
288 ) -> Result<StrategyIndicators, IndicatorError> {
289 Ok(StrategyIndicators {
290 ema_fast: ema(close, self.fast_ema_period)?,
291 ema_slow: ema(close, self.slow_ema_period)?,
292 atr: atr(high, low, close, self.atr_period)?,
293 })
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_ema_sma_seed() {
303 let prices = vec![22.27, 22.19, 22.08, 22.17, 22.18];
304 let result = ema(&prices, 5).unwrap();
305 let expected = (22.27 + 22.19 + 22.08 + 22.17 + 22.18) / 5.0;
306 assert!((result[4] - expected).abs() < 1e-9);
307 }
308
309 #[test]
310 fn test_true_range_first() {
311 let h = vec![50.0, 52.0];
312 let l = vec![48.0, 49.0];
313 let c = vec![49.0, 51.0];
314 let tr = true_range(&h, &l, &c).unwrap();
315 assert_eq!(tr[0], 2.0);
316 assert_eq!(tr[1], 3.0);
317 }
318
319 #[test]
320 fn test_ema_incremental() {
321 let mut e = EMA::new(3);
322 e.update(10.0);
323 assert!(!e.is_ready());
324 e.update(20.0);
325 assert!(!e.is_ready());
326 e.update(30.0);
327 assert!(e.is_ready());
328 assert!((e.value() - 20.0).abs() < 1e-9);
329 }
330}