Skip to main content

quantwave_core/indicators/incremental/
sar.rs

1//! Native O(1) Parabolic SAR and SAREXT — TA-Lib parity.
2
3use crate::traits::Next;
4
5/// Parabolic SAR — matches `talib_rs::overlap::sar`.
6#[derive(Debug, Clone)]
7#[allow(non_camel_case_types)]
8pub struct SAR {
9    pub acceleration: f64,
10    pub maximum: f64,
11    bar: usize,
12    is_long: bool,
13    sar_val: f64,
14    ep: f64,
15    af: f64,
16    prev_low: f64,
17    prev_high: f64,
18    low0: f64,
19    high0: f64,
20    high1: f64,
21    low1: f64,
22}
23
24impl SAR {
25    pub fn new(acceleration: f64, maximum: f64) -> Self {
26        Self {
27            acceleration,
28            maximum,
29            bar: 0,
30            is_long: false,
31            sar_val: 0.0,
32            ep: 0.0,
33            af: acceleration,
34            prev_low: 0.0,
35            prev_high: 0.0,
36            low0: 0.0,
37            high0: 0.0,
38            high1: 0.0,
39            low1: 0.0,
40        }
41    }
42
43    fn init_direction(&self) -> bool {
44        let diff_m = self.low0 - self.low1;
45        let diff_p = self.high1 - self.high0;
46        !(diff_m > 0.0 && diff_m > diff_p)
47    }
48}
49
50impl Next<(f64, f64)> for SAR {
51    type Output = f64;
52
53    fn next(&mut self, (high, low): (f64, f64)) -> Self::Output {
54        let i = self.bar;
55        self.bar += 1;
56
57        if i == 0 {
58            self.high0 = high;
59            self.low0 = low;
60            return f64::NAN;
61        }
62
63        if i == 1 {
64            self.high1 = high;
65            self.low1 = low;
66            self.is_long = self.init_direction();
67            self.af = self.acceleration;
68            if self.is_long {
69                self.ep = high;
70                self.sar_val = self.low0;
71            } else {
72                self.ep = low;
73                self.sar_val = self.high0;
74            }
75            let out = self.step_bar1(high, low);
76            self.prev_low = low;
77            self.prev_high = high;
78            return out;
79        }
80
81        let p_low = self.prev_low;
82        let p_high = self.prev_high;
83        self.prev_low = low;
84        self.prev_high = high;
85        self.step_main(high, low, p_low, p_high)
86    }
87}
88
89impl SAR {
90    fn step_bar1(&mut self, new_high: f64, new_low: f64) -> f64 {
91        let p_low = new_low;
92        let p_high = new_high;
93
94        if self.is_long {
95            if new_low <= self.sar_val {
96                self.is_long = false;
97                self.sar_val = self.ep;
98                self.sar_val = self.sar_val.max(p_high).max(new_high);
99                let out = self.sar_val;
100                self.af = self.acceleration;
101                self.ep = new_low;
102                self.sar_val += self.af * (self.ep - self.sar_val);
103                self.sar_val = self.sar_val.max(p_high).max(new_high);
104                out
105            } else {
106                let out = self.sar_val;
107                if new_high > self.ep {
108                    self.ep = new_high;
109                    self.af = (self.af + self.acceleration).min(self.maximum);
110                }
111                self.sar_val += self.af * (self.ep - self.sar_val);
112                self.sar_val = self.sar_val.min(p_low).min(new_low);
113                out
114            }
115        } else if new_high >= self.sar_val {
116            self.is_long = true;
117            self.sar_val = self.ep;
118            self.sar_val = self.sar_val.min(p_low).min(new_low);
119            let out = self.sar_val;
120            self.af = self.acceleration;
121            self.ep = new_high;
122            self.sar_val += self.af * (self.ep - self.sar_val);
123            self.sar_val = self.sar_val.min(p_low).min(new_low);
124            out
125        } else {
126            let out = self.sar_val;
127            if new_low < self.ep {
128                self.ep = new_low;
129                self.af = (self.af + self.acceleration).min(self.maximum);
130            }
131            self.sar_val += self.af * (self.ep - self.sar_val);
132            self.sar_val = self.sar_val.max(p_high).max(new_high);
133            out
134        }
135    }
136
137    fn step_main(&mut self, new_high: f64, new_low: f64, p_low: f64, p_high: f64) -> f64 {
138        if self.is_long {
139            if new_low <= self.sar_val {
140                self.is_long = false;
141                self.sar_val = self.ep;
142                self.sar_val = self.sar_val.max(p_high).max(new_high);
143                let out = self.sar_val;
144                self.af = self.acceleration;
145                self.ep = new_low;
146                self.sar_val += self.af * (self.ep - self.sar_val);
147                self.sar_val = self.sar_val.max(p_high).max(new_high);
148                out
149            } else {
150                let out = self.sar_val;
151                if new_high > self.ep {
152                    self.ep = new_high;
153                    self.af = (self.af + self.acceleration).min(self.maximum);
154                }
155                self.sar_val += self.af * (self.ep - self.sar_val);
156                self.sar_val = self.sar_val.min(p_low).min(new_low);
157                out
158            }
159        } else if new_high >= self.sar_val {
160            self.is_long = true;
161            self.sar_val = self.ep;
162            self.sar_val = self.sar_val.min(p_low).min(new_low);
163            let out = self.sar_val;
164            self.af = self.acceleration;
165            self.ep = new_high;
166            self.sar_val += self.af * (self.ep - self.sar_val);
167            self.sar_val = self.sar_val.min(p_low).min(new_low);
168            out
169        } else {
170            let out = self.sar_val;
171            if new_low < self.ep {
172                self.ep = new_low;
173                self.af = (self.af + self.acceleration).min(self.maximum);
174            }
175            self.sar_val += self.af * (self.ep - self.sar_val);
176            self.sar_val = self.sar_val.max(p_high).max(new_high);
177            out
178        }
179    }
180}
181
182/// Parabolic SAR Extended — matches `talib_rs::overlap::sar_ext`.
183#[derive(Debug, Clone)]
184#[allow(non_camel_case_types)]
185pub struct SAREXT {
186    pub startvalue: f64,
187    pub offsetonreverse: f64,
188    pub accelerationinitlong: f64,
189    pub accelerationlong: f64,
190    pub accelerationmaxlong: f64,
191    pub accelerationinitshort: f64,
192    pub accelerationshort: f64,
193    pub accelerationmaxshort: f64,
194    bar: usize,
195    is_long: bool,
196    sar_val: f64,
197    ep: f64,
198    af_long: f64,
199    af_short: f64,
200    prev_low: f64,
201    prev_high: f64,
202    low0: f64,
203    high0: f64,
204    high1: f64,
205    low1: f64,
206}
207
208impl SAREXT {
209    #[allow(clippy::too_many_arguments)]
210    pub fn new(
211        startvalue: f64,
212        offsetonreverse: f64,
213        accelerationinitlong: f64,
214        accelerationlong: f64,
215        accelerationmaxlong: f64,
216        accelerationinitshort: f64,
217        accelerationshort: f64,
218        accelerationmaxshort: f64,
219    ) -> Self {
220        Self {
221            startvalue,
222            offsetonreverse,
223            accelerationinitlong,
224            accelerationlong,
225            accelerationmaxlong,
226            accelerationinitshort,
227            accelerationshort,
228            accelerationmaxshort,
229            bar: 0,
230            is_long: false,
231            sar_val: 0.0,
232            ep: 0.0,
233            af_long: accelerationinitlong,
234            af_short: accelerationinitshort,
235            prev_low: 0.0,
236            prev_high: 0.0,
237            low0: 0.0,
238            high0: 0.0,
239            high1: 0.0,
240            low1: 0.0,
241        }
242    }
243
244    fn init_direction(&self) -> bool {
245        let diff_m = self.low0 - self.low1;
246        let diff_p = self.high1 - self.high0;
247        !(diff_m > 0.0 && diff_m > diff_p)
248    }
249
250    fn init_state(&mut self) {
251        if self.startvalue == 0.0 {
252            self.is_long = self.init_direction();
253            if self.is_long {
254                self.ep = self.high1;
255                self.sar_val = self.low0;
256            } else {
257                self.ep = self.low1;
258                self.sar_val = self.high0;
259            }
260        } else if self.startvalue > 0.0 {
261            self.is_long = true;
262            self.ep = self.high1;
263            self.sar_val = self.startvalue;
264        } else {
265            self.is_long = false;
266            self.ep = self.low1;
267            self.sar_val = self.startvalue.abs();
268        }
269        self.af_long = self.accelerationinitlong;
270        self.af_short = self.accelerationinitshort;
271    }
272}
273
274impl Next<(f64, f64)> for SAREXT {
275    type Output = f64;
276
277    fn next(&mut self, (high, low): (f64, f64)) -> Self::Output {
278        let i = self.bar;
279        self.bar += 1;
280
281        if i == 0 {
282            self.high0 = high;
283            self.low0 = low;
284            return f64::NAN;
285        }
286
287        if i == 1 {
288            self.high1 = high;
289            self.low1 = low;
290            self.init_state();
291            let out = self.step_bar1(high, low);
292            self.prev_low = low;
293            self.prev_high = high;
294            return out;
295        }
296
297        let p_low = self.prev_low;
298        let p_high = self.prev_high;
299        self.prev_low = low;
300        self.prev_high = high;
301        self.step_main(high, low, p_low, p_high)
302    }
303}
304
305impl SAREXT {
306    fn step_bar1(&mut self, new_high: f64, new_low: f64) -> f64 {
307        let p_low = new_low;
308        let p_high = new_high;
309
310        if self.is_long {
311            if new_low <= self.sar_val {
312                self.is_long = false;
313                self.sar_val = self.ep;
314                self.sar_val = self.sar_val.max(p_high).max(new_high);
315                if self.offsetonreverse != 0.0 {
316                    self.sar_val += self.sar_val * self.offsetonreverse;
317                }
318                let out = -self.sar_val;
319                self.af_short = self.accelerationinitshort;
320                self.ep = new_low;
321                self.sar_val += self.af_short * (self.ep - self.sar_val);
322                self.sar_val = self.sar_val.max(p_high).max(new_high);
323                out
324            } else {
325                let out = self.sar_val;
326                if new_high > self.ep {
327                    self.ep = new_high;
328                    self.af_long = (self.af_long + self.accelerationlong)
329                        .min(self.accelerationmaxlong);
330                }
331                self.sar_val += self.af_long * (self.ep - self.sar_val);
332                self.sar_val = self.sar_val.min(p_low).min(new_low);
333                out
334            }
335        } else if new_high >= self.sar_val {
336            self.is_long = true;
337            self.sar_val = self.ep;
338            self.sar_val = self.sar_val.min(p_low).min(new_low);
339            if self.offsetonreverse != 0.0 {
340                self.sar_val -= self.sar_val * self.offsetonreverse;
341            }
342            let out = self.sar_val;
343            self.af_long = self.accelerationinitlong;
344            self.ep = new_high;
345            self.sar_val += self.af_long * (self.ep - self.sar_val);
346            self.sar_val = self.sar_val.min(p_low).min(new_low);
347            out
348        } else {
349            let out = -self.sar_val;
350            if new_low < self.ep {
351                self.ep = new_low;
352                self.af_short = (self.af_short + self.accelerationshort)
353                    .min(self.accelerationmaxshort);
354            }
355            self.sar_val += self.af_short * (self.ep - self.sar_val);
356            self.sar_val = self.sar_val.max(p_high).max(new_high);
357            out
358        }
359    }
360
361    fn step_main(&mut self, new_high: f64, new_low: f64, p_low: f64, p_high: f64) -> f64 {
362        if self.is_long {
363            if new_low <= self.sar_val {
364                self.is_long = false;
365                self.sar_val = self.ep;
366                self.sar_val = self.sar_val.max(p_high).max(new_high);
367                if self.offsetonreverse != 0.0 {
368                    self.sar_val += self.sar_val * self.offsetonreverse;
369                }
370                let out = -self.sar_val;
371                self.af_short = self.accelerationinitshort;
372                self.ep = new_low;
373                self.sar_val += self.af_short * (self.ep - self.sar_val);
374                self.sar_val = self.sar_val.max(p_high).max(new_high);
375                out
376            } else {
377                let out = self.sar_val;
378                if new_high > self.ep {
379                    self.ep = new_high;
380                    self.af_long = (self.af_long + self.accelerationlong)
381                        .min(self.accelerationmaxlong);
382                }
383                self.sar_val += self.af_long * (self.ep - self.sar_val);
384                self.sar_val = self.sar_val.min(p_low).min(new_low);
385                out
386            }
387        } else if new_high >= self.sar_val {
388            self.is_long = true;
389            self.sar_val = self.ep;
390            self.sar_val = self.sar_val.min(p_low).min(new_low);
391            if self.offsetonreverse != 0.0 {
392                self.sar_val -= self.sar_val * self.offsetonreverse;
393            }
394            let out = self.sar_val;
395            self.af_long = self.accelerationinitlong;
396            self.ep = new_high;
397            self.sar_val += self.af_long * (self.ep - self.sar_val);
398            self.sar_val = self.sar_val.min(p_low).min(new_low);
399            out
400        } else {
401            let out = -self.sar_val;
402            if new_low < self.ep {
403                self.ep = new_low;
404                self.af_short = (self.af_short + self.accelerationshort)
405                    .min(self.accelerationmaxshort);
406            }
407            self.sar_val += self.af_short * (self.ep - self.sar_val);
408            self.sar_val = self.sar_val.max(p_high).max(new_high);
409            out
410        }
411    }
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417    use proptest::prelude::*;
418
419    proptest! {
420        #[test]
421        fn test_sar_parity(
422            h in prop::collection::vec(10.0..100.0, 2..100),
423            l in prop::collection::vec(10.0..100.0, 2..100)
424        ) {
425            let len = h.len().min(l.len());
426            let mut high = Vec::with_capacity(len);
427            let mut low = Vec::with_capacity(len);
428            for i in 0..len {
429                let hi: f64 = h[i];
430                let lo: f64 = l[i];
431                high.push(hi.max(lo));
432                low.push(hi.min(lo));
433            }
434            let accel = 0.02;
435            let max = 0.2;
436            let mut sar = SAR::new(accel, max);
437            let streaming: Vec<f64> = (0..len).map(|i| sar.next((high[i], low[i]))).collect();
438            let batch = talib_rs::overlap::sar(&high, &low, accel, max)
439                .unwrap_or_else(|_| vec![f64::NAN; len]);
440            for (s, b) in streaming.iter().zip(batch.iter()) {
441                if s.is_nan() { assert!(b.is_nan()); }
442                else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
443            }
444        }
445    }
446}