egui_charts/studies/builtin/
parabolic_sar.rs1use super::clamp_param;
30use crate::model::Bar;
31use crate::studies::palette;
32use crate::studies::stepper::Stepper;
33use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
34use egui::Color32;
35
36#[derive(Clone)]
38pub struct ParabolicSAR {
39 initial_af: f64,
40 af_step: f64,
41 max_af: f64,
42 values: IndicatorValues,
43 stepper: Stepper<State>,
44 bullish_color: Color32,
45 bearish_color: Color32,
46 visible: bool,
47}
48
49impl ParabolicSAR {
50 pub fn new(initial_af: f64, af_step: f64, max_af: f64) -> Self {
60 let initial_af = clamp_param(initial_af, 0.001, 1.0);
61 Self {
62 initial_af,
63 af_step: clamp_param(af_step, 0.001, 1.0),
64 max_af: clamp_param(max_af, initial_af, 1.0),
65 values: IndicatorValues::new(),
66 stepper: Stepper::default(),
67 bullish_color: palette::BLUE,
68 bearish_color: palette::BLUE,
69 visible: true,
70 }
71 }
72
73 pub fn default_params() -> Self {
75 Self::new(0.02, 0.02, 0.2)
76 }
77
78 pub fn with_colors(mut self, bullish: Color32, bearish: Color32) -> Self {
80 self.bullish_color = bullish;
81 self.bearish_color = bearish;
82 self
83 }
84}
85
86impl Default for ParabolicSAR {
88 fn default() -> Self {
89 Self::new(0.02, 0.02, 0.2)
90 }
91}
92
93#[derive(Clone, Default)]
94struct State {
95 previous: Option<[f64; 3]>,
96 before_previous: Option<[f64; 2]>,
97 sar: f64,
98 ep: f64,
99 af: f64,
100 up: bool,
101}
102
103impl Indicator for ParabolicSAR {
104 crate::studies::inputs::study_inputs!(self;
105 initial_af: number("Initial af", 0.001, 1.0),
106 af_step: number("Af step", 0.001, 1.0),
107 max_af: number("Max af", 0.001, 1.0)
108 ; { self.max_af = self.max_af.max(self.initial_af); });
109
110 fn plot_style(&self, line: usize) -> crate::studies::PlotStyle {
111 crate::studies::PlotStyle {
112 color: self.colors().get(line).copied().unwrap_or(Color32::WHITE),
113 width: 1.0,
114 pattern: crate::model::LineStyle::Solid,
115 plot_type: crate::studies::PlotType::Crosses,
116 visible: true,
117 }
118 }
119
120 fn name(&self) -> &str {
121 "PSAR"
122 }
123
124 fn desc(&self) -> &str {
125 "Parabolic SAR - Trend following stop-and-reverse"
126 }
127
128 fn calculate(&mut self, data: &[Bar]) {
129 self.update(data, 0);
130 }
131
132 fn update(&mut self, data: &[Bar], from: usize) {
133 self.stepper
134 .run(data, from, &mut self.values, |state, bar| {
135 if !bar.high.is_finite() || !bar.low.is_finite() || !bar.close.is_finite() {
136 return IndicatorValue::None;
137 }
138 let current = [bar.high, bar.low, bar.close];
139 let Some(previous) = state.previous.replace(current) else {
140 return IndicatorValue::None;
141 };
142 if let Some(before) = state.before_previous {
143 let mut next = state.sar + state.af * (state.ep - state.sar);
144 let reversal = if state.up {
145 next = next.min(previous[1]).min(before[1]);
146 bar.low < next
147 } else {
148 next = next.max(previous[0]).max(before[0]);
149 bar.high > next
150 };
151 if reversal {
152 state.up = !state.up;
153 state.sar = state.ep;
154 state.ep = if state.up { bar.high } else { bar.low };
155 state.af = self.initial_af;
156 } else {
157 state.sar = next;
158 if (state.up && bar.high > state.ep) || (!state.up && bar.low < state.ep) {
159 state.ep = if state.up { bar.high } else { bar.low };
160 state.af = (state.af + self.af_step).min(self.max_af);
161 }
162 }
163 } else {
164 state.up = bar.close >= previous[2];
165 state.sar = if state.up { previous[1] } else { previous[0] };
166 state.ep = if state.up {
167 bar.high.max(previous[0])
168 } else {
169 bar.low.min(previous[1])
170 };
171 state.af = self.initial_af;
172 }
173 state.before_previous = Some([previous[0], previous[1]]);
174 IndicatorValue::Multiple(if state.up {
175 vec![state.sar, f64::NAN]
176 } else {
177 vec![f64::NAN, state.sar]
178 })
179 });
180 }
181
182 fn is_incremental(&self) -> bool {
183 true
184 }
185
186 fn values(&self) -> &IndicatorValues {
187 &self.values
188 }
189
190 fn colors(&self) -> Vec<Color32> {
191 vec![self.bullish_color, self.bearish_color]
192 }
193
194 fn set_colors(&mut self, colors: Vec<Color32>) {
195 if !colors.is_empty() {
196 self.bullish_color = colors[0];
197 }
198 if colors.len() > 1 {
199 self.bearish_color = colors[1];
200 }
201 }
202
203 fn is_overlay(&self) -> bool {
204 true }
206
207 fn is_visible(&self) -> bool {
208 self.visible
209 }
210
211 fn set_visible(&mut self, visible: bool) {
212 self.visible = visible;
213 }
214
215 fn clone_box(&self) -> Box<dyn Indicator> {
216 Box::new(self.clone())
217 }
218
219 fn line_names(&self) -> Vec<String> {
220 vec!["Up Trend".to_string(), "Down Trend".to_string()]
221 }
222}