wickra_core/indicators/
adxr.rs1use std::collections::VecDeque;
4
5use crate::error::{Error, Result};
6use crate::indicators::adx::Adx;
7use crate::ohlcv::Candle;
8use crate::traits::Indicator;
9
10#[derive(Debug, Clone)]
45pub struct Adxr {
46 period: usize,
47 adx: Adx,
48 window: VecDeque<f64>,
52 last: Option<f64>,
53}
54
55impl Adxr {
56 pub fn new(period: usize) -> Result<Self> {
62 if period == 0 {
63 return Err(Error::PeriodZero);
64 }
65 Ok(Self {
66 period,
67 adx: Adx::new(period)?,
68 window: VecDeque::with_capacity(period),
69 last: None,
70 })
71 }
72
73 pub const fn period(&self) -> usize {
75 self.period
76 }
77
78 pub const fn value(&self) -> Option<f64> {
80 self.last
81 }
82}
83
84impl Indicator for Adxr {
85 type Input = Candle;
86 type Output = f64;
87
88 fn update(&mut self, candle: Candle) -> Option<f64> {
89 let adx_value = self.adx.update(candle)?.adx;
90 if self.window.len() == self.period {
91 self.window.pop_front();
92 }
93 self.window.push_back(adx_value);
94 if self.window.len() < self.period {
95 return None;
96 }
97 let oldest = *self.window.front().expect("ring is full");
98 let adxr = f64::midpoint(adx_value, oldest);
99 self.last = Some(adxr);
100 Some(adxr)
101 }
102
103 fn reset(&mut self) {
104 self.adx.reset();
105 self.window.clear();
106 self.last = None;
107 }
108
109 fn warmup_period(&self) -> usize {
110 3 * self.period - 1
114 }
115
116 fn is_ready(&self) -> bool {
117 self.last.is_some()
118 }
119
120 fn name(&self) -> &'static str {
121 "ADXR"
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use crate::traits::BatchExt;
129 use approx::assert_relative_eq;
130
131 fn candle(h: f64, l: f64, c: f64, ts: i64) -> Candle {
132 Candle::new(c, h, l, c, 1.0, ts).unwrap()
133 }
134
135 #[test]
136 fn rejects_zero_period() {
137 assert!(matches!(Adxr::new(0), Err(Error::PeriodZero)));
138 }
139
140 #[test]
141 fn accessors_and_metadata() {
142 let mut a = Adxr::new(14).unwrap();
143 assert_eq!(a.period(), 14);
144 assert_eq!(a.warmup_period(), 41);
145 assert_eq!(a.name(), "ADXR");
146 assert!(a.value().is_none());
147 for i in 0..50_i64 {
149 let base = 100.0 + (i as f64) * 2.0;
150 a.update(candle(base + 1.0, base - 0.5, base + 0.5, i));
151 }
152 assert!(a.value().is_some());
153 }
154
155 #[test]
156 fn pure_uptrend_yields_finite_positive_adxr() {
157 let candles: Vec<Candle> = (0..80_i64)
158 .map(|i| {
159 let base = 100.0 + (i as f64) * 2.0;
160 candle(base + 1.0, base - 0.5, base + 0.5, i)
161 })
162 .collect();
163 let mut a = Adxr::new(14).unwrap();
164 let last = a.batch(&candles).into_iter().flatten().last().unwrap();
165 assert!(last > 0.0 && last <= 100.0 + 1e-9);
166 }
167
168 #[test]
169 fn constant_series_yields_zero_adxr() {
170 let candles: Vec<Candle> = (0..50_i64).map(|i| candle(10.0, 10.0, 10.0, i)).collect();
171 let mut a = Adxr::new(5).unwrap();
172 let last = a.batch(&candles).into_iter().flatten().last().unwrap();
173 assert_eq!(last, 0.0);
174 }
175
176 #[test]
177 fn first_emission_at_warmup_period() {
178 let candles: Vec<Candle> = (0..80_i64)
179 .map(|i| {
180 let p = 100.0 + ((i as f64) * 0.3).sin() * 5.0;
181 candle(p + 1.0, p - 1.0, p, i)
182 })
183 .collect();
184 let mut a = Adxr::new(5).unwrap();
185 let out = a.batch(&candles);
186 let warmup = 3 * 5 - 1; for v in out.iter().take(warmup - 1) {
188 assert!(v.is_none());
189 }
190 assert!(out[warmup - 1].is_some());
191 }
192
193 #[test]
194 fn reference_value_against_explicit_adx_average() {
195 let candles: Vec<Candle> = (0..60_i64)
199 .map(|i| {
200 let p = 100.0 + ((i as f64) * 0.2).sin() * 6.0;
201 candle(p + 1.5, p - 1.5, p, i)
202 })
203 .collect();
204 let period = 5;
205 let mut adx = Adx::new(period).unwrap();
206 let adx_out: Vec<_> = adx
207 .batch(&candles)
208 .into_iter()
209 .map(|o| o.map(|x| x.adx))
210 .collect();
211 let mut adxr = Adxr::new(period).unwrap();
212 let adxr_out = adxr.batch(&candles);
213 let first = 3 * period - 2;
215 let prev = first - (period - 1);
216 let expected = f64::midpoint(adx_out[first].unwrap(), adx_out[prev].unwrap());
217 assert_relative_eq!(adxr_out[first].unwrap(), expected, epsilon = 1e-12);
218 }
219
220 #[test]
221 fn batch_equals_streaming() {
222 let candles: Vec<Candle> = (0..60_i64)
223 .map(|i| {
224 let p = 100.0 + ((i as f64) * 0.25).sin() * 5.0;
225 candle(p + 1.0, p - 1.0, p, i)
226 })
227 .collect();
228 let mut a = Adxr::new(7).unwrap();
229 let mut b = Adxr::new(7).unwrap();
230 assert_eq!(
231 a.batch(&candles),
232 candles.iter().map(|c| b.update(*c)).collect::<Vec<_>>()
233 );
234 }
235
236 #[test]
237 fn reset_clears_state() {
238 let candles: Vec<Candle> = (0..60_i64).map(|i| candle(11.0, 9.0, 10.0, i)).collect();
239 let mut a = Adxr::new(5).unwrap();
240 a.batch(&candles);
241 assert!(a.is_ready());
242 a.reset();
243 assert!(!a.is_ready());
244 assert_eq!(a.update(candles[0]), None);
245 }
246}