1use termichart_core::{Candle, Indicator, Point};
2
3pub struct SmaIndicator {
9 period: usize,
10 name: String,
11}
12
13impl SmaIndicator {
14 pub fn new(period: usize) -> Self {
15 Self {
16 period,
17 name: format!("SMA({})", period),
18 }
19 }
20}
21
22impl Indicator for SmaIndicator {
23 fn name(&self) -> &str {
24 &self.name
25 }
26
27 fn compute(&self, candles: &[Candle]) -> Vec<Point> {
28 if candles.len() < self.period || self.period == 0 {
29 return Vec::new();
30 }
31
32 let mut points = Vec::with_capacity(candles.len() - self.period + 1);
33
34 let mut sum: f64 = candles[..self.period].iter().map(|c| c.close).sum();
36 points.push(Point {
37 x: candles[self.period - 1].time,
38 y: sum / self.period as f64,
39 });
40
41 for i in self.period..candles.len() {
43 sum += candles[i].close - candles[i - self.period].close;
44 points.push(Point {
45 x: candles[i].time,
46 y: sum / self.period as f64,
47 });
48 }
49
50 points
51 }
52}
53
54pub struct EmaIndicator {
60 period: usize,
61 name: String,
62}
63
64impl EmaIndicator {
65 pub fn new(period: usize) -> Self {
66 Self {
67 period,
68 name: format!("EMA({})", period),
69 }
70 }
71}
72
73impl Indicator for EmaIndicator {
74 fn name(&self) -> &str {
75 &self.name
76 }
77
78 fn compute(&self, candles: &[Candle]) -> Vec<Point> {
79 if candles.len() < self.period || self.period == 0 {
80 return Vec::new();
81 }
82
83 let multiplier = 2.0 / (self.period as f64 + 1.0);
84 let mut points = Vec::with_capacity(candles.len() - self.period + 1);
85
86 let sma: f64 =
88 candles[..self.period].iter().map(|c| c.close).sum::<f64>() / self.period as f64;
89 let mut prev_ema = sma;
90
91 points.push(Point {
92 x: candles[self.period - 1].time,
93 y: prev_ema,
94 });
95
96 for candle in &candles[self.period..] {
98 let ema = (candle.close - prev_ema) * multiplier + prev_ema;
99 points.push(Point {
100 x: candle.time,
101 y: ema,
102 });
103 prev_ema = ema;
104 }
105
106 points
107 }
108}
109
110pub struct VwapIndicator;
116
117impl VwapIndicator {
118 pub fn new() -> Self {
119 Self
120 }
121}
122
123impl Default for VwapIndicator {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129impl Indicator for VwapIndicator {
130 fn name(&self) -> &str {
131 "VWAP"
132 }
133
134 fn compute(&self, candles: &[Candle]) -> Vec<Point> {
135 let mut points = Vec::with_capacity(candles.len());
136 let mut cumulative_tp_vol = 0.0_f64;
137 let mut cumulative_vol = 0.0_f64;
138
139 for candle in candles {
140 let typical_price = (candle.high + candle.low + candle.close) / 3.0;
141 cumulative_tp_vol += typical_price * candle.volume;
142 cumulative_vol += candle.volume;
143
144 let vwap = if cumulative_vol != 0.0 {
145 cumulative_tp_vol / cumulative_vol
146 } else {
147 0.0
148 };
149
150 points.push(Point {
151 x: candle.time,
152 y: vwap,
153 });
154 }
155
156 points
157 }
158}
159
160pub struct BollingerBandsIndicator {
166 period: usize,
167 std_dev_mult: f64,
168 name: String,
169}
170
171impl BollingerBandsIndicator {
172 pub fn new(period: usize, std_dev_mult: f64) -> Self {
173 Self {
174 period,
175 std_dev_mult,
176 name: format!("BB({},{})", period, std_dev_mult),
177 }
178 }
179
180 pub fn compute_bands(&self, candles: &[Candle]) -> (Vec<Point>, Vec<Point>, Vec<Point>) {
182 if candles.len() < self.period || self.period == 0 {
183 return (Vec::new(), Vec::new(), Vec::new());
184 }
185
186 let mut middle = Vec::with_capacity(candles.len() - self.period + 1);
187 let mut upper = Vec::with_capacity(candles.len() - self.period + 1);
188 let mut lower = Vec::with_capacity(candles.len() - self.period + 1);
189
190 for i in (self.period - 1)..candles.len() {
191 let start = i + 1 - self.period;
192 let slice = &candles[start..=i];
193 let mean: f64 = slice.iter().map(|c| c.close).sum::<f64>() / self.period as f64;
194 let variance: f64 =
195 slice.iter().map(|c| (c.close - mean).powi(2)).sum::<f64>() / self.period as f64;
196 let std_dev = variance.sqrt();
197
198 let time = candles[i].time;
199 middle.push(Point { x: time, y: mean });
200 upper.push(Point {
201 x: time,
202 y: mean + self.std_dev_mult * std_dev,
203 });
204 lower.push(Point {
205 x: time,
206 y: mean - self.std_dev_mult * std_dev,
207 });
208 }
209
210 (middle, upper, lower)
211 }
212}
213
214impl Indicator for BollingerBandsIndicator {
215 fn name(&self) -> &str {
216 &self.name
217 }
218
219 fn compute(&self, candles: &[Candle]) -> Vec<Point> {
221 let (middle, _, _) = self.compute_bands(candles);
222 middle
223 }
224}
225
226pub struct RsiIndicator {
232 period: usize,
233 name: String,
234}
235
236impl RsiIndicator {
237 pub fn new(period: usize) -> Self {
238 Self {
239 period,
240 name: format!("RSI({})", period),
241 }
242 }
243}
244
245impl Indicator for RsiIndicator {
246 fn name(&self) -> &str {
247 &self.name
248 }
249
250 fn compute(&self, candles: &[Candle]) -> Vec<Point> {
251 if candles.len() < self.period + 1 || self.period == 0 {
252 return Vec::new();
253 }
254
255 let mut points = Vec::with_capacity(candles.len() - self.period);
256
257 let mut avg_gain = 0.0_f64;
259 let mut avg_loss = 0.0_f64;
260
261 for i in 1..=self.period {
262 let change = candles[i].close - candles[i - 1].close;
263 if change > 0.0 {
264 avg_gain += change;
265 } else {
266 avg_loss += change.abs();
267 }
268 }
269
270 avg_gain /= self.period as f64;
271 avg_loss /= self.period as f64;
272
273 let rs = if avg_loss == 0.0 {
274 100.0
275 } else {
276 avg_gain / avg_loss
277 };
278 let rsi = 100.0 - (100.0 / (1.0 + rs));
279 points.push(Point {
280 x: candles[self.period].time,
281 y: rsi,
282 });
283
284 for i in (self.period + 1)..candles.len() {
286 let change = candles[i].close - candles[i - 1].close;
287 let (gain, loss) = if change > 0.0 {
288 (change, 0.0)
289 } else {
290 (0.0, change.abs())
291 };
292
293 avg_gain = (avg_gain * (self.period as f64 - 1.0) + gain) / self.period as f64;
294 avg_loss = (avg_loss * (self.period as f64 - 1.0) + loss) / self.period as f64;
295
296 let rs = if avg_loss == 0.0 {
297 100.0
298 } else {
299 avg_gain / avg_loss
300 };
301 let rsi = 100.0 - (100.0 / (1.0 + rs));
302 points.push(Point {
303 x: candles[i].time,
304 y: rsi,
305 });
306 }
307
308 points
309 }
310}
311
312pub struct MacdIndicator {
318 fast_period: usize,
319 slow_period: usize,
320 signal_period: usize,
321 name: String,
322}
323
324impl MacdIndicator {
325 pub fn new(fast: usize, slow: usize, signal: usize) -> Self {
326 Self {
327 fast_period: fast,
328 slow_period: slow,
329 signal_period: signal,
330 name: format!("MACD({},{},{})", fast, slow, signal),
331 }
332 }
333
334 pub fn compute_full(&self, candles: &[Candle]) -> (Vec<Point>, Vec<Point>, Vec<Point>) {
336 let fast_ema = EmaIndicator::new(self.fast_period);
337 let slow_ema = EmaIndicator::new(self.slow_period);
338
339 let fast_points = fast_ema.compute(candles);
340 let slow_points = slow_ema.compute(candles);
341
342 if fast_points.is_empty() || slow_points.is_empty() {
343 return (Vec::new(), Vec::new(), Vec::new());
344 }
345
346 let mut macd_line = Vec::new();
348 let mut slow_idx = 0;
349 for fp in &fast_points {
350 while slow_idx < slow_points.len() && slow_points[slow_idx].x < fp.x {
351 slow_idx += 1;
352 }
353 if slow_idx < slow_points.len() && (slow_points[slow_idx].x - fp.x).abs() < 0.001 {
354 macd_line.push(Point {
355 x: fp.x,
356 y: fp.y - slow_points[slow_idx].y,
357 });
358 }
359 }
360
361 if macd_line.len() < self.signal_period {
362 return (macd_line, Vec::new(), Vec::new());
363 }
364
365 let multiplier = 2.0 / (self.signal_period as f64 + 1.0);
367 let sma: f64 = macd_line[..self.signal_period]
368 .iter()
369 .map(|p| p.y)
370 .sum::<f64>()
371 / self.signal_period as f64;
372 let mut prev_signal = sma;
373
374 let mut signal_line = Vec::with_capacity(macd_line.len() - self.signal_period + 1);
375 signal_line.push(Point {
376 x: macd_line[self.signal_period - 1].x,
377 y: prev_signal,
378 });
379
380 for p in &macd_line[self.signal_period..] {
381 let signal = (p.y - prev_signal) * multiplier + prev_signal;
382 signal_line.push(Point {
383 x: p.x,
384 y: signal,
385 });
386 prev_signal = signal;
387 }
388
389 let mut histogram = Vec::new();
391 let mut sig_idx = 0;
392 for mp in &macd_line {
393 while sig_idx < signal_line.len() && signal_line[sig_idx].x < mp.x {
394 sig_idx += 1;
395 }
396 if sig_idx < signal_line.len() && (signal_line[sig_idx].x - mp.x).abs() < 0.001 {
397 histogram.push(Point {
398 x: mp.x,
399 y: mp.y - signal_line[sig_idx].y,
400 });
401 }
402 }
403
404 (macd_line, signal_line, histogram)
405 }
406}
407
408impl Indicator for MacdIndicator {
409 fn name(&self) -> &str {
410 &self.name
411 }
412
413 fn compute(&self, candles: &[Candle]) -> Vec<Point> {
415 let (macd_line, _, _) = self.compute_full(candles);
416 macd_line
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 fn sample_candles() -> Vec<Candle> {
425 vec![
426 Candle { time: 1.0, open: 10.0, high: 12.0, low: 9.0, close: 11.0, volume: 100.0 },
427 Candle { time: 2.0, open: 11.0, high: 13.0, low: 10.0, close: 12.0, volume: 150.0 },
428 Candle { time: 3.0, open: 12.0, high: 14.0, low: 11.0, close: 13.0, volume: 200.0 },
429 Candle { time: 4.0, open: 13.0, high: 15.0, low: 12.0, close: 14.0, volume: 120.0 },
430 Candle { time: 5.0, open: 14.0, high: 16.0, low: 13.0, close: 15.0, volume: 180.0 },
431 ]
432 }
433
434 #[test]
435 fn sma_basic() {
436 let candles = sample_candles();
437 let sma = SmaIndicator::new(3);
438 let points = sma.compute(&candles);
439 assert_eq!(points.len(), 3);
441 assert!((points[0].y - 12.0).abs() < 1e-10);
442 assert_eq!(points[0].x, 3.0);
443 assert!((points[1].y - 13.0).abs() < 1e-10);
445 assert!((points[2].y - 14.0).abs() < 1e-10);
447 }
448
449 #[test]
450 fn sma_insufficient_data() {
451 let candles = sample_candles();
452 let sma = SmaIndicator::new(10);
453 assert!(sma.compute(&candles).is_empty());
454 }
455
456 #[test]
457 fn ema_basic() {
458 let candles = sample_candles();
459 let ema = EmaIndicator::new(3);
460 let points = ema.compute(&candles);
461 assert_eq!(points.len(), 3);
462 assert!((points[0].y - 12.0).abs() < 1e-10);
464 assert!((points[1].y - 13.0).abs() < 1e-10);
467 assert!((points[2].y - 14.0).abs() < 1e-10);
469 }
470
471 #[test]
472 fn ema_name() {
473 let ema = EmaIndicator::new(20);
474 assert_eq!(ema.name(), "EMA(20)");
475 }
476
477 #[test]
478 fn vwap_basic() {
479 let candles = sample_candles();
480 let vwap = VwapIndicator::new();
481 let points = vwap.compute(&candles);
482 assert_eq!(points.len(), 5);
483
484 let tp1 = (12.0 + 9.0 + 11.0) / 3.0;
487 assert!((points[0].y - tp1).abs() < 1e-10);
488 }
489
490 #[test]
491 fn vwap_name() {
492 let vwap = VwapIndicator::new();
493 assert_eq!(vwap.name(), "VWAP");
494 }
495
496 #[test]
497 fn bb_basic() {
498 let candles = sample_candles();
499 let bb = BollingerBandsIndicator::new(3, 2.0);
500 let (middle, upper, lower) = bb.compute_bands(&candles);
501 assert_eq!(middle.len(), 3);
502 assert_eq!(upper.len(), 3);
503 assert_eq!(lower.len(), 3);
504 assert!((middle[0].y - 12.0).abs() < 1e-10);
506 assert!(upper[0].y > middle[0].y);
508 assert!(lower[0].y < middle[0].y);
509 }
510
511 #[test]
512 fn rsi_basic() {
513 let candles = sample_candles();
514 let rsi = RsiIndicator::new(3);
515 let points = rsi.compute(&candles);
516 assert!(!points.is_empty());
517 for p in &points {
519 assert!(p.y >= 0.0 && p.y <= 100.0);
520 }
521 }
522
523 #[test]
524 fn rsi_all_gains() {
525 let candles: Vec<Candle> = (0..10)
527 .map(|i| Candle {
528 time: i as f64,
529 open: i as f64,
530 high: i as f64 + 1.0,
531 low: i as f64,
532 close: i as f64 + 1.0,
533 volume: 100.0,
534 })
535 .collect();
536 let rsi = RsiIndicator::new(3);
537 let points = rsi.compute(&candles);
538 if let Some(last) = points.last() {
539 assert!(last.y > 90.0);
540 }
541 }
542
543 #[test]
544 fn macd_basic() {
545 let candles: Vec<Candle> = (0..30)
547 .map(|i| Candle {
548 time: i as f64,
549 open: 100.0 + (i as f64).sin() * 5.0,
550 high: 105.0 + (i as f64).sin() * 5.0,
551 low: 95.0 + (i as f64).sin() * 5.0,
552 close: 100.0 + (i as f64).sin() * 5.0 + 0.5,
553 volume: 100.0,
554 })
555 .collect();
556 let macd = MacdIndicator::new(12, 26, 9);
557 let (macd_line, _signal, _hist) = macd.compute_full(&candles);
558 assert!(!macd_line.is_empty());
560 }
561
562 #[test]
563 fn macd_name() {
564 let macd = MacdIndicator::new(12, 26, 9);
565 assert_eq!(macd.name(), "MACD(12,26,9)");
566 }
567
568 #[test]
569 fn bb_insufficient_data() {
570 let candles = vec![sample_candles()[0]];
571 let bb = BollingerBandsIndicator::new(5, 2.0);
572 let (m, u, l) = bb.compute_bands(&candles);
573 assert!(m.is_empty());
574 assert!(u.is_empty());
575 assert!(l.is_empty());
576 }
577}