kestrel_chartkit/indicator/
relative_strength.rs1use std::collections::HashMap;
2
3use crate::indicator::{Indicator, IndicatorAlert, IndicatorOutput};
4use crate::model::Bar;
5
6pub struct RelativeStrengthEngine {
8 lookback: usize,
9 own_closes: Vec<f64>,
10 bench_closes: Vec<f64>,
11 alerts: Vec<IndicatorAlert>,
12}
13
14impl RelativeStrengthEngine {
15 pub fn new(lookback: usize) -> Self {
16 Self {
17 lookback,
18 own_closes: Vec::new(),
19 bench_closes: Vec::new(),
20 alerts: Vec::new(),
21 }
22 }
23
24 pub fn update(&mut self, own_bar: &Bar, benchmark_bar: &Bar) -> Option<IndicatorOutput> {
26 self.own_closes.push(own_bar.close);
27 self.bench_closes.push(benchmark_bar.close);
28
29 if self.own_closes.len() > self.lookback + 1 {
30 self.own_closes.remove(0);
31 self.bench_closes.remove(0);
32 }
33
34 self.alerts.clear();
35 if self.own_closes.len() < self.lookback + 1 {
36 return None;
37 }
38
39 let curr_ratio = if benchmark_bar.close > 0.0 {
40 own_bar.close / benchmark_bar.close
41 } else {
42 1.0
43 };
44
45 let past_own = self.own_closes[0];
46 let past_bench = self.bench_closes[0];
47
48 let own_perf = if past_own > 0.0 {
49 (own_bar.close - past_own) / past_own
50 } else {
51 0.0
52 };
53 let bench_perf = if past_bench > 0.0 {
54 (benchmark_bar.close - past_bench) / past_bench
55 } else {
56 0.0
57 };
58
59 let alpha = (own_perf - bench_perf) * 100.0;
60
61 let mut extra = HashMap::new();
62 extra.insert("ratio".to_string(), curr_ratio);
63 extra.insert("own_perf_pct".to_string(), own_perf * 100.0);
64 extra.insert("bench_perf_pct".to_string(), bench_perf * 100.0);
65 extra.insert("alpha_pct".to_string(), alpha);
66
67 if alpha > 5.0 {
68 self.alerts.push(IndicatorAlert::new(
69 "outperforming_benchmark",
70 format!("Outperforming Benchmark by +{:.2}%", alpha),
71 0.85,
72 ));
73 } else if alpha < -5.0 {
74 self.alerts.push(IndicatorAlert::new(
75 "underperforming_benchmark",
76 format!("Underperforming Benchmark by {:.2}%", alpha),
77 0.85,
78 ));
79 }
80
81 Some(IndicatorOutput::with_extra(alpha, extra))
82 }
83}
84
85impl Indicator for RelativeStrengthEngine {
86 fn name(&self) -> &str {
87 "relative_strength"
88 }
89
90 fn warmup_period(&self) -> usize {
91 self.lookback + 1
92 }
93
94 fn reset(&mut self) {
95 self.own_closes.clear();
96 self.bench_closes.clear();
97 self.alerts.clear();
98 }
99
100 fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
101 self.update(bar, bar)
103 }
104
105 fn alerts(&self) -> Vec<IndicatorAlert> {
106 self.alerts.clone()
107 }
108}