1use crate::traits::Next;
4
5#[derive(Debug, Clone)]
7struct DmiCore {
8 timeperiod: usize,
9 period_f: f64,
10 prev_high: Option<f64>,
11 prev_low: Option<f64>,
12 prev_close: Option<f64>,
13 bar_index: usize,
14 sum_tr: f64,
15 sum_pdm: f64,
16 sum_mdm: f64,
17 seeded: bool,
18}
19
20impl DmiCore {
21 fn new(timeperiod: usize) -> Self {
22 Self {
23 timeperiod,
24 period_f: timeperiod as f64,
25 prev_high: None,
26 prev_low: None,
27 prev_close: None,
28 bar_index: 0,
29 sum_tr: 0.0,
30 sum_pdm: 0.0,
31 sum_mdm: 0.0,
32 seeded: false,
33 }
34 }
35
36 #[inline]
37 fn dm_components(&self, high: f64, low: f64) -> (f64, f64, f64) {
38 let (ph, pl, pc) = match (self.prev_high, self.prev_low, self.prev_close) {
39 (Some(ph), Some(pl), Some(pc)) => (ph, pl, pc),
40 _ => return (0.0, 0.0, 0.0),
41 };
42 let hl = high - low;
43 let hc = (high - pc).abs();
44 let lc = (low - pc).abs();
45 let tr = hl.max(hc).max(lc);
46 let up = high - ph;
47 let down = pl - low;
48 let pdm = if up > down && up > 0.0 { up } else { 0.0 };
49 let mdm = if down > up && down > 0.0 { down } else { 0.0 };
50 (tr, pdm, mdm)
51 }
52
53 fn step(&mut self, high: f64, low: f64, close: f64) -> Option<(f64, f64, f64)> {
55 let period = self.timeperiod;
56 if period < 1 {
57 return None;
58 }
59
60 if self.prev_high.is_none() {
61 self.prev_high = Some(high);
62 self.prev_low = Some(low);
63 self.prev_close = Some(close);
64 self.bar_index = 1;
65 return None;
66 }
67
68 let (tr, pdm, mdm) = self.dm_components(high, low);
69 self.prev_high = Some(high);
70 self.prev_low = Some(low);
71 self.prev_close = Some(close);
72 let i = self.bar_index;
73 self.bar_index += 1;
74
75 if !self.seeded {
76 if i < period {
77 self.sum_tr += tr;
78 self.sum_pdm += pdm;
79 self.sum_mdm += mdm;
80 return None;
81 }
82 self.seeded = true;
83 }
84 self.sum_tr = self.sum_tr - self.sum_tr / self.period_f + tr;
85 self.sum_pdm = self.sum_pdm - self.sum_pdm / self.period_f + pdm;
86 self.sum_mdm = self.sum_mdm - self.sum_mdm / self.period_f + mdm;
87
88 if self.sum_tr <= 0.0 {
89 return None;
90 }
91 let pdi = 100.0 * self.sum_pdm / self.sum_tr;
92 let mdi = 100.0 * self.sum_mdm / self.sum_tr;
93 let sum_di = pdi + mdi;
94 let dx = if sum_di > 0.0 {
95 100.0 * (pdi - mdi).abs() / sum_di
96 } else {
97 0.0
98 };
99 Some((pdi, mdi, dx))
100 }
101}
102
103#[derive(Debug, Clone)]
105#[allow(non_camel_case_types)]
106pub struct PLUS_DI {
107 pub timeperiod: usize,
108 core: DmiCore,
109}
110
111impl PLUS_DI {
112 pub fn new(timeperiod: usize) -> Self {
113 Self {
114 timeperiod,
115 core: DmiCore::new(timeperiod),
116 }
117 }
118}
119
120impl Next<(f64, f64, f64)> for PLUS_DI {
121 type Output = f64;
122
123 fn next(&mut self, (high, low, close): (f64, f64, f64)) -> Self::Output {
124 match self.core.step(high, low, close) {
125 Some((pdi, _, _)) => pdi,
126 None => f64::NAN,
127 }
128 }
129}
130
131#[derive(Debug, Clone)]
133#[allow(non_camel_case_types)]
134pub struct MINUS_DI {
135 pub timeperiod: usize,
136 core: DmiCore,
137}
138
139impl MINUS_DI {
140 pub fn new(timeperiod: usize) -> Self {
141 Self {
142 timeperiod,
143 core: DmiCore::new(timeperiod),
144 }
145 }
146}
147
148impl Next<(f64, f64, f64)> for MINUS_DI {
149 type Output = f64;
150
151 fn next(&mut self, (high, low, close): (f64, f64, f64)) -> Self::Output {
152 match self.core.step(high, low, close) {
153 Some((_, mdi, _)) => mdi,
154 None => f64::NAN,
155 }
156 }
157}
158
159#[derive(Debug, Clone)]
161#[allow(non_camel_case_types)]
162pub struct DX {
163 pub timeperiod: usize,
164 core: DmiCore,
165}
166
167impl DX {
168 pub fn new(timeperiod: usize) -> Self {
169 Self {
170 timeperiod,
171 core: DmiCore::new(timeperiod),
172 }
173 }
174}
175
176impl Next<(f64, f64, f64)> for DX {
177 type Output = f64;
178
179 fn next(&mut self, (high, low, close): (f64, f64, f64)) -> Self::Output {
180 match self.core.step(high, low, close) {
181 Some((_, _, dx)) => dx,
182 None => f64::NAN,
183 }
184 }
185}
186
187#[derive(Debug, Clone)]
189#[allow(non_camel_case_types)]
190pub struct ADX {
191 pub timeperiod: usize,
192 core: DmiCore,
193 dx_values: Vec<f64>,
194 adx: f64,
195 adx_ready: bool,
196}
197
198impl ADX {
199 pub fn new(timeperiod: usize) -> Self {
200 Self {
201 timeperiod,
202 core: DmiCore::new(timeperiod),
203 dx_values: Vec::new(),
204 adx: 0.0,
205 adx_ready: false,
206 }
207 }
208}
209
210impl Next<(f64, f64, f64)> for ADX {
211 type Output = f64;
212
213 fn next(&mut self, (high, low, close): (f64, f64, f64)) -> Self::Output {
214 let period = self.timeperiod;
215 if period < 2 {
216 return f64::NAN;
217 }
218
219 let Some((_, _, dx)) = self.core.step(high, low, close) else {
220 return f64::NAN;
221 };
222
223 let adx_start = 2 * period - 1;
224 let bar = self.core.bar_index.saturating_sub(1);
225
226 if bar < period {
227 return f64::NAN;
228 }
229
230 if bar < adx_start {
231 self.dx_values.push(dx);
232 return f64::NAN;
233 }
234
235 if bar == adx_start {
236 self.dx_values.push(dx);
237 let seed: f64 = self.dx_values.iter().sum::<f64>() / period as f64;
238 self.adx = seed;
239 self.adx_ready = true;
240 return seed;
241 }
242
243 if self.adx_ready {
244 self.adx = (self.adx * (period as f64 - 1.0) + dx) / period as f64;
245 return self.adx;
246 }
247
248 f64::NAN
249 }
250}
251
252#[derive(Debug, Clone)]
254#[allow(non_camel_case_types)]
255pub struct ADXR {
256 pub timeperiod: usize,
257 adx: ADX,
258 adx_history: Vec<f64>,
259}
260
261impl ADXR {
262 pub fn new(timeperiod: usize) -> Self {
263 Self {
264 timeperiod,
265 adx: ADX::new(timeperiod),
266 adx_history: Vec::new(),
267 }
268 }
269}
270
271impl Next<(f64, f64, f64)> for ADXR {
272 type Output = f64;
273
274 fn next(&mut self, (high, low, close): (f64, f64, f64)) -> Self::Output {
275 let period = self.timeperiod;
276 let adx_val = self.adx.next((high, low, close));
277 self.adx_history.push(adx_val);
278
279 let adxr_lookback = 3 * period - 2;
280 let bar = self.adx_history.len().saturating_sub(1);
281 if bar < adxr_lookback {
282 return f64::NAN;
283 }
284 if adx_val.is_nan() {
285 return f64::NAN;
286 }
287 let past_idx = bar + 1 - period;
288 let past = self.adx_history[past_idx];
289 if past.is_nan() {
290 return f64::NAN;
291 }
292 (adx_val + past) / 2.0
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299 use proptest::prelude::*;
300
301 fn hlc(len: usize, h: &[f64], l: &[f64], c: &[f64]) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
302 let mut high = Vec::with_capacity(len);
303 let mut low = Vec::with_capacity(len);
304 let mut close = Vec::with_capacity(len);
305 for i in 0..len {
306 let val_h = h[i];
307 let val_l = l[i];
308 let val_c = c[i];
309 high.push(val_h.max(val_l).max(val_c));
310 low.push(val_h.min(val_l).min(val_c));
311 close.push(val_c);
312 }
313 (high, low, close)
314 }
315
316 proptest! {
317 #[test]
318 fn test_adx_parity(
319 h in prop::collection::vec(1.0..100.0, 1..100),
320 l in prop::collection::vec(1.0..100.0, 1..100),
321 c in prop::collection::vec(1.0..100.0, 1..100)
322 ) {
323 let len = h.len().min(l.len()).min(c.len());
324 if len < 30 { return Ok(()); }
325 let (high, low, close) = hlc(len, &h, &l, &c);
326 let period = 14;
327 let mut adx = ADX::new(period);
328 let streaming: Vec<f64> = (0..len)
329 .map(|i| adx.next((high[i], low[i], close[i])))
330 .collect();
331 let batch = talib_rs::momentum::adx(&high, &low, &close, period)
332 .unwrap_or_else(|_| vec![f64::NAN; len]);
333 for (s, b) in streaming.iter().zip(batch.iter()) {
334 if s.is_nan() { assert!(b.is_nan()); }
335 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
336 }
337 }
338
339 #[test]
340 fn test_dx_parity(
341 h in prop::collection::vec(1.0..100.0, 1..100),
342 l in prop::collection::vec(1.0..100.0, 1..100),
343 c in prop::collection::vec(1.0..100.0, 1..100)
344 ) {
345 let len = h.len().min(l.len()).min(c.len());
346 if len < 20 { return Ok(()); }
347 let (high, low, close) = hlc(len, &h, &l, &c);
348 let period = 14;
349 let mut dx = DX::new(period);
350 let streaming: Vec<f64> = (0..len)
351 .map(|i| dx.next((high[i], low[i], close[i])))
352 .collect();
353 let batch = talib_rs::momentum::dx(&high, &low, &close, period)
354 .unwrap_or_else(|_| vec![f64::NAN; len]);
355 for (s, b) in streaming.iter().zip(batch.iter()) {
356 if s.is_nan() { assert!(b.is_nan()); }
357 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
358 }
359 }
360
361 #[test]
362 fn test_plus_di_parity(
363 h in prop::collection::vec(1.0..100.0, 1..100),
364 l in prop::collection::vec(1.0..100.0, 1..100),
365 c in prop::collection::vec(1.0..100.0, 1..100)
366 ) {
367 let len = h.len().min(l.len()).min(c.len());
368 if len < 20 { return Ok(()); }
369 let (high, low, close) = hlc(len, &h, &l, &c);
370 let period = 14;
371 let mut pdi = PLUS_DI::new(period);
372 let streaming: Vec<f64> = (0..len)
373 .map(|i| pdi.next((high[i], low[i], close[i])))
374 .collect();
375 let batch = talib_rs::momentum::plus_di(&high, &low, &close, period)
376 .unwrap_or_else(|_| vec![f64::NAN; len]);
377 for (s, b) in streaming.iter().zip(batch.iter()) {
378 if s.is_nan() { assert!(b.is_nan()); }
379 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
380 }
381 }
382 }
383}