egui_charts/studies/builtin/
rci_ribbon.rs1use super::RankCorrelationIndex;
20use crate::model::Bar;
21use crate::studies::palette;
22use crate::studies::stepper::{update_window, usable_behind};
23use crate::studies::{Indicator, IndicatorValues};
24use egui::Color32;
25
26#[derive(Clone)]
28pub struct RciRibbon {
29 lengths: [usize; 3],
30 values: IndicatorValues,
31 colors: Vec<Color32>,
33 visible: bool,
34}
35
36impl RciRibbon {
37 pub fn new(short: usize, middle: usize, long: usize) -> Self {
40 Self {
41 lengths: [short, middle, long].map(|length| length.max(2)),
42 values: IndicatorValues::new(),
43 colors: vec![palette::ACCENT, palette::RED, palette::GREEN],
44 visible: true,
45 }
46 }
47}
48
49impl Default for RciRibbon {
50 fn default() -> Self {
51 Self::new(10, 30, 50)
52 }
53}
54
55impl RciRibbon {
56 fn compute(&self, data: &[Bar]) -> IndicatorValues {
57 let lines = self.lengths.map(|length| {
58 let mut rci = RankCorrelationIndex::new(length);
59 rci.calculate(data);
60 rci
61 });
62 let mut values = IndicatorValues::new();
63 for bar in 0..data.len() {
64 values.push_lines(
65 &lines
66 .each_ref()
67 .map(|rci| rci.values().value(0, bar).unwrap_or(f64::NAN)),
68 );
69 }
70 values
71 }
72}
73
74impl Indicator for RciRibbon {
75 fn source(&self) -> Option<crate::studies::StudySource> {
76 Some(crate::studies::StudySource::Price(
77 crate::model::PriceSource::Close,
78 ))
79 }
80
81 crate::studies::inputs::study_inputs!(self;
82 lengths[0]: length("Short RCI length", 2, usize::MAX),
83 lengths[1]: length("Middle RCI length", 2, usize::MAX),
84 lengths[2]: length("Long RCI length", 2, usize::MAX)
85 ; {});
86
87 fn name(&self) -> &str {
88 "RCI Ribbon"
89 }
90
91 fn desc(&self) -> &str {
92 "RCI Ribbon - Rank correlation of price with time over three lookbacks [-100, 100]"
93 }
94
95 fn calculate(&mut self, data: &[Bar]) {
96 self.update(data, 0);
97 }
98
99 fn update(&mut self, data: &[Bar], from: usize) {
100 let mut values = std::mem::take(&mut self.values);
101 update_window(
102 &mut values,
103 data,
104 from,
105 usable_behind(self.lengths.into_iter().max().unwrap() - 1, |b| {
106 b.close.is_finite()
107 }),
108 |data| self.compute(data),
109 );
110 self.values = values;
111 }
112
113 fn is_incremental(&self) -> bool {
114 true
115 }
116
117 fn values(&self) -> &IndicatorValues {
118 &self.values
119 }
120
121 fn colors(&self) -> Vec<Color32> {
122 self.colors.clone()
123 }
124
125 fn set_colors(&mut self, colors: Vec<Color32>) {
126 if colors.len() >= 3 {
127 self.colors = colors;
128 }
129 }
130
131 fn pane(&self) -> crate::studies::StudyPane {
132 let gray = palette::GRAY;
133 crate::studies::StudyPane {
134 height: None,
135 range: Some((-100.0, 100.0)),
136 levels: vec![
137 (80.0, gray, "Upper Band"),
138 (0.0, gray, "Middle Band"),
139 (-80.0, gray, "Lower Band"),
140 ],
141 fill_between_levels: false,
142 }
143 }
144
145 fn is_overlay(&self) -> bool {
146 false
147 }
148
149 fn is_visible(&self) -> bool {
150 self.visible
151 }
152
153 fn set_visible(&mut self, visible: bool) {
154 self.visible = visible;
155 }
156
157 fn clone_box(&self) -> Box<dyn Indicator> {
158 Box::new(self.clone())
159 }
160
161 fn line_count(&self) -> usize {
162 3
163 }
164
165 fn line_names(&self) -> Vec<String> {
166 let [short, middle, long] = self.lengths;
167 vec![
168 format!("Short RCI({short})"),
169 format!("Middle RCI({middle})"),
170 format!("Long RCI({long})"),
171 ]
172 }
173}