Skip to main content

kestrel_chartkit/indicator/
mcginley.rs

1use super::{Indicator, IndicatorAlert, IndicatorOutput};
2use crate::model::Bar;
3
4/// McGinley Dynamic Moving Average Engine.
5/// McGinley Dynamic adapts its smoothing factor dynamically based on price speed relative to the average.
6#[derive(Debug, Clone)]
7pub struct McGinleyDynamicEngine {
8    period: usize,
9    state: Option<f64>,
10}
11
12impl McGinleyDynamicEngine {
13    pub fn new(period: usize) -> Self {
14        Self {
15            period: period.max(1),
16            state: None,
17        }
18    }
19}
20
21impl Indicator for McGinleyDynamicEngine {
22    fn name(&self) -> &str {
23        "mcginley"
24    }
25
26    fn warmup_period(&self) -> usize {
27        self.period
28    }
29
30    fn reset(&mut self) {
31        self.state = None;
32    }
33
34    fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
35        let price = bar.close;
36        let k = self.period as f64;
37
38        let next = match self.state {
39            None => price,
40            Some(prev) => {
41                let ratio = (price / prev.max(1e-8)).powi(4);
42                prev + (price - prev) / (k * ratio).max(1e-6)
43            }
44        };
45
46        self.state = Some(next);
47        Some(IndicatorOutput::new(next))
48    }
49
50    fn alerts(&self) -> Vec<IndicatorAlert> {
51        Vec::new()
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn test_mcginley_basic() {
61        let mut mg = McGinleyDynamicEngine::new(14);
62        let b1 = Bar::new(1, 100.0, 105.0, 95.0, 100.0, 1000.0);
63        let out1 = mg.on_bar(&b1).unwrap();
64        assert_eq!(out1.value, 100.0);
65    }
66}