quantwave_core/indicators/incremental/
dm.rs1use crate::traits::Next;
4
5#[derive(Debug, Clone)]
6struct PlusDmCore {
7 timeperiod: usize,
8 period_f: f64,
9 prev_high: Option<f64>,
10 prev_low: Option<f64>,
11 bar_index: usize,
12 sum: f64,
13 seeded: bool,
14}
15
16impl PlusDmCore {
17 fn new(timeperiod: usize) -> Self {
18 Self {
19 timeperiod,
20 period_f: timeperiod as f64,
21 prev_high: None,
22 prev_low: None,
23 bar_index: 0,
24 sum: 0.0,
25 seeded: false,
26 }
27 }
28
29 fn step(&mut self, high: f64, low: f64) -> Option<f64> {
30 let period = self.timeperiod;
31 if period < 1 {
32 return None;
33 }
34 if self.prev_high.is_none() {
35 self.prev_high = Some(high);
36 self.prev_low = Some(low);
37 self.bar_index = 1;
38 return None;
39 }
40 let (ph, pl) = match (self.prev_high, self.prev_low) {
41 (Some(ph), Some(pl)) => (ph, pl),
42 _ => {
43 self.prev_high = Some(high);
44 self.prev_low = Some(low);
45 self.bar_index = 1;
46 return None;
47 }
48 };
49 self.prev_high = Some(high);
50 self.prev_low = Some(low);
51 let i = self.bar_index;
52 self.bar_index += 1;
53
54 let up = high - ph;
55 let down = pl - low;
56 let pdm = if up > down && up > 0.0 { up } else { 0.0 };
57
58 if !self.seeded {
59 if i < period - 1 {
60 self.sum += pdm;
61 return None;
62 }
63 if i == period - 1 {
64 self.sum += pdm;
65 return Some(self.sum);
66 }
67 self.seeded = true;
68 }
69 self.sum = self.sum - self.sum / self.period_f + pdm;
70 Some(self.sum)
71 }
72}
73
74#[derive(Debug, Clone)]
75struct MinusDmCore {
76 timeperiod: usize,
77 period_f: f64,
78 prev_high: Option<f64>,
79 prev_low: Option<f64>,
80 bar_index: usize,
81 sum: f64,
82 seeded: bool,
83}
84
85impl MinusDmCore {
86 fn new(timeperiod: usize) -> Self {
87 Self {
88 timeperiod,
89 period_f: timeperiod as f64,
90 prev_high: None,
91 prev_low: None,
92 bar_index: 0,
93 sum: 0.0,
94 seeded: false,
95 }
96 }
97
98 fn step(&mut self, high: f64, low: f64) -> Option<f64> {
99 let period = self.timeperiod;
100 if period < 1 {
101 return None;
102 }
103 if self.prev_high.is_none() {
104 self.prev_high = Some(high);
105 self.prev_low = Some(low);
106 self.bar_index = 1;
107 return None;
108 }
109 let (ph, pl) = match (self.prev_high, self.prev_low) {
110 (Some(ph), Some(pl)) => (ph, pl),
111 _ => {
112 self.prev_high = Some(high);
113 self.prev_low = Some(low);
114 self.bar_index = 1;
115 return None;
116 }
117 };
118 self.prev_high = Some(high);
119 self.prev_low = Some(low);
120 let i = self.bar_index;
121 self.bar_index += 1;
122
123 let up = high - ph;
124 let down = pl - low;
125 let mdm = if down > up && down > 0.0 { down } else { 0.0 };
126
127 if !self.seeded {
128 if i < period - 1 {
129 self.sum += mdm;
130 return None;
131 }
132 if i == period - 1 {
133 self.sum += mdm;
134 return Some(self.sum);
135 }
136 self.seeded = true;
137 }
138 self.sum = self.sum - self.sum / self.period_f + mdm;
139 Some(self.sum)
140 }
141}
142
143#[derive(Debug, Clone)]
145#[allow(non_camel_case_types)]
146pub struct PLUS_DM {
147 pub timeperiod: usize,
148 core: PlusDmCore,
149}
150
151impl PLUS_DM {
152 pub fn new(timeperiod: usize) -> Self {
153 Self {
154 timeperiod,
155 core: PlusDmCore::new(timeperiod),
156 }
157 }
158}
159
160impl Next<(f64, f64)> for PLUS_DM {
161 type Output = f64;
162
163 fn next(&mut self, (high, low): (f64, f64)) -> Self::Output {
164 self.core.step(high, low).unwrap_or(f64::NAN)
165 }
166}
167
168#[derive(Debug, Clone)]
170#[allow(non_camel_case_types)]
171pub struct MINUS_DM {
172 pub timeperiod: usize,
173 core: MinusDmCore,
174}
175
176impl MINUS_DM {
177 pub fn new(timeperiod: usize) -> Self {
178 Self {
179 timeperiod,
180 core: MinusDmCore::new(timeperiod),
181 }
182 }
183}
184
185impl Next<(f64, f64)> for MINUS_DM {
186 type Output = f64;
187
188 fn next(&mut self, (high, low): (f64, f64)) -> Self::Output {
189 self.core.step(high, low).unwrap_or(f64::NAN)
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use proptest::prelude::*;
197
198 proptest! {
199 #[test]
200 fn test_plus_dm_parity(
201 highs in prop::collection::vec(1.0..100.0, 1..100),
202 lows in prop::collection::vec(1.0..100.0, 1..100)
203 ) {
204 let len = highs.len().min(lows.len());
205 if len < 20 { return Ok(()); }
206 let mut high = Vec::with_capacity(len);
207 let mut low = Vec::with_capacity(len);
208 for i in 0..len {
209 let hi: f64 = highs[i];
210 let lo: f64 = lows[i];
211 high.push(hi.max(lo));
212 low.push(hi.min(lo));
213 }
214 let period = 14;
215 let mut pdm = PLUS_DM::new(period);
216 let streaming: Vec<f64> = (0..len).map(|i| pdm.next((high[i], low[i]))).collect();
217 let batch = talib_rs::momentum::plus_dm(&high, &low, period)
218 .unwrap_or_else(|_| vec![f64::NAN; len]);
219 for (s, b) in streaming.iter().zip(batch.iter()) {
220 if s.is_nan() { assert!(b.is_nan()); }
221 else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
222 }
223 }
224 }
225}