egui_charts/studies/builtin/
center_of_gravity.rs1use crate::model::Bar;
2use crate::studies::palette;
3use crate::studies::stepper::update_window;
4use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
5use egui::Color32;
6
7#[derive(Clone)]
11pub struct CenterOfGravity {
12 period: usize,
14 values: IndicatorValues,
15 colors: Vec<Color32>,
17 visible: bool,
18}
19
20impl CenterOfGravity {
21 pub fn new(period: usize) -> Self {
23 Self {
24 period: period.max(1),
25 values: IndicatorValues::new(),
26 colors: vec![
27 palette::UP, palette::ORANGE, ],
30 visible: true,
31 }
32 }
33
34 pub fn with_colors(mut self, colors: Vec<Color32>) -> Self {
36 self.colors = colors;
37 self
38 }
39}
40
41impl Default for CenterOfGravity {
42 fn default() -> Self {
43 Self::new(10)
44 }
45}
46
47impl CenterOfGravity {
48 fn compute(&self, data: &[Bar]) -> IndicatorValues {
49 let mut values = IndicatorValues::new();
50
51 if data.len() < self.period {
52 for _ in 0..data.len() {
53 values.push(IndicatorValue::None);
54 }
55 return values;
56 }
57
58 let closes: Vec<f64> = data.iter().map(|b| b.close).collect();
60
61 let mut cog_values = Vec::with_capacity(data.len());
62 let mut prev_cog = 0.0;
63
64 for i in 0..data.len() {
65 if i < self.period - 1 {
66 cog_values.push((f64::NAN, f64::NAN));
67 continue;
68 }
69
70 let window = &closes[i + 1 - self.period..=i];
71
72 let mut num = 0.0;
74 let mut den = 0.0;
75
76 for (j, &price) in window.iter().enumerate() {
77 let weight = (self.period - j) as f64;
78 num += weight * price;
79 den += price;
80 }
81
82 let cog = if den != 0.0 { -num / den } else { 0.0 };
83 let signal = if i + 1 == self.period {
85 f64::NAN
86 } else {
87 prev_cog
88 };
89
90 prev_cog = cog;
91 cog_values.push((cog, signal));
92 }
93
94 for (cog, signal) in &cog_values {
96 if cog.is_nan() {
97 values.push(IndicatorValue::None);
98 } else {
99 values.push_lines(&[*cog, *signal]);
100 }
101 }
102 values
103 }
104}
105
106impl Indicator for CenterOfGravity {
107 fn source(&self) -> Option<crate::studies::StudySource> {
108 Some(crate::studies::StudySource::Price(
109 crate::model::PriceSource::Close,
110 ))
111 }
112
113 crate::studies::inputs::study_inputs!(self;
114 period: length("Period", 1, usize::MAX)
115 ; {});
116
117 fn name(&self) -> &str {
118 "COG"
119 }
120
121 fn desc(&self) -> &str {
122 "Center of Gravity - Identifies price reversal points"
123 }
124
125 fn calculate(&mut self, data: &[Bar]) {
126 self.update(data, 0);
127 }
128
129 fn update(&mut self, data: &[Bar], from: usize) {
130 let mut values = std::mem::take(&mut self.values);
131 update_window(
132 &mut values,
133 data,
134 from,
135 |_data, from| from.saturating_sub(self.period),
136 |data| self.compute(data),
137 );
138 self.values = values;
139 }
140
141 fn is_incremental(&self) -> bool {
142 true
143 }
144
145 fn values(&self) -> &IndicatorValues {
146 &self.values
147 }
148
149 fn colors(&self) -> Vec<Color32> {
150 self.colors.clone()
151 }
152
153 fn set_colors(&mut self, colors: Vec<Color32>) {
154 if colors.len() >= 2 {
155 self.colors = colors;
156 }
157 }
158
159 fn is_overlay(&self) -> bool {
160 false
161 }
162
163 fn is_visible(&self) -> bool {
164 self.visible
165 }
166
167 fn set_visible(&mut self, visible: bool) {
168 self.visible = visible;
169 }
170
171 fn clone_box(&self) -> Box<dyn Indicator> {
172 Box::new(self.clone())
173 }
174
175 fn line_names(&self) -> Vec<String> {
176 vec![format!("COG({})", self.period), "Signal".to_string()]
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::studies::test_support::make_bar;
184
185 #[test]
186 fn test_cog_calculation() {
187 let mut cog = CenterOfGravity::new(5);
188
189 let data = vec![
190 make_bar(100.0),
191 make_bar(102.0),
192 make_bar(104.0),
193 make_bar(103.0),
194 make_bar(105.0),
195 make_bar(107.0),
196 make_bar(106.0),
197 ];
198
199 cog.calculate(&data);
200
201 assert_eq!(cog.values.len(), 7);
202
203 let valid_cnt = cog
205 .values
206 .to_rows()
207 .iter()
208 .filter(|v| !matches!(v, IndicatorValue::None))
209 .count();
210
211 assert!(valid_cnt > 0);
212 }
213}