egui_charts/studies/builtin/
chande_momentum.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 ChandeMomentumOscillator {
12 period: usize,
14 values: IndicatorValues,
15 color: Color32,
16 visible: bool,
17}
18
19impl ChandeMomentumOscillator {
20 pub fn new(period: usize) -> Self {
22 Self {
23 period: period.max(1),
24 values: IndicatorValues::new(),
25 color: palette::BLUE,
26 visible: true,
27 }
28 }
29
30 pub fn with_color(mut self, color: Color32) -> Self {
32 self.color = color;
33 self
34 }
35}
36
37impl Default for ChandeMomentumOscillator {
38 fn default() -> Self {
39 Self::new(9)
40 }
41}
42
43impl ChandeMomentumOscillator {
44 fn compute(&self, data: &[Bar]) -> IndicatorValues {
45 let mut values = IndicatorValues::new();
46
47 if data.len() < self.period.saturating_add(1) {
48 for _ in 0..data.len() {
49 values.push(IndicatorValue::None);
50 }
51 return values;
52 }
53
54 values.push(IndicatorValue::None);
56
57 let mut gains = Vec::with_capacity(data.len() - 1);
59 let mut losses = Vec::with_capacity(data.len() - 1);
60
61 for i in 1..data.len() {
62 let change = data[i].close - data[i - 1].close;
63 if change > 0.0 {
64 gains.push(change);
65 losses.push(0.0);
66 } else {
67 gains.push(0.0);
68 losses.push((-change).abs());
69 }
70 }
71
72 for i in 0..gains.len() {
73 if i < self.period - 1 {
74 values.push(IndicatorValue::None);
75 } else {
76 let sum_up: f64 = gains[i + 1 - self.period..=i].iter().sum();
77 let sum_down: f64 = losses[i + 1 - self.period..=i].iter().sum();
78
79 let cmo = if sum_up + sum_down != 0.0 {
80 100.0 * (sum_up - sum_down) / (sum_up + sum_down)
81 } else {
82 0.0
83 };
84
85 values.push(IndicatorValue::Single(cmo));
86 }
87 }
88 values
89 }
90}
91
92impl Indicator for ChandeMomentumOscillator {
93 fn source(&self) -> Option<crate::studies::StudySource> {
94 Some(crate::studies::StudySource::Price(
95 crate::model::PriceSource::Close,
96 ))
97 }
98
99 crate::studies::inputs::study_inputs!(self;
100 period: length("Period", 1, usize::MAX)
101 ; {});
102
103 fn name(&self) -> &str {
104 "CMO"
105 }
106
107 fn desc(&self) -> &str {
108 "Chande Momentum Oscillator - Measures momentum"
109 }
110
111 fn calculate(&mut self, data: &[Bar]) {
112 self.update(data, 0);
113 }
114
115 fn update(&mut self, data: &[Bar], from: usize) {
116 let mut values = std::mem::take(&mut self.values);
117 update_window(
118 &mut values,
119 data,
120 from,
121 |_data, from| from.saturating_sub(self.period),
122 |data| self.compute(data),
123 );
124 self.values = values;
125 }
126
127 fn is_incremental(&self) -> bool {
128 true
129 }
130
131 fn values(&self) -> &IndicatorValues {
132 &self.values
133 }
134
135 fn colors(&self) -> Vec<Color32> {
136 vec![self.color]
137 }
138
139 fn set_colors(&mut self, colors: Vec<Color32>) {
140 if !colors.is_empty() {
141 self.color = colors[0];
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_names(&self) -> Vec<String> {
162 vec![format!("CMO({})", self.period)]
163 }
164}