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