egui_charts/studies/builtin/
rank_correlation.rs1use 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#[derive(Clone)]
11pub struct RankCorrelationIndex {
12 period: usize,
13 values: IndicatorValues,
14 colors: Vec<Color32>,
15 visible: bool,
16}
17
18impl RankCorrelationIndex {
19 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 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 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 let spearman = covariance / (variance * n * (n * n - 1.0) / 12.0).sqrt();
87
88 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}