1use crate::stocks::ta::common::{opt_cell, require_hlc, true_range};
59use crate::util::error::FinanceResult;
60use crate::util::primitives::PeriodLength;
61use crate::{columns_with_strings, print_table_locale_opt};
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
65pub struct AtrParams {
66 pub period: usize,
67}
68
69impl AtrParams {
70 pub const fn new(period: usize) -> Self {
71 Self { period }
72 }
73
74 pub const fn period_14() -> Self {
75 Self { period: 14 }
76 }
77
78 pub const fn period_10() -> Self {
79 Self { period: 10 }
80 }
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
85pub struct ValidatedAtr {
86 params: AtrParams,
87}
88
89impl ValidatedAtr {
90 pub fn new(params: AtrParams) -> FinanceResult<Self> {
91 PeriodLength::new(params.period)?;
92 Ok(Self { params })
93 }
94
95 pub fn params(self) -> AtrParams {
96 self.params
97 }
98
99 pub fn compute(self, high: &[f64], low: &[f64], close: &[f64]) -> FinanceResult<AtrSeries> {
100 atr_validated(high, low, close, self)
101 }
102}
103
104#[derive(Clone, Debug, PartialEq)]
105pub struct AtrSeries {
106 pub atr: Vec<Option<f64>>,
107 pub params: AtrParams,
108}
109
110impl AtrSeries {
111 pub fn last(&self) -> Option<f64> {
112 self.atr.iter().rev().find_map(|x| *x)
113 }
114}
115
116#[derive(Clone, Debug)]
117pub struct AtrSolution {
118 series: AtrSeries,
119 close: Vec<f64>,
120 formula: String,
121 symbolic_formula: String,
122}
123
124impl AtrSolution {
125 pub fn series(&self) -> &AtrSeries {
126 &self.series
127 }
128 pub fn formula(&self) -> &str {
129 &self.formula
130 }
131 pub fn symbolic_formula(&self) -> &str {
132 &self.symbolic_formula
133 }
134
135 pub fn print_table(&self) {
136 self.print_table_locale_opt(None, None);
137 }
138
139 pub fn print_table_locale(&self, locale: &num_format::Locale, precision: usize) {
140 self.print_table_locale_opt(Some(locale), Some(precision));
141 }
142
143 fn print_table_locale_opt(
144 &self,
145 locale: Option<&num_format::Locale>,
146 precision: Option<usize>,
147 ) {
148 let columns = columns_with_strings(&[
149 ("period", "i", true),
150 ("close", "f", true),
151 ("atr", "f", true),
152 ]);
153 let data = self
154 .close
155 .iter()
156 .enumerate()
157 .map(|(i, c)| vec![i.to_string(), c.to_string(), opt_cell(self.series.atr[i])])
158 .collect();
159 print_table_locale_opt(&columns, data, locale, precision);
160 }
161}
162
163#[derive(Clone, Debug, PartialEq)]
165pub struct AtrState {
166 params: AtrParams,
167 prev_close: Option<f64>,
168 atr: Option<f64>,
169 seed_tr: Vec<f64>,
170 last: Option<f64>,
171}
172
173impl AtrState {
174 pub fn new(params: AtrParams) -> FinanceResult<Self> {
175 PeriodLength::new(params.period)?;
176 Ok(Self {
177 params,
178 prev_close: None,
179 atr: None,
180 seed_tr: Vec::with_capacity(params.period),
181 last: None,
182 })
183 }
184
185 pub fn from_history(
186 params: AtrParams,
187 high: &[f64],
188 low: &[f64],
189 close: &[f64],
190 ) -> FinanceResult<Self> {
191 let mut s = Self::new(params)?;
192 let _ = s.push_bars(high, low, close)?;
193 Ok(s)
194 }
195
196 pub fn params(&self) -> AtrParams {
197 self.params
198 }
199
200 pub fn push(&mut self, high: f64, low: f64, close: f64) -> FinanceResult<Option<f64>> {
201 crate::util::error::require_finite("high", high)?;
202 crate::util::error::require_finite("low", low)?;
203 crate::util::error::require_finite("close", close)?;
204 if high < low {
205 return Err(crate::util::error::FinanceError::InvalidCashflow {
206 message: "high must be >= low",
207 });
208 }
209 let period = self.params.period;
210 let tr = true_range(high, low, self.prev_close);
211 let out = if self.atr.is_none() {
212 let _ = self.seed_tr.push(tr);
213 if self.seed_tr.len() == period {
214 let a = self.seed_tr.iter().sum::<f64>() / period as f64;
215 self.atr = Some(a);
216 self.last = Some(a);
217 self.last
218 } else {
219 None
220 }
221 } else {
222 let a = self.atr.unwrap();
223 let a = (a * (period as f64 - 1.0) + tr) / period as f64;
224 self.atr = Some(a);
225 self.last = Some(a);
226 self.last
227 };
228 self.prev_close = Some(close);
229 Ok(out)
230 }
231
232 pub fn push_bars(
233 &mut self,
234 high: &[f64],
235 low: &[f64],
236 close: &[f64],
237 ) -> FinanceResult<Vec<Option<f64>>> {
238 require_hlc(high, low, close)?;
239 let mut out = Vec::with_capacity(close.len());
240 for i in 0..close.len() {
241 out.push(self.push(high[i], low[i], close[i])?);
242 }
243 Ok(out)
244 }
245
246 pub fn last(&self) -> Option<f64> {
247 self.last
248 }
249
250 pub fn reset(&mut self) {
251 self.prev_close = None;
252 self.atr = None;
253 self.seed_tr.clear();
254 self.last = None;
255 }
256}
257
258pub fn atr(
259 high: &[f64],
260 low: &[f64],
261 close: &[f64],
262 params: AtrParams,
263) -> FinanceResult<AtrSeries> {
264 ValidatedAtr::new(params)?.compute(high, low, close)
265}
266
267pub fn true_range_series(high: &[f64], low: &[f64], close: &[f64]) -> FinanceResult<Vec<f64>> {
279 require_hlc(high, low, close)?;
280 let mut out = Vec::with_capacity(close.len());
281 let mut prev = None;
282 for i in 0..close.len() {
283 out.push(true_range(high[i], low[i], prev));
284 prev = Some(close[i]);
285 }
286 Ok(out)
287}
288
289pub fn natr(
302 high: &[f64],
303 low: &[f64],
304 close: &[f64],
305 params: AtrParams,
306) -> FinanceResult<NatrSeries> {
307 let a = atr(high, low, close, params)?;
308 let mut natr = Vec::with_capacity(close.len());
309 for i in 0..close.len() {
310 let v = match a.atr[i] {
311 Some(atr_v) if close[i] != 0.0 => Some(100.0 * atr_v / close[i]),
312 _ => None,
313 };
314 natr.push(v);
315 }
316 Ok(NatrSeries { natr, params })
317}
318
319#[derive(Clone, Debug, PartialEq)]
321pub struct NatrSeries {
322 pub natr: Vec<Option<f64>>,
323 pub params: AtrParams,
324}
325
326impl NatrSeries {
327 pub fn last(&self) -> Option<f64> {
328 self.natr.iter().rev().find_map(|x| *x)
329 }
330}
331
332#[derive(Clone, Debug)]
334pub struct NatrState {
335 atr: AtrState,
336 last: Option<f64>,
337}
338
339impl NatrState {
340 pub fn new(params: AtrParams) -> FinanceResult<Self> {
341 Ok(Self {
342 atr: AtrState::new(params)?,
343 last: None,
344 })
345 }
346
347 pub fn from_history(
348 params: AtrParams,
349 high: &[f64],
350 low: &[f64],
351 close: &[f64],
352 ) -> FinanceResult<Self> {
353 let mut s = Self::new(params)?;
354 let _ = s.push_bars(high, low, close)?;
355 Ok(s)
356 }
357
358 pub fn params(&self) -> AtrParams {
359 self.atr.params
360 }
361
362 pub fn reset(&mut self) {
363 self.atr.reset();
364 self.last = None;
365 }
366
367 pub fn push(&mut self, high: f64, low: f64, close: f64) -> FinanceResult<Option<f64>> {
368 let a = self.atr.push(high, low, close)?;
369 let out = match a {
370 Some(atr_v) if close != 0.0 => Some(100.0 * atr_v / close),
371 _ => None,
372 };
373 self.last = out;
374 Ok(out)
375 }
376
377 pub fn push_bars(
378 &mut self,
379 high: &[f64],
380 low: &[f64],
381 close: &[f64],
382 ) -> FinanceResult<Vec<Option<f64>>> {
383 require_hlc(high, low, close)?;
384 let mut out = Vec::with_capacity(close.len());
385 for i in 0..close.len() {
386 out.push(self.push(high[i], low[i], close[i])?);
387 }
388 Ok(out)
389 }
390
391 pub fn last(&self) -> Option<f64> {
392 self.last
393 }
394}
395
396fn atr_validated(
397 high: &[f64],
398 low: &[f64],
399 close: &[f64],
400 eng: ValidatedAtr,
401) -> FinanceResult<AtrSeries> {
402 require_hlc(high, low, close)?;
403 let mut state = AtrState::new(eng.params)?;
404 let atr = state.push_bars(high, low, close)?;
405 Ok(AtrSeries {
406 atr,
407 params: eng.params,
408 })
409}
410
411pub fn atr_solution(
421 high: &[f64],
422 low: &[f64],
423 close: &[f64],
424 params: AtrParams,
425) -> FinanceResult<AtrSolution> {
426 let series = atr(high, low, close, params)?;
427 Ok(AtrSolution {
428 series,
429 close: close.to_vec(),
430 formula: format!("ATR({}) = Wilder smooth of true range", params.period),
431 symbolic_formula: "ATR = Wilder(TR); TR = max(H-L, |H-Cprev|, |L-Cprev|)".to_string(),
432 })
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438
439 #[test]
440 fn constant_range() {
441 let n = 30usize;
442 let high: Vec<_> = (0..n).map(|_| 102.0).collect();
443 let low: Vec<_> = (0..n).map(|_| 100.0).collect();
444 let close: Vec<_> = (0..n).map(|_| 101.0).collect();
445 let s = atr(&high, &low, &close, AtrParams::period_14()).unwrap();
446 assert!((s.last().unwrap() - 2.0).abs() < 1e-9);
447 }
448
449 #[test]
450 fn state_parity() {
451 let n = 40usize;
452 let high: Vec<_> = (0..n).map(|i| 101.0 + i as f64 * 0.1).collect();
453 let low: Vec<_> = (0..n).map(|i| 99.0 + i as f64 * 0.1).collect();
454 let close: Vec<_> = (0..n).map(|i| 100.0 + i as f64 * 0.1).collect();
455 let batch = atr(&high, &low, &close, AtrParams::period_10()).unwrap();
456 let st = AtrState::from_history(AtrParams::period_10(), &high, &low, &close).unwrap();
457 assert!((batch.last().unwrap() - st.last().unwrap()).abs() < 1e-9);
458 }
459
460 #[test]
461 fn high_lt_low_err() {
462 let high = vec![10.0, 9.0];
463 let low = vec![9.0, 10.0]; let close = vec![9.5, 9.5];
465 assert!(atr(&high, &low, &close, AtrParams::period_14()).is_err());
466 }
467
468 #[test]
469 fn first_atr_at_period_minus_one() {
470 let n = 20usize;
471 let high: Vec<_> = (0..n).map(|_| 102.0).collect();
472 let low: Vec<_> = (0..n).map(|_| 100.0).collect();
473 let close: Vec<_> = (0..n).map(|_| 101.0).collect();
474 let s = atr(&high, &low, &close, AtrParams::period_14()).unwrap();
475 assert!(s.atr[12].is_none());
476 assert!(s.atr[13].is_some()); }
478
479 #[test]
480 fn true_range_and_natr() {
481 let h = [10.0, 12.0];
482 let l = [9.0, 10.0];
483 let c = [9.5, 11.0];
484 let tr = true_range_series(&h, &l, &c).unwrap();
485 assert!((tr[0] - 1.0).abs() < 1e-12);
486 assert!((tr[1] - 2.5).abs() < 1e-12);
487 let n = 30usize;
488 let high: Vec<_> = (0..n).map(|_| 102.0).collect();
489 let low: Vec<_> = (0..n).map(|_| 100.0).collect();
490 let close: Vec<_> = (0..n).map(|_| 100.0).collect();
491 let s = natr(&high, &low, &close, AtrParams::period_14()).unwrap();
492 assert!((s.last().unwrap() - 2.0).abs() < 1e-9);
493 }
494
495 #[test]
496 fn gap_increases_atr_vs_no_gap() {
497 let n = 30usize;
499 let mut high: Vec<f64> = (0..n).map(|_| 102.0).collect();
500 let mut low: Vec<f64> = (0..n).map(|_| 100.0).collect();
501 let mut close: Vec<f64> = (0..n).map(|_| 101.0).collect();
502 let base = atr(&high, &low, &close, AtrParams::period_14())
503 .unwrap()
504 .last()
505 .unwrap();
506 high[16] = 110.0;
508 low[16] = 108.0;
509 close[16] = 109.0;
510 let gapped = atr(&high, &low, &close, AtrParams::period_14())
511 .unwrap()
512 .last()
513 .unwrap();
514 assert!(gapped > base);
515 }
516}