Skip to main content

egui_charts/studies/builtin/
rank_correlation.rs

1use super::over_finite_bars;
2use crate::model::Bar;
3use crate::studies::palette;
4use crate::studies::stepper::{update_window, usable_behind};
5use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
6use egui::Color32;
7
8/// Rank Correlation Index (Spearman) - rank correlation of prices vs time index
9/// over a rolling window, scaled to [-100, 100].
10#[derive(Clone)]
11pub struct RankCorrelationIndex {
12    period: usize,
13    values: IndicatorValues,
14    colors: Vec<Color32>,
15    visible: bool,
16}
17
18impl RankCorrelationIndex {
19    /// Create with a lookback of `period` bars, clamped to at least 2.
20    pub fn new(period: usize) -> Self {
21        Self {
22            period: period.max(2),
23            values: IndicatorValues::new(),
24            colors: vec![palette::BLUE],
25            visible: true,
26        }
27    }
28
29    /// Fill average ranks for ties, reusing the sorting and output buffers.
30    fn rank(data: &[Bar], indexed: &mut Vec<(usize, f64)>, ranks: &mut [f64]) {
31        indexed.clear();
32        indexed.extend(data.iter().enumerate().map(|(i, b)| (i, b.close)));
33        indexed.sort_unstable_by(|a, b| a.1.total_cmp(&b.1));
34        let mut i = 0;
35        while i < indexed.len() {
36            let mut end = i + 1;
37            while end < indexed.len() && indexed[end].1 == indexed[i].1 {
38                end += 1;
39            }
40            let rank = (i + end + 1) as f64 / 2.0;
41            for &(at, _) in &indexed[i..end] {
42                ranks[at] = rank;
43            }
44            i = end;
45        }
46    }
47}
48
49impl Default for RankCorrelationIndex {
50    fn default() -> Self {
51        Self::new(14)
52    }
53}
54
55impl RankCorrelationIndex {
56    /// The values over `data`, every bar of which has finite inputs.
57    fn compute(&self, data: &[Bar]) -> IndicatorValues {
58        let mut values = IndicatorValues::new();
59        if data.is_empty() {
60            return values;
61        }
62
63        let mut indexed = Vec::with_capacity(self.period.min(data.len()));
64        let mut ranks = vec![0.0; self.period.min(data.len())];
65        for i in 0..data.len() {
66            if i + 1 < self.period {
67                values.push(IndicatorValue::None);
68                continue;
69            }
70
71            let start = i + 1 - self.period;
72            Self::rank(&data[start..=i], &mut indexed, &mut ranks);
73            let n = self.period as f64;
74            let mean = (n + 1.0) / 2.0;
75            let covariance: f64 = ranks
76                .iter()
77                .enumerate()
78                .map(|(i, rank)| (i as f64 + 1.0 - mean) * (rank - mean))
79                .sum();
80            let variance: f64 = ranks.iter().map(|rank| (rank - mean).powi(2)).sum();
81            if variance == 0.0 {
82                values.push(IndicatorValue::None);
83                continue;
84            }
85            // Spearman is Pearson correlation of the ranks, including ties.
86            let spearman = covariance / (variance * n * (n * n - 1.0) / 12.0).sqrt();
87
88            // Scale to [-100, 100]
89            values.push(IndicatorValue::Single(
90                (spearman * 100.0).clamp(-100.0, 100.0),
91            ));
92        }
93
94        values
95    }
96}
97
98impl Indicator for RankCorrelationIndex {
99    fn source(&self) -> Option<crate::studies::StudySource> {
100        Some(crate::studies::StudySource::Price(
101            crate::model::PriceSource::Close,
102        ))
103    }
104
105    crate::studies::inputs::study_inputs!(self;
106        period: length("Period", 2, usize::MAX)
107    ; {});
108
109    fn name(&self) -> &str {
110        "RankCorr"
111    }
112
113    fn desc(&self) -> &str {
114        "Rank Correlation Index (Spearman) - Price vs time rank correlation [-100, 100]"
115    }
116
117    fn calculate(&mut self, data: &[Bar]) {
118        self.update(data, 0);
119    }
120
121    fn update(&mut self, data: &[Bar], from: usize) {
122        let mut values = std::mem::take(&mut self.values);
123        update_window(
124            &mut values,
125            data,
126            from,
127            usable_behind(self.period - 1, |b| b.close.is_finite()),
128            |data| over_finite_bars(data, |b| b.close.is_finite(), |data| self.compute(data)),
129        );
130        self.values = values;
131    }
132
133    fn is_incremental(&self) -> bool {
134        true
135    }
136
137    fn values(&self) -> &IndicatorValues {
138        &self.values
139    }
140
141    fn colors(&self) -> Vec<Color32> {
142        self.colors.clone()
143    }
144
145    fn set_colors(&mut self, colors: Vec<Color32>) {
146        if !colors.is_empty() {
147            self.colors = colors;
148        }
149    }
150
151    fn is_overlay(&self) -> bool {
152        false
153    }
154
155    fn is_visible(&self) -> bool {
156        self.visible
157    }
158
159    fn set_visible(&mut self, visible: bool) {
160        self.visible = visible;
161    }
162
163    fn clone_box(&self) -> Box<dyn Indicator> {
164        Box::new(self.clone())
165    }
166
167    fn line_names(&self) -> Vec<String> {
168        vec![format!("RankCorr({})", self.period)]
169    }
170}