indexes_rs/v2/parabolic_sar/
main.rs1use crate::v2::parabolic_sar::types::{ParabolicSARConfig, ParabolicSARError, ParabolicSARInput, ParabolicSAROutput, ParabolicSARState, TrendDirection};
2
3pub struct ParabolicSAR {
16 pub state: ParabolicSARState,
17}
18
19impl ParabolicSAR {
20 pub fn new() -> Self {
22 Self::with_config(ParabolicSARConfig::default())
23 }
24
25 pub fn with_acceleration(start: f64, increment: f64, maximum: f64) -> Result<Self, ParabolicSARError> {
27 let config = ParabolicSARConfig {
28 acceleration_start: start,
29 acceleration_increment: increment,
30 acceleration_maximum: maximum,
31 };
32
33 if start <= 0.0 || increment <= 0.0 || maximum <= start {
35 return Err(ParabolicSARError::InvalidAcceleration);
36 }
37
38 Ok(Self::with_config(config))
39 }
40
41 pub fn with_config(config: ParabolicSARConfig) -> Self {
43 Self {
44 state: ParabolicSARState::new(config),
45 }
46 }
47
48 pub fn calculate(&mut self, input: ParabolicSARInput) -> Result<ParabolicSAROutput, ParabolicSARError> {
50 self.validate_input(&input)?;
52 self.validate_config()?;
53
54 let result = if self.state.is_first {
55 self.handle_first_calculation(input)
56 } else if self.state.is_second {
57 self.handle_second_calculation(input)
58 } else {
59 self.handle_normal_calculation(input)
60 };
61
62 self.update_state_after_calculation(input);
64
65 result
66 }
67
68 pub fn calculate_batch(&mut self, inputs: &[ParabolicSARInput]) -> Result<Vec<ParabolicSAROutput>, ParabolicSARError> {
70 inputs.iter().map(|input| self.calculate(*input)).collect()
71 }
72
73 pub fn reset(&mut self) {
75 self.state = ParabolicSARState::new(self.state.config);
76 }
77
78 pub fn get_state(&self) -> &ParabolicSARState {
80 &self.state
81 }
82
83 pub fn set_state(&mut self, state: ParabolicSARState) {
85 self.state = state;
86 }
87
88 pub fn current_trend(&self) -> Option<TrendDirection> {
90 self.state.trend
91 }
92
93 pub fn current_acceleration_factor(&self) -> f64 {
95 self.state.acceleration_factor
96 }
97
98 fn validate_input(&self, input: &ParabolicSARInput) -> Result<(), ParabolicSARError> {
101 if !input.high.is_finite() || !input.low.is_finite() {
103 return Err(ParabolicSARError::InvalidPrice);
104 }
105
106 if input.high < input.low {
108 return Err(ParabolicSARError::InvalidHL);
109 }
110
111 if let Some(close) = input.close {
113 if !close.is_finite() {
114 return Err(ParabolicSARError::InvalidPrice);
115 }
116 if close < input.low || close > input.high {
117 return Err(ParabolicSARError::CloseOutOfRange);
118 }
119 }
120
121 Ok(())
122 }
123
124 fn validate_config(&self) -> Result<(), ParabolicSARError> {
125 let config = &self.state.config;
126
127 if config.acceleration_start <= 0.0 || config.acceleration_increment <= 0.0 || config.acceleration_maximum <= config.acceleration_start {
128 return Err(ParabolicSARError::InvalidAcceleration);
129 }
130
131 Ok(())
132 }
133
134 fn handle_first_calculation(&mut self, input: ParabolicSARInput) -> Result<ParabolicSAROutput, ParabolicSARError> {
135 self.state.is_first = false;
138 self.state.is_second = true;
139
140 Ok(ParabolicSAROutput {
141 sar: input.low, trend: TrendDirection::Up, acceleration_factor: self.state.config.acceleration_start,
144 extreme_point: input.high,
145 trend_reversal: false,
146 trend_periods: 1,
147 })
148 }
149
150 fn handle_second_calculation(&mut self, input: ParabolicSARInput) -> Result<ParabolicSAROutput, ParabolicSARError> {
151 self.state.is_second = false;
153
154 let prev_high = self.state.previous_high.unwrap();
155 let prev_low = self.state.previous_low.unwrap();
156
157 let trend = if input.high > prev_high { TrendDirection::Up } else { TrendDirection::Down };
159
160 let (sar, extreme_point) = match trend {
162 TrendDirection::Up => (prev_low, input.high.max(prev_high)),
163 TrendDirection::Down => (prev_high, input.low.min(prev_low)),
164 };
165
166 self.state.trend = Some(trend);
167 self.state.current_sar = Some(sar);
168 self.state.extreme_point = Some(extreme_point);
169 self.state.trend_periods = 1;
170
171 Ok(ParabolicSAROutput {
172 sar,
173 trend,
174 acceleration_factor: self.state.acceleration_factor,
175 extreme_point,
176 trend_reversal: false,
177 trend_periods: self.state.trend_periods,
178 })
179 }
180
181 fn handle_normal_calculation(&mut self, input: ParabolicSARInput) -> Result<ParabolicSAROutput, ParabolicSARError> {
182 let current_trend = self.state.trend.unwrap();
183 let current_sar = self.state.current_sar.unwrap();
184 let current_ep = self.state.extreme_point.unwrap();
185
186 let trend_reversal = match current_trend {
188 TrendDirection::Up => input.low <= current_sar,
189 TrendDirection::Down => input.high >= current_sar,
190 };
191
192 if trend_reversal {
193 self.handle_trend_reversal(input, current_trend, current_ep)
194 } else {
195 self.handle_trend_continuation(input, current_trend, current_sar, current_ep)
196 }
197 }
198
199 fn handle_trend_reversal(&mut self, input: ParabolicSARInput, old_trend: TrendDirection, old_ep: f64) -> Result<ParabolicSAROutput, ParabolicSARError> {
200 let new_trend = match old_trend {
202 TrendDirection::Up => TrendDirection::Down,
203 TrendDirection::Down => TrendDirection::Up,
204 };
205
206 let new_sar = old_ep;
208
209 let new_ep = match new_trend {
211 TrendDirection::Up => input.high,
212 TrendDirection::Down => input.low,
213 };
214
215 self.state.acceleration_factor = self.state.config.acceleration_start;
217 self.state.trend = Some(new_trend);
218 self.state.current_sar = Some(new_sar);
219 self.state.extreme_point = Some(new_ep);
220 self.state.trend_periods = 1;
221
222 Ok(ParabolicSAROutput {
223 sar: new_sar,
224 trend: new_trend,
225 acceleration_factor: self.state.acceleration_factor,
226 extreme_point: new_ep,
227 trend_reversal: true,
228 trend_periods: self.state.trend_periods,
229 })
230 }
231
232 fn handle_trend_continuation(&mut self, input: ParabolicSARInput, trend: TrendDirection, current_sar: f64, current_ep: f64) -> Result<ParabolicSAROutput, ParabolicSARError> {
233 let (new_ep, ep_updated) = match trend {
235 TrendDirection::Up => {
236 if input.high > current_ep {
237 (input.high, true)
238 } else {
239 (current_ep, false)
240 }
241 }
242 TrendDirection::Down => {
243 if input.low < current_ep {
244 (input.low, true)
245 } else {
246 (current_ep, false)
247 }
248 }
249 };
250
251 if ep_updated {
253 self.state.acceleration_factor = (self.state.acceleration_factor + self.state.config.acceleration_increment).min(self.state.config.acceleration_maximum);
254 }
255
256 let mut new_sar = current_sar + self.state.acceleration_factor * (new_ep - current_sar);
258
259 new_sar = match trend {
261 TrendDirection::Up => {
262 let prev_low = self.state.previous_low.unwrap_or(input.low);
264 new_sar.min(input.low).min(prev_low)
265 }
266 TrendDirection::Down => {
267 let prev_high = self.state.previous_high.unwrap_or(input.high);
269 new_sar.max(input.high).max(prev_high)
270 }
271 };
272
273 self.state.current_sar = Some(new_sar);
274 self.state.extreme_point = Some(new_ep);
275 self.state.trend_periods += 1;
276
277 Ok(ParabolicSAROutput {
278 sar: new_sar,
279 trend,
280 acceleration_factor: self.state.acceleration_factor,
281 extreme_point: new_ep,
282 trend_reversal: false,
283 trend_periods: self.state.trend_periods,
284 })
285 }
286
287 fn update_state_after_calculation(&mut self, input: ParabolicSARInput) {
288 self.state.previous_high = Some(input.high);
289 self.state.previous_low = Some(input.low);
290 if let Some(close) = input.close {
291 self.state.previous_close = Some(close);
292 }
293 }
294}
295
296impl Default for ParabolicSAR {
297 fn default() -> Self {
298 Self::new()
299 }
300}
301
302pub fn calculate_parabolic_sar_simple(
304 highs: &[f64],
305 lows: &[f64],
306 acceleration_start: Option<f64>,
307 acceleration_increment: Option<f64>,
308 acceleration_maximum: Option<f64>,
309) -> Result<Vec<f64>, ParabolicSARError> {
310 if highs.len() != lows.len() {
311 return Err(ParabolicSARError::InvalidInput("Highs and lows must have same length".to_string()));
312 }
313
314 if highs.is_empty() {
315 return Ok(Vec::new());
316 }
317
318 let config = ParabolicSARConfig {
319 acceleration_start: acceleration_start.unwrap_or(0.02),
320 acceleration_increment: acceleration_increment.unwrap_or(0.02),
321 acceleration_maximum: acceleration_maximum.unwrap_or(0.20),
322 };
323
324 let mut sar_calculator = ParabolicSAR::with_config(config);
325 let mut results = Vec::with_capacity(highs.len());
326
327 for i in 0..highs.len() {
328 let input = ParabolicSARInput {
329 high: highs[i],
330 low: lows[i],
331 close: None,
332 };
333 let output = sar_calculator.calculate(input)?;
334 results.push(output.sar);
335 }
336
337 Ok(results)
338}