wickra_embed_core/indicators/
atr.rs1use crate::ohlcv::Candle;
5use crate::traits::Indicator;
6
7#[derive(Debug, Clone)]
29pub struct Atr<const N: usize> {
30 n_minus_1: f64,
31 inv_period: f64,
32 prev_close: Option<f64>,
33 seed_count: usize,
37 seed_sum: f64,
38 avg: f64,
39 seeded: bool,
40}
41
42impl<const N: usize> Atr<N> {
43 #[must_use]
45 pub fn new() -> Self {
46 Self {
47 n_minus_1: (N - 1) as f64,
48 inv_period: 1.0 / N as f64,
49 prev_close: None,
50 seed_count: 0,
51 seed_sum: 0.0,
52 avg: 0.0,
53 seeded: false,
54 }
55 }
56
57 #[must_use]
59 pub const fn value(&self) -> Option<f64> {
60 if self.seeded {
61 Some(self.avg)
62 } else {
63 None
64 }
65 }
66}
67
68impl<const N: usize> Default for Atr<N> {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl<const N: usize> Indicator for Atr<N> {
75 type Input = Candle;
76
77 fn update(&mut self, candle: Candle) -> Option<f64> {
78 let tr = candle.true_range(self.prev_close);
79 self.prev_close = Some(candle.close);
80
81 if self.seeded {
82 let new_avg = crate::math::mul_add(self.avg, self.n_minus_1, tr) * self.inv_period;
83 self.avg = new_avg;
84 return Some(new_avg);
85 }
86
87 self.seed_sum += tr;
88 self.seed_count += 1;
89 if self.seed_count == N {
90 let seed = self.seed_sum / N as f64;
91 self.avg = seed;
92 self.seeded = true;
93 return Some(seed);
94 }
95 None
96 }
97
98 fn reset(&mut self) {
99 *self = Self::new();
100 }
101
102 fn warmup_period(&self) -> usize {
103 N
104 }
105
106 fn is_ready(&self) -> bool {
107 self.seeded
108 }
109
110 fn name(&self) -> &'static str {
111 "atr"
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::Atr;
118 use crate::ohlcv::Candle;
119 use crate::traits::Indicator;
120
121 fn candle(i: i64) -> Candle {
122 let base = 100.0 + i as f64;
123 Candle::new(base, base + 2.0, base - 2.0, base + 1.0, 10.0, i)
124 }
125
126 #[test]
127 fn warmup_then_value() {
128 let mut atr = Atr::<5>::new();
129 for i in 0..4 {
130 assert_eq!(atr.update(candle(i)), None);
131 }
132 let v = atr.update(candle(4));
133 assert!(v.is_some());
134 assert!(atr.is_ready());
135 assert_eq!(atr.warmup_period(), 5);
136 }
137
138 #[test]
139 fn reset_clears_state() {
140 let mut atr = Atr::<5>::new();
141 for i in 0..20 {
142 atr.update(candle(i));
143 }
144 atr.reset();
145 assert!(!atr.is_ready());
146 assert_eq!(atr.value(), None);
147 }
148
149 #[test]
150 fn byte_parity_with_wickra_core() {
151 let mut ours = Atr::<14>::new();
152 let mut theirs = wickra_core::Atr::new(14).unwrap();
153 for i in 0..1000 {
154 let base = 100.0 + 20.0 * (f64::from(i) * 0.08).sin();
155 let (open, high, low, close) = (base, base + 3.0, base - 3.0, base + 1.5);
156 let ours_c = Candle::new(open, high, low, close, 10.0, i64::from(i));
157 let theirs_c =
158 wickra_core::Candle::new(open, high, low, close, 10.0, i64::from(i)).unwrap();
159 let a = ours.update(ours_c);
160 let b = <wickra_core::Atr as wickra_core::Indicator>::update(&mut theirs, theirs_c);
161 assert_eq!(a.map(f64::to_bits), b.map(f64::to_bits), "index {i}");
162 }
163 }
164}