egui_charts/studies/builtin/
price_momentum_oscillator.rs1use crate::model::Bar;
33use crate::studies::palette;
34use crate::studies::stepper::Stepper;
35use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
36use egui::Color32;
37
38#[derive(Clone)]
40pub struct PriceMomentumOscillator {
41 first_length: usize,
42 second_length: usize,
43 signal_length: usize,
44 values: IndicatorValues,
45 stepper: Stepper<Option<State>>,
46 colors: Vec<Color32>,
48 visible: bool,
49}
50
51#[derive(Clone)]
52struct State {
53 previous: Option<f64>,
54 first: Smoothing,
55 second: Smoothing,
56 signal: Smoothing,
57}
58
59#[derive(Clone)]
61struct Smoothing {
62 length: usize,
63 alpha: f64,
64 seen: usize,
65 value: f64,
66}
67
68impl Smoothing {
69 fn new(length: usize, alpha: f64) -> Self {
70 Self {
71 length,
72 alpha,
73 seen: 0,
74 value: 0.0,
75 }
76 }
77
78 fn push(&mut self, x: f64) -> Option<f64> {
79 if self.seen < self.length {
80 self.seen += 1;
81 self.value += (x - self.value) / self.seen as f64;
82 return (self.seen == self.length).then_some(self.value);
83 }
84 self.value += (x - self.value) * self.alpha;
85 Some(self.value)
86 }
87}
88
89impl PriceMomentumOscillator {
90 pub fn new(first_length: usize, second_length: usize, signal_length: usize) -> Self {
92 Self {
93 first_length: first_length.max(2),
94 second_length: second_length.max(2),
95 signal_length: signal_length.max(1),
96 values: IndicatorValues::new(),
97 stepper: Stepper::default(),
98 colors: vec![palette::BLUE, palette::ORANGE],
99 visible: true,
100 }
101 }
102}
103
104impl Default for PriceMomentumOscillator {
105 fn default() -> Self {
106 Self::new(35, 20, 10)
107 }
108}
109
110impl Indicator for PriceMomentumOscillator {
111 fn source(&self) -> Option<crate::studies::StudySource> {
112 Some(crate::studies::StudySource::Price(
113 crate::model::PriceSource::Close,
114 ))
115 }
116
117 crate::studies::inputs::study_inputs!(self;
118 first_length: length("Length 1", 2, usize::MAX),
119 second_length: length("Length 2", 2, usize::MAX),
120 signal_length: length("Signal length", 1, usize::MAX)
121 ; {});
122
123 fn name(&self) -> &str {
124 "Price Momentum Oscillator"
125 }
126
127 fn desc(&self) -> &str {
128 "Price Momentum Oscillator - Doubly smoothed rate of change with a signal line"
129 }
130
131 fn calculate(&mut self, data: &[Bar]) {
132 self.update(data, 0);
133 }
134
135 fn update(&mut self, data: &[Bar], from: usize) {
136 self.stepper
137 .run(data, from, &mut self.values, |state, bar| {
138 if !bar.close.is_finite() {
139 return IndicatorValue::None;
140 }
141 let state = state.get_or_insert_with(|| State {
142 previous: None,
143 first: Smoothing::new(self.first_length, 2.0 / self.first_length as f64),
144 second: Smoothing::new(self.second_length, 2.0 / self.second_length as f64),
145 signal: Smoothing::new(
146 self.signal_length,
147 2.0 / (self.signal_length as f64 + 1.0),
148 ),
149 });
150 let roc = state
151 .previous
152 .map(|previous| (bar.close / previous - 1.0) * 100.0);
153 state.previous = Some(bar.close);
154 let pmo = roc
155 .filter(|roc| roc.is_finite())
156 .and_then(|roc| state.first.push(roc))
157 .and_then(|smoothed| state.second.push(10.0 * smoothed));
158 match pmo {
159 Some(pmo) => IndicatorValue::Multiple(vec![
160 pmo,
161 state.signal.push(pmo).unwrap_or(f64::NAN),
162 ]),
163 None => IndicatorValue::None,
164 }
165 });
166 }
167
168 fn is_incremental(&self) -> bool {
169 true
170 }
171
172 fn values(&self) -> &IndicatorValues {
173 &self.values
174 }
175
176 fn colors(&self) -> Vec<Color32> {
177 self.colors.clone()
178 }
179
180 fn set_colors(&mut self, colors: Vec<Color32>) {
181 if colors.len() >= 2 {
182 self.colors = colors;
183 }
184 }
185
186 fn pane(&self) -> crate::studies::StudyPane {
187 crate::studies::StudyPane {
188 height: None,
189 range: None,
190 levels: vec![(0.0, palette::GRAY, "Zero Line")],
191 fill_between_levels: false,
192 }
193 }
194
195 fn is_overlay(&self) -> bool {
196 false
197 }
198
199 fn is_visible(&self) -> bool {
200 self.visible
201 }
202
203 fn set_visible(&mut self, visible: bool) {
204 self.visible = visible;
205 }
206
207 fn clone_box(&self) -> Box<dyn Indicator> {
208 Box::new(self.clone())
209 }
210
211 fn line_count(&self) -> usize {
212 2
213 }
214
215 fn line_names(&self) -> Vec<String> {
216 vec![
217 format!("PMO({},{})", self.first_length, self.second_length),
218 format!("Signal({})", self.signal_length),
219 ]
220 }
221}