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 start = match prices.iter().position(|v| !v.is_nan()) {
61 Some(i) => i,
62 None => return Ok(result), };
64
65 result[start] = prices[start];
66 for i in (start + 1)..prices.len() {
67 result[i] = if prices[i].is_nan() {
68 f64::NAN
69 } else {
70 prices[i] * alpha + result[i - 1] * (1.0 - alpha)
71 };
72 }
73 Ok(result)
74}
75
76pub fn sma(prices: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
78 if period == 0 {
79 return Err(IndicatorError::InvalidParameter {
80 name: "period".into(),
81 value: 0.0,
82 });
83 }
84 if prices.len() < period {
85 return Err(IndicatorError::InsufficientData {
86 required: period,
87 available: prices.len(),
88 });
89 }
90 let mut result = vec![f64::NAN; prices.len()];
91 for i in (period - 1)..prices.len() {
92 let sum: f64 = prices[(i + 1 - period)..=i].iter().sum();
93 result[i] = sum / period as f64;
94 }
95 Ok(result)
96}
97
98pub fn true_range(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, IndicatorError> {
100 if high.len() != low.len() || high.len() != close.len() {
101 return Err(IndicatorError::InsufficientData {
102 required: high.len(),
103 available: low.len().min(close.len()),
104 });
105 }
106 let mut result = vec![f64::NAN; high.len()];
107 if !high.is_empty() {
108 result[0] = high[0] - low[0];
109 }
110 for i in 1..high.len() {
111 let tr1 = high[i] - low[i];
112 let tr2 = (high[i] - close[i - 1]).abs();
113 let tr3 = (low[i] - close[i - 1]).abs();
114 result[i] = tr1.max(tr2).max(tr3);
115 }
116 Ok(result)
117}
118
119pub fn atr(
121 high: &[f64],
122 low: &[f64],
123 close: &[f64],
124 period: usize,
125) -> Result<Vec<f64>, IndicatorError> {
126 let tr = true_range(high, low, close)?;
127 ema(&tr, period)
128}
129
130pub fn rsi(prices: &[f64], period: usize) -> Result<Vec<f64>, IndicatorError> {
132 if prices.len() < period + 1 {
133 return Err(IndicatorError::InsufficientData {
134 required: period + 1,
135 available: prices.len(),
136 });
137 }
138 let mut result = vec![f64::NAN; prices.len()];
139 let mut gains = vec![0.0; prices.len()];
140 let mut losses = vec![0.0; prices.len()];
141 for i in 1..prices.len() {
142 let change = prices[i] - prices[i - 1];
143 if change > 0.0 {
144 gains[i] = change;
145 } else {
146 losses[i] = -change;
147 }
148 }
149 let avg_gains = ema(&gains, period)?;
150 let avg_losses = ema(&losses, period)?;
151 for i in period..prices.len() {
152 if avg_losses[i] == 0.0 {
153 result[i] = 100.0;
154 } else {
155 let rs = avg_gains[i] / avg_losses[i];
156 result[i] = 100.0 - (100.0 / (1.0 + rs));
157 }
158 }
159 Ok(result)
160}
161
162pub fn macd(
164 prices: &[f64],
165 fast_period: usize,
166 slow_period: usize,
167 signal_period: usize,
168) -> MacdResult {
169 let fast_ema = ema_nan_aware(prices, fast_period)?;
172 let slow_ema = ema_nan_aware(prices, slow_period)?;
173 let mut macd_line = vec![f64::NAN; prices.len()];
174 for i in 0..prices.len() {
175 if !fast_ema[i].is_nan() && !slow_ema[i].is_nan() {
176 macd_line[i] = fast_ema[i] - slow_ema[i];
177 }
178 }
179 let signal_line = ema_nan_aware(&macd_line, signal_period)?;
183 let mut histogram = vec![f64::NAN; prices.len()];
184 for i in 0..prices.len() {
185 if !macd_line[i].is_nan() && !signal_line[i].is_nan() {
186 histogram[i] = macd_line[i] - signal_line[i];
187 }
188 }
189 Ok((macd_line, signal_line, histogram))
190}
191
192#[derive(Debug, Clone)]
201pub struct EMA {
202 period: usize,
203 alpha: f64,
204 value: f64,
205 initialized: bool,
206 warmup: VecDeque<f64>,
207}
208
209impl EMA {
210 pub fn new(period: usize) -> Self {
211 Self {
212 period,
213 alpha: 2.0 / (period as f64 + 1.0),
214 value: 0.0,
215 initialized: false,
216 warmup: VecDeque::with_capacity(period),
217 }
218 }
219
220 pub fn update(&mut self, price: f64) {
221 if !self.initialized {
222 self.warmup.push_back(price);
223 if self.warmup.len() >= self.period {
224 self.value = self.warmup.iter().sum::<f64>() / self.period as f64;
225 self.initialized = true;
226 self.warmup.clear();
227 }
228 } else {
229 self.value = price * self.alpha + self.value * (1.0 - self.alpha);
230 }
231 }
232
233 pub fn value(&self) -> f64 {
234 if self.initialized {
235 self.value
236 } else {
237 f64::NAN
238 }
239 }
240
241 pub fn is_ready(&self) -> bool {
242 self.initialized
243 }
244
245 pub fn reset(&mut self) {
246 self.value = 0.0;
247 self.initialized = false;
248 self.warmup.clear();
249 }
250}
251
252#[derive(Debug, Clone)]
254pub struct ATR {
255 #[allow(dead_code)]
256 period: usize,
257 ema: EMA,
258 prev_close: Option<f64>,
259}
260
261impl ATR {
262 pub fn new(period: usize) -> Self {
263 Self {
264 period,
265 ema: EMA::new(period),
266 prev_close: None,
267 }
268 }
269
270 pub fn update(&mut self, high: f64, low: f64, close: f64) {
271 let tr = if let Some(prev) = self.prev_close {
272 (high - low)
273 .max((high - prev).abs())
274 .max((low - prev).abs())
275 } else {
276 high - low
277 };
278 self.ema.update(tr);
279 self.prev_close = Some(close);
280 }
281
282 pub fn value(&self) -> f64 {
283 self.ema.value()
284 }
285
286 pub fn is_ready(&self) -> bool {
287 self.ema.is_ready()
288 }
289}
290
291#[derive(Debug, Clone)]
293pub struct StrategyIndicators {
294 pub ema_fast: Vec<f64>,
295 pub ema_slow: Vec<f64>,
296 pub atr: Vec<f64>,
297}
298
299#[derive(Debug, Clone)]
301pub struct IndicatorCalculator {
302 pub fast_ema_period: usize,
303 pub slow_ema_period: usize,
304 pub atr_period: usize,
305}
306
307impl Default for IndicatorCalculator {
308 fn default() -> Self {
309 Self {
310 fast_ema_period: 8,
311 slow_ema_period: 21,
312 atr_period: 14,
313 }
314 }
315}
316
317impl IndicatorCalculator {
318 pub fn new(fast_ema: usize, slow_ema: usize, atr_period: usize) -> Self {
319 Self {
320 fast_ema_period: fast_ema,
321 slow_ema_period: slow_ema,
322 atr_period,
323 }
324 }
325
326 pub fn calculate_all(
327 &self,
328 close: &[f64],
329 high: &[f64],
330 low: &[f64],
331 ) -> Result<StrategyIndicators, IndicatorError> {
332 Ok(StrategyIndicators {
333 ema_fast: ema(close, self.fast_ema_period)?,
334 ema_slow: ema(close, self.slow_ema_period)?,
335 atr: atr(high, low, close, self.atr_period)?,
336 })
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
345 fn test_ema_sma_seed() {
346 let prices = vec![22.27, 22.19, 22.08, 22.17, 22.18];
347 let result = ema(&prices, 5).unwrap();
348 let expected = (22.27 + 22.19 + 22.08 + 22.17 + 22.18) / 5.0;
349 assert!((result[4] - expected).abs() < 1e-9);
350 }
351
352 #[test]
353 fn test_true_range_first() {
354 let h = vec![50.0, 52.0];
355 let l = vec![48.0, 49.0];
356 let c = vec![49.0, 51.0];
357 let tr = true_range(&h, &l, &c).unwrap();
358 assert_eq!(tr[0], 2.0);
359 assert_eq!(tr[1], 3.0);
360 }
361
362 #[test]
363 fn test_ema_incremental() {
364 let mut e = EMA::new(3);
365 e.update(10.0);
366 assert!(!e.is_ready());
367 e.update(20.0);
368 assert!(!e.is_ready());
369 e.update(30.0);
370 assert!(e.is_ready());
371 assert!((e.value() - 20.0).abs() < 1e-9);
372 }
373}