egui_charts/studies/builtin/
coppock.rs1use crate::model::Bar;
2use crate::studies::helpers;
3use crate::studies::palette;
4use crate::studies::stepper::update_window;
5use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
6use egui::Color32;
7
8#[derive(Clone)]
12pub struct CoppockCurve {
13 long_roc: usize,
15 short_roc: usize,
17 wma_period: usize,
19 values: IndicatorValues,
20 color: Color32,
21 visible: bool,
22}
23
24impl CoppockCurve {
25 pub fn new() -> Self {
27 Self {
28 long_roc: 14,
29 short_roc: 11,
30 wma_period: 10,
31 values: IndicatorValues::new(),
32 color: palette::LIGHT_BLUE,
33 visible: true,
34 }
35 }
36
37 pub fn with_periods(mut self, long_roc: usize, short_roc: usize, wma: usize) -> Self {
39 self.long_roc = long_roc.max(1);
40 self.short_roc = short_roc.max(1);
41 self.wma_period = wma.max(1);
42 self
43 }
44
45 pub fn with_color(mut self, color: Color32) -> Self {
47 self.color = color;
48 self
49 }
50}
51
52impl Default for CoppockCurve {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl CoppockCurve {
59 fn compute(&self, data: &[Bar]) -> IndicatorValues {
60 let mut values = IndicatorValues::new();
61
62 let required = self
63 .long_roc
64 .max(self.short_roc)
65 .saturating_add(self.wma_period);
66 if data.len() < required {
67 for _ in 0..data.len() {
68 values.push(IndicatorValue::None);
69 }
70 return values;
71 }
72
73 let closes: Vec<f64> = data.iter().map(|b| b.close).collect();
75
76 let long_roc = helpers::calculate_roc(&closes, self.long_roc);
78 let short_roc = helpers::calculate_roc(&closes, self.short_roc);
79
80 let roc_sum: Vec<f64> = long_roc
82 .iter()
83 .zip(short_roc.iter())
84 .map(|(l, s)| {
85 if l.is_nan() || s.is_nan() {
86 f64::NAN
87 } else {
88 l + s
89 }
90 })
91 .collect();
92
93 let coppock = helpers::wma(&roc_sum, self.wma_period);
95
96 for value in coppock {
98 if value.is_nan() {
99 values.push(IndicatorValue::None);
100 } else {
101 values.push(IndicatorValue::Single(value));
102 }
103 }
104 values
105 }
106}
107
108impl Indicator for CoppockCurve {
109 fn source(&self) -> Option<crate::studies::StudySource> {
110 Some(crate::studies::StudySource::Price(
111 crate::model::PriceSource::Close,
112 ))
113 }
114
115 crate::studies::inputs::study_inputs!(self;
116 long_roc: length("Long roc", 1, usize::MAX),
117 short_roc: length("Short roc", 1, usize::MAX),
118 wma_period: length("Wma period", 1, usize::MAX)
119 ; {});
120
121 fn name(&self) -> &str {
122 "Coppock"
123 }
124
125 fn desc(&self) -> &str {
126 "Coppock Curve - Long-term momentum indicator"
127 }
128
129 fn calculate(&mut self, data: &[Bar]) {
130 self.update(data, 0);
131 }
132
133 fn update(&mut self, data: &[Bar], from: usize) {
134 let mut values = std::mem::take(&mut self.values);
135 update_window(
136 &mut values,
137 data,
138 from,
139 |_data, from| {
140 from.saturating_sub(
141 self.long_roc
142 .max(self.short_roc)
143 .saturating_add(self.wma_period - 1),
144 )
145 },
146 |data| self.compute(data),
147 );
148 self.values = values;
149 }
150
151 fn is_incremental(&self) -> bool {
152 true
153 }
154
155 fn values(&self) -> &IndicatorValues {
156 &self.values
157 }
158
159 fn colors(&self) -> Vec<Color32> {
160 vec![self.color]
161 }
162
163 fn set_colors(&mut self, colors: Vec<Color32>) {
164 if !colors.is_empty() {
165 self.color = colors[0];
166 }
167 }
168
169 fn is_overlay(&self) -> bool {
170 false }
172
173 fn is_visible(&self) -> bool {
174 self.visible
175 }
176
177 fn set_visible(&mut self, visible: bool) {
178 self.visible = visible;
179 }
180
181 fn clone_box(&self) -> Box<dyn Indicator> {
182 Box::new(self.clone())
183 }
184
185 fn line_names(&self) -> Vec<String> {
186 vec![format!(
187 "Coppock({},{},{})",
188 self.long_roc, self.short_roc, self.wma_period
189 )]
190 }
191}