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 ema_nan_aware(prices: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
50 if period == 0 {
51 return Err(IndicatorError::InvalidParameter {
52 name: "period".into(),
53 value: 0.0,
54 });
55 }
56 let mut result = vec![f64::NAN; prices.len()];
57 let alpha = 2.0 / (period as f64 + 1.0);
58
59 let Some(start) = prices.iter().position(|v| !v.is_nan()) else {
61 return Ok(result); };
63
64 result[start] = prices[start];
65 for i in (start + 1)..prices.len() {
66 result[i] = if prices[i].is_nan() {
67 f64::NAN
68 } else {
69 prices[i] * alpha + result[i - 1] * (1.0 - alpha)
70 };
71 }
72 Ok(result)
73}
74
75pub fn sma(prices: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
77 if period == 0 {
78 return Err(IndicatorError::InvalidParameter {
79 name: "period".into(),
80 value: 0.0,
81 });
82 }
83 if prices.len() < period {
84 return Err(IndicatorError::InsufficientData {
85 required: period,
86 available: prices.len(),
87 });
88 }
89 let mut result = vec![f64::NAN; prices.len()];
90 for i in (period - 1)..prices.len() {
91 let sum: f64 = prices[(i + 1 - period)..=i].iter().sum();
92 result[i] = sum / period as f64;
93 }
94 Ok(result)
95}
96
97pub fn true_range(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, IndicatorError> {
99 if high.len() != low.len() || high.len() != close.len() {
100 return Err(IndicatorError::InsufficientData {
101 required: high.len(),
102 available: low.len().min(close.len()),
103 });
104 }
105 let mut result = vec![f64::NAN; high.len()];
106 if !high.is_empty() {
107 result[0] = high[0] - low[0];
108 }
109 for i in 1..high.len() {
110 let tr1 = high[i] - low[i];
111 let tr2 = (high[i] - close[i - 1]).abs();
112 let tr3 = (low[i] - close[i - 1]).abs();
113 result[i] = tr1.max(tr2).max(tr3);
114 }
115 Ok(result)
116}
117
118pub fn atr(
120 high: &[f64],
121 low: &[f64],
122 close: &[f64],
123 period: usize,
124) -> Result<Vec<f64>, IndicatorError> {
125 let tr = true_range(high, low, close)?;
126 ema(&tr, period)
127}
128
129pub fn rsi(prices: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
131 if prices.len() < period + 1 {
132 return Err(IndicatorError::InsufficientData {
133 required: period + 1,
134 available: prices.len(),
135 });
136 }
137 let mut result = vec![f64::NAN; prices.len()];
138 let mut gains = vec![0.0; prices.len()];
139 let mut losses = vec![0.0; prices.len()];
140 for i in 1..prices.len() {
141 let change = prices[i] - prices[i - 1];
142 if change > 0.0 {
143 gains[i] = change;
144 } else {
145 losses[i] = -change;
146 }
147 }
148 let avg_gains = ema(&gains, period)?;
149 let avg_losses = ema(&losses, period)?;
150 for i in period..prices.len() {
151 if avg_losses[i] == 0.0 {
152 result[i] = 100.0;
153 } else {
154 let rs = avg_gains[i] / avg_losses[i];
155 result[i] = 100.0 - (100.0 / (1.0 + rs));
156 }
157 }
158 Ok(result)
159}
160
161pub fn macd(
163 prices: &[f64],
164 fast_period: usize,
165 slow_period: usize,
166 signal_period: usize,
167) -> MacdResult {
168 let fast_ema = ema_nan_aware(prices, fast_period)?;
171 let slow_ema = ema_nan_aware(prices, slow_period)?;
172 let mut macd_line = vec![f64::NAN; prices.len()];
173 for i in 0..prices.len() {
174 if !fast_ema[i].is_nan() && !slow_ema[i].is_nan() {
175 macd_line[i] = fast_ema[i] - slow_ema[i];
176 }
177 }
178 let signal_line = ema_nan_aware(&macd_line, signal_period)?;
182 let mut histogram = vec![f64::NAN; prices.len()];
183 for i in 0..prices.len() {
184 if !macd_line[i].is_nan() && !signal_line[i].is_nan() {
185 histogram[i] = macd_line[i] - signal_line[i];
186 }
187 }
188 Ok((macd_line, signal_line, histogram))
189}
190
191#[derive(Debug, Clone)]
200pub struct EMA {
201 period: usize,
202 alpha: f64,
203 value: f64,
204 initialized: bool,
205 warmup: VecDeque<f64>,
206}
207
208impl EMA {
209 pub fn new(period: usize) -> Self {
210 Self {
211 period,
212 alpha: 2.0 / (period as f64 + 1.0),
213 value: 0.0,
214 initialized: false,
215 warmup: VecDeque::with_capacity(period),
216 }
217 }
218
219 pub fn update(&mut self, price: f64) {
220 if !self.initialized {
221 self.warmup.push_back(price);
222 if self.warmup.len() >= self.period {
223 self.value = self.warmup.iter().sum::<f64>() / self.period as f64;
224 self.initialized = true;
225 self.warmup.clear();
226 }
227 } else {
228 self.value = price * self.alpha + self.value * (1.0 - self.alpha);
229 }
230 }
231
232 pub fn value(&self) -> f64 {
233 if self.initialized {
234 self.value
235 } else {
236 f64::NAN
237 }
238 }
239
240 pub fn is_ready(&self) -> bool {
241 self.initialized
242 }
243
244 pub fn reset(&mut self) {
245 self.value = 0.0;
246 self.initialized = false;
247 self.warmup.clear();
248 }
249}
250
251#[derive(Debug, Clone)]
253pub struct ATR {
254 #[allow(dead_code)]
255 period: usize,
256 ema: EMA,
257 prev_close: Option<f64>,
258}
259
260impl ATR {
261 pub fn new(period: usize) -> Self {
262 Self {
263 period,
264 ema: EMA::new(period),
265 prev_close: None,
266 }
267 }
268
269 pub fn update(&mut self, high: f64, low: f64, close: f64) {
270 let tr = if let Some(prev) = self.prev_close {
271 (high - low)
272 .max((high - prev).abs())
273 .max((low - prev).abs())
274 } else {
275 high - low
276 };
277 self.ema.update(tr);
278 self.prev_close = Some(close);
279 }
280
281 pub fn value(&self) -> f64 {
282 self.ema.value()
283 }
284
285 pub fn is_ready(&self) -> bool {
286 self.ema.is_ready()
287 }
288}
289
290#[derive(Debug, Clone)]
292pub struct StrategyIndicators {
293 pub ema_fast: Vec<f64>,
294 pub ema_slow: Vec<f64>,
295 pub atr: Vec<f64>,
296}
297
298#[derive(Debug, Clone)]
300pub struct IndicatorCalculator {
301 pub fast_ema_period: usize,
302 pub slow_ema_period: usize,
303 pub atr_period: usize,
304}
305
306impl Default for IndicatorCalculator {
307 fn default() -> Self {
308 Self {
309 fast_ema_period: 8,
310 slow_ema_period: 21,
311 atr_period: 14,
312 }
313 }
314}
315
316impl IndicatorCalculator {
317 pub fn new(fast_ema: usize, slow_ema: usize, atr_period: usize) -> Self {
318 Self {
319 fast_ema_period: fast_ema,
320 slow_ema_period: slow_ema,
321 atr_period,
322 }
323 }
324
325 pub fn calculate_all(
326 &self,
327 close: &[f64],
328 high: &[f64],
329 low: &[f64],
330 ) -> Result<StrategyIndicators, IndicatorError> {
331 Ok(StrategyIndicators {
332 ema_fast: ema(close, self.fast_ema_period)?,
333 ema_slow: ema(close, self.slow_ema_period)?,
334 atr: atr(high, low, close, self.atr_period)?,
335 })
336 }
337}
338
339pub struct IncrementalEma {
345 alpha: f64,
346 state: f64,
347 initialized: bool,
348}
349
350impl IncrementalEma {
351 pub fn new(period: usize) -> Self {
353 Self {
354 alpha: 2.0 / (period as f64 + 1.0),
355 state: 0.0,
356 initialized: false,
357 }
358 }
359
360 pub fn update(&mut self, price: f64) -> f64 {
362 if !self.initialized {
363 self.state = price;
364 self.initialized = true;
365 } else {
366 self.state = self.alpha * price + (1.0 - self.alpha) * self.state;
367 }
368 self.state
369 }
370
371 pub fn current(&self) -> Option<f64> {
373 if self.initialized {
374 Some(self.state)
375 } else {
376 None
377 }
378 }
379}
380
381pub struct IncrementalAtr {
386 ema: IncrementalEma,
387 prev_close: Option<f64>,
388}
389
390impl IncrementalAtr {
391 pub fn new(period: usize) -> Self {
393 Self {
394 ema: IncrementalEma::new(period),
395 prev_close: None,
396 }
397 }
398
399 pub fn update(&mut self, high: f64, low: f64, close: f64) -> Option<f64> {
401 let tr = if let Some(prev) = self.prev_close {
402 let tr1 = high - low;
403 let tr2 = (high - prev).abs();
404 let tr3 = (low - prev).abs();
405 tr1.max(tr2).max(tr3)
406 } else {
407 high - low
408 };
409
410 self.prev_close = Some(close);
411 Some(self.ema.update(tr))
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418
419 #[test]
420 fn test_ema_sma_seed() {
421 let prices = vec![22.27, 22.19, 22.08, 22.17, 22.18];
422 let result = ema(&prices, 5).unwrap();
423 let expected = (22.27 + 22.19 + 22.08 + 22.17 + 22.18) / 5.0;
424 assert!((result[4] - expected).abs() < 1e-9);
425 }
426
427 #[test]
428 fn test_true_range_first() {
429 let h = vec![50.0, 52.0];
430 let l = vec![48.0, 49.0];
431 let c = vec![49.0, 51.0];
432 let tr = true_range(&h, &l, &c).unwrap();
433 assert_eq!(tr[0], 2.0);
434 assert_eq!(tr[1], 3.0);
435 }
436
437 #[test]
438 fn test_ema_incremental() {
439 let mut e = EMA::new(3);
440 e.update(10.0);
441 assert!(!e.is_ready());
442 e.update(20.0);
443 assert!(!e.is_ready());
444 e.update(30.0);
445 assert!(e.is_ready());
446 assert!((e.value() - 20.0).abs() < 1e-9);
447 }
448
449 #[test]
450 fn test_incremental_ema_returns_value() {
451 let mut e = IncrementalEma::new(3); assert_eq!(e.current(), None);
453 assert_eq!(e.update(10.0), 10.0); assert_eq!(e.current(), Some(10.0));
455 let v = e.update(20.0); assert!((v - 15.0).abs() < 1e-9);
457 }
458
459 #[test]
460 fn test_incremental_atr_first_is_range() {
461 let mut a = IncrementalAtr::new(3);
462 assert_eq!(a.update(12.0, 10.0, 11.0), Some(2.0));
464 }
465}