kestrel_chartkit/indicator/
efficiency.rs1use std::collections::HashMap;
2
3use crate::indicator::{Indicator, IndicatorAlert, IndicatorOutput};
4use crate::model::Bar;
5
6#[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}