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