Skip to main content

trading_toolkit/indicator/
macd.rs

1use super::MovingAverage;
2use crate::types::{data::BaseData, error::ToolkitError};
3
4#[derive(Debug, Clone, Copy)]
5pub struct MovingAverageConvergenceDivergence {
6    signal: f64,
7    ema_12: f64,
8    ema_26: f64,
9}
10
11impl MovingAverageConvergenceDivergence {
12    pub fn new<T>(data: &[T]) -> Result<Self, ToolkitError>
13    where
14        T: BaseData + Clone,
15    {
16        if data.len() < 34 {
17            return Err(ToolkitError::DataNotEnough);
18        }
19        let mut data = data.to_vec();
20        data.sort_by_key(|k| k.epoch_time());
21        let n = data.len();
22
23        // MACD 값을 BaseData로 wrapping하여 MovingAverage에 활용
24        #[derive(Debug, Clone, Copy)]
25        struct MacdPoint(f64, u128);
26        impl BaseData for MacdPoint {
27            fn value(&self) -> f64 {
28                self.0
29            }
30            fn weight(&self) -> u64 {
31                1
32            }
33            fn epoch_time(&self) -> u128 {
34                self.1
35            }
36        }
37
38        // EMA(26): 첫 26개 SMA로 seed → bar 26부터 rolling
39        let mut ema26 = MovingAverage::simple(&data[0..26]);
40        // EMA(12): 첫 12개 SMA로 seed → bar 25까지 rolling (ema26과 기준 bar 맞춤)
41        let mut ema12 = MovingAverage::simple(&data[0..12]);
42        for i in 12..26 {
43            ema12 = MovingAverage::exponential_from(12, &ema12, &data[i]);
44        }
45
46        // bar 25 기준 첫 번째 MACD 값
47        let mut macd_for_seed = vec![MacdPoint(
48            ema12.inner() - ema26.inner(),
49            data[25].epoch_time(),
50        )];
51
52        // bars 26..33: MACD seed용 9개 수집 (총 9개)
53        for i in 26..34 {
54            ema12 = MovingAverage::exponential_from(12, &ema12, &data[i]);
55            ema26 = MovingAverage::exponential_from(26, &ema26, &data[i]);
56            macd_for_seed.push(MacdPoint(
57                ema12.inner() - ema26.inner(),
58                data[i].epoch_time(),
59            ));
60        }
61
62        // Signal: 첫 9개 MACD의 SMA로 seed → bar 34부터 rolling EMA(9)
63        let mut signal = MovingAverage::simple(&macd_for_seed);
64        for i in 34..n {
65            ema12 = MovingAverage::exponential_from(12, &ema12, &data[i]);
66            ema26 = MovingAverage::exponential_from(26, &ema26, &data[i]);
67            let macd_point = MacdPoint(ema12.inner() - ema26.inner(), data[i].epoch_time());
68            signal = MovingAverage::exponential_from(9, &signal, &macd_point);
69        }
70
71        Ok(Self {
72            signal: signal.inner(),
73            ema_12: ema12.inner(),
74            ema_26: ema26.inner(),
75        })
76    }
77
78    /// fast line / MACD line
79    pub fn fast(&self) -> f64 {
80        self.ema_12 - self.ema_26
81    }
82
83    /// slow line / Signal
84    pub fn slow(&self) -> f64 {
85        self.signal
86    }
87
88    /// MACD histogram
89    pub fn macd_histogram(&self) -> f64 {
90        self.fast() - self.slow()
91    }
92}