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