finance_solution/stocks/ta/
willr.rs1use crate::stocks::ta::common::{opt_cell, require_hlc};
65use crate::stocks::ta::ring::{SlidingMax, SlidingMin};
66use crate::util::error::{require_finite, FinanceError, FinanceResult};
67use crate::util::primitives::PeriodLength;
68use crate::{columns_with_strings, print_table_locale_opt};
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
72pub struct WillrParams {
73 pub period: usize,
74}
75
76impl WillrParams {
77 pub const fn new(period: usize) -> Self {
78 Self { period }
79 }
80
81 pub const fn period_14() -> Self {
83 Self { period: 14 }
84 }
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
89pub struct ValidatedWillr {
90 params: WillrParams,
91}
92
93impl ValidatedWillr {
94 pub fn new(params: WillrParams) -> FinanceResult<Self> {
95 PeriodLength::new(params.period)?;
96 Ok(Self { params })
97 }
98
99 pub fn params(self) -> WillrParams {
100 self.params
101 }
102
103 pub fn compute(self, high: &[f64], low: &[f64], close: &[f64]) -> FinanceResult<WillrSeries> {
104 willr_validated(high, low, close, self)
105 }
106}
107
108#[derive(Clone, Debug, PartialEq)]
109pub struct WillrSeries {
110 pub willr: Vec<Option<f64>>,
111 pub params: WillrParams,
112}
113
114impl WillrSeries {
115 pub fn last(&self) -> Option<f64> {
116 self.willr.iter().rev().find_map(|x| *x)
117 }
118}
119
120#[derive(Clone, Debug)]
124pub struct WillrState {
125 params: WillrParams,
126 high_max: SlidingMax,
127 low_min: SlidingMin,
128 prev: Option<f64>,
129 last: Option<f64>,
130}
131
132impl WillrState {
133 pub fn new(params: WillrParams) -> FinanceResult<Self> {
134 let _ = ValidatedWillr::new(params)?;
135 Ok(Self {
136 params,
137 high_max: SlidingMax::with_window(params.period),
138 low_min: SlidingMin::with_window(params.period),
139 prev: None,
140 last: None,
141 })
142 }
143
144 pub fn from_history(
145 params: WillrParams,
146 high: &[f64],
147 low: &[f64],
148 close: &[f64],
149 ) -> FinanceResult<Self> {
150 let mut s = Self::new(params)?;
151 let _ = s.push_bars(high, low, close)?;
152 Ok(s)
153 }
154
155 pub fn params(&self) -> WillrParams {
156 self.params
157 }
158
159 pub fn reset(&mut self) {
160 self.high_max.clear();
161 self.low_min.clear();
162 self.prev = None;
163 self.last = None;
164 }
165
166 pub fn push(&mut self, high: f64, low: f64, close: f64) -> FinanceResult<Option<f64>> {
167 require_finite("high", high)?;
168 require_finite("low", low)?;
169 require_finite("close", close)?;
170 if high < low {
171 return Err(FinanceError::InvalidCashflow {
172 message: "high must be >= low for each bar",
173 });
174 }
175 let hh = self.high_max.push(high).unwrap();
176 let ll = self.low_min.push(low).unwrap();
177 if !self.high_max.is_full() {
178 self.last = None;
179 return Ok(None);
180 }
181 let range = hh - ll;
182 let raw = if range == 0.0 {
183 self.prev.unwrap_or(-50.0)
184 } else {
185 -100.0 * (hh - close) / range
186 };
187 self.prev = Some(raw);
188 self.last = Some(raw);
189 Ok(Some(raw))
190 }
191
192 pub fn push_bars(
193 &mut self,
194 high: &[f64],
195 low: &[f64],
196 close: &[f64],
197 ) -> FinanceResult<Vec<Option<f64>>> {
198 require_hlc(high, low, close)?;
199 let mut out = Vec::with_capacity(close.len());
200 for i in 0..close.len() {
201 out.push(self.push(high[i], low[i], close[i])?);
202 }
203 Ok(out)
204 }
205
206 pub fn last(&self) -> Option<f64> {
207 self.last
208 }
209}
210
211pub fn willr(
212 high: &[f64],
213 low: &[f64],
214 close: &[f64],
215 params: WillrParams,
216) -> FinanceResult<WillrSeries> {
217 ValidatedWillr::new(params)?.compute(high, low, close)
218}
219
220fn willr_validated(
221 high: &[f64],
222 low: &[f64],
223 close: &[f64],
224 eng: ValidatedWillr,
225) -> FinanceResult<WillrSeries> {
226 let mut st = WillrState::new(eng.params)?;
227 let willr = st.push_bars(high, low, close)?;
228 Ok(WillrSeries {
229 willr,
230 params: eng.params,
231 })
232}
233
234#[derive(Clone, Debug)]
235pub struct WillrSolution {
236 series: WillrSeries,
237 close: Vec<f64>,
238 formula: String,
239}
240
241impl WillrSolution {
242 pub fn series(&self) -> &WillrSeries {
243 &self.series
244 }
245 pub fn formula(&self) -> &str {
246 &self.formula
247 }
248
249 pub fn print_table(&self) {
250 self.print_table_locale_opt(None, None);
251 }
252
253 pub fn print_table_locale(&self, locale: &num_format::Locale, precision: usize) {
254 self.print_table_locale_opt(Some(locale), Some(precision));
255 }
256
257 fn print_table_locale_opt(
258 &self,
259 locale: Option<&num_format::Locale>,
260 precision: Option<usize>,
261 ) {
262 let columns = columns_with_strings(&[
263 ("period", "i", true),
264 ("close", "f", true),
265 ("willr", "f", true),
266 ]);
267 let data = self
268 .close
269 .iter()
270 .enumerate()
271 .map(|(i, c)| vec![i.to_string(), c.to_string(), opt_cell(self.series.willr[i])])
272 .collect();
273 print_table_locale_opt(&columns, data, locale, precision);
274 }
275}
276
277pub fn willr_solution(
287 high: &[f64],
288 low: &[f64],
289 close: &[f64],
290 params: WillrParams,
291) -> FinanceResult<WillrSolution> {
292 let series = willr(high, low, close, params)?;
293 Ok(WillrSolution {
294 series,
295 close: close.to_vec(),
296 formula: format!("%R = -100 * (HH - C) / (HH - LL), period={}", params.period),
297 })
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn flat_mid_is_neg_50() {
306 let h = vec![12.0; 5];
307 let l = vec![10.0; 5];
308 let c = vec![11.0; 5];
309 let s = willr(&h, &l, &c, WillrParams::new(3)).unwrap();
310 assert!((s.willr[2].unwrap() - (-50.0)).abs() < 1e-12);
311 }
312
313 #[test]
314 fn at_high_is_zero() {
315 let h = [10.0, 11.0, 12.0];
316 let l = [8.0, 9.0, 10.0];
317 let c = [10.0, 11.0, 12.0]; let s = willr(&h, &l, &c, WillrParams::new(3)).unwrap();
319 assert!((s.willr[2].unwrap() - 0.0).abs() < 1e-12);
320 }
321
322 #[test]
323 fn at_low_is_neg_100() {
324 let h = [10.0, 11.0, 12.0];
325 let l = [8.0, 9.0, 10.0];
326 let c = [9.0, 9.5, 8.0];
328 let s = willr(&h, &l, &c, WillrParams::new(3)).unwrap();
329 assert!((s.willr[2].unwrap() - (-100.0)).abs() < 1e-12);
330 }
331
332 #[test]
333 fn state_parity() {
334 let h: Vec<_> = (0..30).map(|i| 101.0 + (i as f64) * 0.1).collect();
335 let l: Vec<_> = (0..30).map(|i| 99.0 + (i as f64) * 0.1).collect();
336 let c: Vec<_> = (0..30).map(|i| 100.0 + (i as f64) * 0.1).collect();
337 let p = WillrParams::period_14();
338 let batch = willr(&h, &l, &c, p).unwrap();
339 let mut st = WillrState::new(p).unwrap();
340 for i in 0..c.len() {
341 let o = st.push(h[i], l[i], c[i]).unwrap();
342 match (o, batch.willr[i]) {
343 (None, None) => {}
344 (Some(a), Some(b)) => assert!((a - b).abs() < 1e-12),
345 other => panic!("{other:?}"),
346 }
347 }
348 }
349
350 #[test]
351 fn high_lt_low_err() {
352 assert!(willr(&[1.0], &[2.0], &[1.5], WillrParams::new(1)).is_err());
353 }
354}