Skip to main content

egui_charts/studies/builtin/
rci_ribbon.rs

1//! RCI Ribbon: three Rank Correlation Index lines over short, middle and long lookbacks.
2//!
3//! Each line is [`RankCorrelationIndex`](super::RankCorrelationIndex): Spearman's rank correlation of
4//! the closes in the window with their bar positions, tied closes sharing the mean of their ranks,
5//! times 100. Rising prices give +100 and falling prices -100; a window of equal closes has no value.
6//! Lengths default to 10, 30 and 50, with levels at +80, 0 and -80.
7//!
8//! # Example
9//! ```
10//! # let bars: Vec<egui_charts::model::Bar> = (0..60).map(|i| { let p = 100.0 + (i as f64 * 0.3).sin() * 5.0; egui_charts::model::Bar::new(chrono::Utc::now(), p, p + 1.0, p - 1.0, p + 0.5, 1_000.0) }).collect();
11//! use egui_charts::studies::{Indicator, RciRibbon};
12//!
13//! let mut ribbon = RciRibbon::default();
14//! ribbon.calculate(&bars);
15//! assert!(ribbon.values().value(0, 9).is_some());
16//! assert!(ribbon.values().value(2, 49).is_some());
17//! ```
18
19use 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/// RCI Ribbon: short, middle and long Rank Correlation Index lines in a separate pane.
27#[derive(Clone)]
28pub struct RciRibbon {
29    lengths: [usize; 3],
30    values: IndicatorValues,
31    /// Colors: [Short, Middle, Long]
32    colors: Vec<Color32>,
33    visible: bool,
34}
35
36impl RciRibbon {
37    /// Create a ribbon with the short, middle and long lengths (10, 30 and 50 by convention). A length
38    /// below 2 is raised to 2, the shortest window a rank correlation exists for.
39    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}