1use crate::stocks::ta::common::{opt_cell, require_hlc};
54use crate::util::error::{require_finite, FinanceError, FinanceResult};
55use crate::{columns_with_strings, print_table_locale_opt};
56
57#[derive(Clone, Copy, Debug, PartialEq)]
59pub struct SarParams {
60 pub start: f64,
61 pub increment: f64,
62 pub maximum: f64,
63}
64
65impl SarParams {
66 pub const fn new(start: f64, increment: f64, maximum: f64) -> Self {
67 Self {
68 start,
69 increment,
70 maximum,
71 }
72 }
73
74 pub const fn standard() -> Self {
76 Self {
77 start: 0.02,
78 increment: 0.02,
79 maximum: 0.20,
80 }
81 }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq)]
85pub struct ValidatedSar {
86 params: SarParams,
87}
88
89impl ValidatedSar {
90 pub fn new(params: SarParams) -> FinanceResult<Self> {
91 require_finite("start", params.start)?;
92 require_finite("increment", params.increment)?;
93 require_finite("maximum", params.maximum)?;
94 if params.start <= 0.0 || params.increment <= 0.0 || params.maximum < params.start {
95 return Err(FinanceError::Unsolvable {
96 message: "SAR requires start>0, increment>0, maximum>=start",
97 });
98 }
99 Ok(Self { params })
100 }
101
102 pub fn params(self) -> SarParams {
103 self.params
104 }
105
106 pub fn compute(self, high: &[f64], low: &[f64], close: &[f64]) -> FinanceResult<SarSeries> {
107 sar_validated(high, low, close, self)
108 }
109}
110
111#[derive(Clone, Debug, PartialEq)]
112pub struct SarSeries {
113 pub sar: Vec<Option<f64>>,
114 pub direction: Vec<Option<i8>>,
116 pub params: SarParams,
117}
118
119#[derive(Clone, Copy, Debug, PartialEq)]
120pub struct SarBarOutput {
121 pub sar: f64,
122 pub direction: i8,
123}
124
125#[derive(Clone, Debug)]
127pub struct SarState {
128 params: SarParams,
129 is_long: bool,
131 af: f64,
132 ep: f64,
133 sar: f64,
134 prev_high: f64,
135 prev_low: f64,
136 prev2_high: Option<f64>,
137 prev2_low: Option<f64>,
138 started: bool,
139 last: Option<SarBarOutput>,
140}
141
142impl SarState {
143 pub fn new(params: SarParams) -> FinanceResult<Self> {
144 let _ = ValidatedSar::new(params)?;
145 Ok(Self {
146 params,
147 is_long: true,
148 af: params.start,
149 ep: 0.0,
150 sar: 0.0,
151 prev_high: 0.0,
152 prev_low: 0.0,
153 prev2_high: None,
154 prev2_low: None,
155 started: false,
156 last: None,
157 })
158 }
159
160 pub fn from_history(
161 params: SarParams,
162 high: &[f64],
163 low: &[f64],
164 close: &[f64],
165 ) -> FinanceResult<Self> {
166 let mut s = Self::new(params)?;
167 let _ = s.push_bars(high, low, close)?;
168 Ok(s)
169 }
170
171 pub fn params(&self) -> SarParams {
172 self.params
173 }
174
175 pub fn reset(&mut self) {
176 *self = Self::new(self.params).expect("params already valid");
177 }
178
179 pub fn push(&mut self, high: f64, low: f64, close: f64) -> FinanceResult<Option<SarBarOutput>> {
180 require_finite("high", high)?;
181 require_finite("low", low)?;
182 require_finite("close", close)?;
183 if high < low {
184 return Err(FinanceError::InvalidCashflow {
185 message: "high must be >= low for each bar",
186 });
187 }
188
189 if !self.started {
190 self.prev_high = high;
192 self.prev_low = low;
193 self.started = true;
194 self.last = None;
195 return Ok(None);
196 }
197
198 if self.last.is_none() {
199 self.is_long = close >= self.prev_high; if high > self.prev_high {
202 self.is_long = true;
203 } else if low < self.prev_low {
204 self.is_long = false;
205 }
206 if self.is_long {
207 self.sar = self.prev_low;
208 self.ep = high.max(self.prev_high);
209 } else {
210 self.sar = self.prev_high;
211 self.ep = low.min(self.prev_low);
212 }
213 self.af = self.params.start;
214 let dir = if self.is_long { 1 } else { -1 };
215 let bar = SarBarOutput {
216 sar: self.sar,
217 direction: dir,
218 };
219 self.prev2_high = Some(self.prev_high);
220 self.prev2_low = Some(self.prev_low);
221 self.prev_high = high;
222 self.prev_low = low;
223 self.last = Some(bar);
224 return Ok(Some(bar));
225 }
226
227 let mut sar = self.sar + self.af * (self.ep - self.sar);
229
230 if self.is_long {
231 sar = sar.min(self.prev_low);
233 if let Some(l2) = self.prev2_low {
234 sar = sar.min(l2);
235 }
236 if low < sar {
237 self.is_long = false;
239 sar = self.ep;
240 self.ep = low;
241 self.af = self.params.start;
242 } else {
243 if high > self.ep {
244 self.ep = high;
245 self.af = (self.af + self.params.increment).min(self.params.maximum);
246 }
247 }
248 } else {
249 sar = sar.max(self.prev_high);
250 if let Some(h2) = self.prev2_high {
251 sar = sar.max(h2);
252 }
253 if high > sar {
254 self.is_long = true;
256 sar = self.ep;
257 self.ep = high;
258 self.af = self.params.start;
259 } else if low < self.ep {
260 self.ep = low;
261 self.af = (self.af + self.params.increment).min(self.params.maximum);
262 }
263 }
264
265 self.sar = sar;
266 let dir = if self.is_long { 1 } else { -1 };
267 let bar = SarBarOutput {
268 sar,
269 direction: dir,
270 };
271 self.prev2_high = Some(self.prev_high);
272 self.prev2_low = Some(self.prev_low);
273 self.prev_high = high;
274 self.prev_low = low;
275 self.last = Some(bar);
276 Ok(Some(bar))
277 }
278
279 pub fn push_bars(
280 &mut self,
281 high: &[f64],
282 low: &[f64],
283 close: &[f64],
284 ) -> FinanceResult<Vec<Option<SarBarOutput>>> {
285 require_hlc(high, low, close)?;
286 let mut out = Vec::with_capacity(close.len());
287 for i in 0..close.len() {
288 out.push(self.push(high[i], low[i], close[i])?);
289 }
290 Ok(out)
291 }
292
293 pub fn last(&self) -> Option<SarBarOutput> {
294 self.last
295 }
296}
297
298pub fn sar(
299 high: &[f64],
300 low: &[f64],
301 close: &[f64],
302 params: SarParams,
303) -> FinanceResult<SarSeries> {
304 ValidatedSar::new(params)?.compute(high, low, close)
305}
306
307fn sar_validated(
308 high: &[f64],
309 low: &[f64],
310 close: &[f64],
311 eng: ValidatedSar,
312) -> FinanceResult<SarSeries> {
313 let mut st = SarState::new(eng.params)?;
314 let bars = st.push_bars(high, low, close)?;
315 let n = bars.len();
316 let mut sar = vec![None; n];
317 let mut direction = vec![None; n];
318 for (i, b) in bars.into_iter().enumerate() {
319 if let Some(bar) = b {
320 sar[i] = Some(bar.sar);
321 direction[i] = Some(bar.direction);
322 }
323 }
324 Ok(SarSeries {
325 sar,
326 direction,
327 params: eng.params,
328 })
329}
330
331#[derive(Clone, Debug)]
332pub struct SarSolution {
333 series: SarSeries,
334 close: Vec<f64>,
335 formula: String,
336}
337
338impl SarSolution {
339 pub fn series(&self) -> &SarSeries {
340 &self.series
341 }
342 pub fn formula(&self) -> &str {
343 &self.formula
344 }
345
346 pub fn print_table(&self) {
347 self.print_table_locale_opt(None, None);
348 }
349
350 pub fn print_table_locale(&self, locale: &num_format::Locale, precision: usize) {
351 self.print_table_locale_opt(Some(locale), Some(precision));
352 }
353
354 fn print_table_locale_opt(
355 &self,
356 locale: Option<&num_format::Locale>,
357 precision: Option<usize>,
358 ) {
359 let columns = columns_with_strings(&[
360 ("period", "i", true),
361 ("close", "f", true),
362 ("sar", "f", true),
363 ("dir", "i", true),
364 ]);
365 let data = self
366 .close
367 .iter()
368 .enumerate()
369 .map(|(i, c)| {
370 let d = self.series.direction[i]
371 .map(|x| x.to_string())
372 .unwrap_or_else(|| "n/a".to_string());
373 vec![
374 i.to_string(),
375 c.to_string(),
376 opt_cell(self.series.sar[i]),
377 d,
378 ]
379 })
380 .collect();
381 print_table_locale_opt(&columns, data, locale, precision);
382 }
383}
384
385pub fn sar_solution(
396 high: &[f64],
397 low: &[f64],
398 close: &[f64],
399 params: SarParams,
400) -> FinanceResult<SarSolution> {
401 let series = sar(high, low, close, params)?;
402 Ok(SarSolution {
403 series,
404 close: close.to_vec(),
405 formula: format!(
406 "Parabolic SAR AF start={} step={} max={}",
407 params.start, params.increment, params.maximum
408 ),
409 })
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415
416 #[test]
417 fn produces_sar() {
418 let n = 40usize;
419 let h: Vec<_> = (0..n).map(|i| 101.0 + i as f64 * 0.15).collect();
420 let l: Vec<_> = (0..n).map(|i| 99.0 + i as f64 * 0.15).collect();
421 let c: Vec<_> = (0..n).map(|i| 100.0 + i as f64 * 0.15).collect();
422 let s = sar(&h, &l, &c, SarParams::standard()).unwrap();
423 assert!(s.sar[0].is_none());
424 assert!(s.sar[1].is_some());
425 assert!(s.sar.iter().filter(|x| x.is_some()).count() > 20);
426 }
427
428 #[test]
429 fn state_parity() {
430 let n = 30usize;
431 let h: Vec<_> = (0..n).map(|i| 12.0 + (i as f64 * 0.1).sin()).collect();
432 let l: Vec<_> = (0..n).map(|i| 10.0 + (i as f64 * 0.1).sin()).collect();
433 let c: Vec<_> = (0..n).map(|i| 11.0 + (i as f64 * 0.1).sin()).collect();
434 let p = SarParams::standard();
435 let batch = sar(&h, &l, &c, p).unwrap();
436 let mut st = SarState::new(p).unwrap();
437 for i in 0..n {
438 let o = st.push(h[i], l[i], c[i]).unwrap();
439 match (o, batch.sar[i], batch.direction[i]) {
440 (None, None, None) => {}
441 (Some(bar), Some(v), Some(d)) => {
442 assert!((bar.sar - v).abs() < 1e-9, "i={i}");
443 assert_eq!(bar.direction, d);
444 }
445 other => panic!("i={i}: {other:?}"),
446 }
447 }
448 }
449
450 #[test]
451 fn bad_params_err() {
452 assert!(ValidatedSar::new(SarParams::new(0.0, 0.02, 0.2)).is_err());
453 }
454}