quantwave_core/indicators/incremental/
willr.rs1use crate::traits::Next;
4
5#[derive(Debug, Clone)]
7#[allow(non_camel_case_types)]
8pub struct WILLR {
9 pub timeperiod: usize,
10 high: Vec<f64>,
11 low: Vec<f64>,
12 highest: f64,
13 highest_idx: usize,
14 lowest: f64,
15 lowest_idx: usize,
16 trailing_idx: usize,
17}
18
19impl WILLR {
20 pub fn new(timeperiod: usize) -> Self {
21 Self {
22 timeperiod,
23 high: Vec::new(),
24 low: Vec::new(),
25 highest: f64::NEG_INFINITY,
26 highest_idx: 0,
27 lowest: f64::INFINITY,
28 lowest_idx: 0,
29 trailing_idx: 0,
30 }
31 }
32
33 #[inline]
34 fn willr_from_range(highest: f64, lowest: f64, close: f64) -> f64 {
35 let range = highest - lowest;
36 if range > 0.0 {
37 -100.0 * (highest - close) / range
38 } else {
39 0.0
40 }
41 }
42}
43
44impl Next<(f64, f64, f64)> for WILLR {
45 type Output = f64;
46
47 fn next(&mut self, (h, l, c): (f64, f64, f64)) -> Self::Output {
48 let timeperiod = self.timeperiod;
49 if timeperiod < 2 {
50 return f64::NAN;
51 }
52 let lookback = timeperiod - 1;
53
54 self.high.push(h);
55 self.low.push(l);
56 let today = self.high.len() - 1;
57
58 if today < lookback {
59 if today == 0 {
60 self.highest = h;
61 self.highest_idx = 0;
62 self.lowest = l;
63 self.lowest_idx = 0;
64 } else {
65 if h >= self.highest {
66 self.highest = h;
67 self.highest_idx = today;
68 }
69 if l <= self.lowest {
70 self.lowest = l;
71 self.lowest_idx = today;
72 }
73 }
74 return f64::NAN;
75 }
76
77 if today == lookback {
78 if h >= self.highest {
79 self.highest = h;
80 self.highest_idx = today;
81 }
82 if l <= self.lowest {
83 self.lowest = l;
84 self.lowest_idx = today;
85 }
86 let out = Self::willr_from_range(self.highest, self.lowest, c);
87 self.trailing_idx = 1;
88 return out;
89 }
90
91 let high = &self.high;
92 let low = &self.low;
93
94 if self.highest_idx < self.trailing_idx {
95 self.highest_idx = self.trailing_idx;
96 self.highest = high[self.trailing_idx];
97 for (j, &val) in high[self.trailing_idx + 1..=today].iter().enumerate() {
98 if val >= self.highest {
99 self.highest = val;
100 self.highest_idx = self.trailing_idx + 1 + j;
101 }
102 }
103 } else if h >= self.highest {
104 self.highest_idx = today;
105 self.highest = h;
106 }
107
108 if self.lowest_idx < self.trailing_idx {
109 self.lowest_idx = self.trailing_idx;
110 self.lowest = low[self.trailing_idx];
111 for (j, &val) in low[self.trailing_idx + 1..=today].iter().enumerate() {
112 if val <= self.lowest {
113 self.lowest = val;
114 self.lowest_idx = self.trailing_idx + 1 + j;
115 }
116 }
117 } else if l <= self.lowest {
118 self.lowest_idx = today;
119 self.lowest = l;
120 }
121
122 let out = Self::willr_from_range(self.highest, self.lowest, c);
123 self.trailing_idx += 1;
124 out
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use proptest::prelude::*;
132
133 fn ordered_hlc(
134 h: &[f64],
135 l: &[f64],
136 c: &[f64],
137 ) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
138 let len = h.len().min(l.len()).min(c.len());
139 let mut high = Vec::with_capacity(len);
140 let mut low = Vec::with_capacity(len);
141 let mut close = Vec::with_capacity(len);
142 for i in 0..len {
143 let vh = h[i];
144 let vl = l[i];
145 let vc = c[i];
146 high.push(vh.max(vl).max(vc));
147 low.push(vh.min(vl).min(vc));
148 close.push(vc);
149 }
150 (high, low, close)
151 }
152
153 proptest! {
154 #[test]
155 fn test_willr_parity(
156 h in prop::collection::vec(1.0..100.0, 10..100),
157 l in prop::collection::vec(1.0..100.0, 10..100),
158 c in prop::collection::vec(1.0..100.0, 10..100),
159 ) {
160 let (high, low, close) = ordered_hlc(&h, &l, &c);
161 let len = high.len();
162 if len == 0 { return Ok(()); }
163 let period = 14;
164 let mut willr = WILLR::new(period);
165 let streaming: Vec<f64> =
166 (0..len).map(|i| willr.next((high[i], low[i], close[i]))).collect();
167 let batch = talib_rs::momentum::willr(&high, &low, &close, period)
168 .unwrap_or_else(|_| vec![f64::NAN; len]);
169 for (s, b) in streaming.iter().zip(batch.iter()) {
170 if s.is_nan() {
171 assert!(b.is_nan());
172 } else if !b.is_nan() {
173 approx::assert_relative_eq!(s, b, epsilon = 1e-6);
174 }
175 }
176 }
177 }
178}