Skip to main content

kestrel_chartkit/indicator/
efficiency.rs

1use std::collections::HashMap;
2
3use crate::indicator::{Indicator, IndicatorAlert, IndicatorOutput};
4use crate::model::Bar;
5
6/// Leg Efficiency Engine (Kaufman Efficiency Ratio & Noise Filter).
7/// Measures structural trend cleanliness vs. random market chop.
8#[derive(Debug, Clone)]
9pub struct LegEfficiencyEngine {
10    len: usize,
11    closes: Vec<f64>,
12    alerts: Vec<IndicatorAlert>,
13}
14
15impl LegEfficiencyEngine {
16    pub fn new(len: usize) -> Self {
17        Self {
18            len,
19            closes: Vec::with_capacity(len + 1),
20            alerts: Vec::new(),
21        }
22    }
23}
24
25impl Indicator for LegEfficiencyEngine {
26    fn name(&self) -> &str {
27        "efficiency"
28    }
29
30    fn warmup_period(&self) -> usize {
31        self.len
32    }
33
34    fn reset(&mut self) {
35        self.closes.clear();
36        self.alerts.clear();
37    }
38
39    fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
40        self.closes.push(bar.close);
41        if self.closes.len() > self.len + 1 {
42            self.closes.remove(0);
43        }
44
45        self.alerts.clear();
46
47        if self.closes.len() < self.len + 1 {
48            return None;
49        }
50
51        let change = (self.closes[self.closes.len() - 1] - self.closes[0]).abs();
52        let mut volatility = 0.0f64;
53        for i in 1..self.closes.len() {
54            volatility += (self.closes[i] - self.closes[i - 1]).abs();
55        }
56
57        let er = if volatility > 0.0 {
58            change / volatility
59        } else {
60            0.0
61        };
62
63        if er >= 0.65 {
64            self.alerts.push(IndicatorAlert::new(
65                "high_leg_efficiency",
66                format!(
67                    "High Trend Efficiency Ratio ({:.0}% Clean Move)",
68                    er * 100.0
69                ),
70                0.85,
71            ));
72        } else if er <= 0.20 {
73            self.alerts.push(IndicatorAlert::new(
74                "low_leg_efficiency",
75                format!("High Market Chop / Low Efficiency ({:.0}%)", er * 100.0),
76                0.70,
77            ));
78        }
79
80        Some(IndicatorOutput::new(er))
81    }
82
83    fn alerts(&self) -> Vec<IndicatorAlert> {
84        self.alerts.clone()
85    }
86}
87
88pub fn build_efficiency(params: &HashMap<String, f64>) -> LegEfficiencyEngine {
89    let len = params.get("len").copied().unwrap_or(14.0) as usize;
90    LegEfficiencyEngine::new(len)
91}