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