wickra_core/indicators/
williams_r.rs1use std::collections::VecDeque;
4
5use crate::error::{Error, Result};
6use crate::ohlcv::Candle;
7use crate::traits::Indicator;
8
9#[derive(Debug, Clone)]
30pub struct WilliamsR {
31 period: usize,
32 candles: VecDeque<Candle>,
33}
34
35impl WilliamsR {
36 pub fn new(period: usize) -> Result<Self> {
39 if period == 0 {
40 return Err(Error::PeriodZero);
41 }
42 Ok(Self {
43 period,
44 candles: VecDeque::with_capacity(period),
45 })
46 }
47
48 pub const fn period(&self) -> usize {
50 self.period
51 }
52}
53
54impl Indicator for WilliamsR {
55 type Input = Candle;
56 type Output = f64;
57
58 fn update(&mut self, candle: Candle) -> Option<f64> {
59 if self.candles.len() == self.period {
60 self.candles.pop_front();
61 }
62 self.candles.push_back(candle);
63 if self.candles.len() < self.period {
64 return None;
65 }
66 let hh = self
67 .candles
68 .iter()
69 .map(|c| c.high)
70 .fold(f64::NEG_INFINITY, f64::max);
71 let ll = self
72 .candles
73 .iter()
74 .map(|c| c.low)
75 .fold(f64::INFINITY, f64::min);
76 let range = hh - ll;
77 if range == 0.0 {
78 return Some(-50.0);
79 }
80 Some(-100.0 * (hh - candle.close) / range)
81 }
82
83 fn reset(&mut self) {
84 self.candles.clear();
85 }
86
87 fn warmup_period(&self) -> usize {
88 self.period
89 }
90
91 fn is_ready(&self) -> bool {
92 self.candles.len() == self.period
93 }
94
95 fn name(&self) -> &'static str {
96 "WilliamsR"
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use crate::traits::BatchExt;
104 use approx::assert_relative_eq;
105
106 fn c(h: f64, l: f64, cl: f64) -> Candle {
107 Candle::new(cl, h, l, cl, 1.0, 0).unwrap()
108 }
109
110 #[test]
111 fn close_at_high_yields_zero() {
112 let candles = vec![c(10.0, 8.0, 9.0), c(11.0, 9.0, 10.0), c(12.0, 10.0, 12.0)];
113 let mut w = WilliamsR::new(3).unwrap();
114 let out = w.batch(&candles);
115 assert_relative_eq!(out[2].unwrap(), 0.0, epsilon = 1e-12);
116 }
117
118 #[test]
119 fn close_at_low_yields_minus_100() {
120 let candles = vec![c(12.0, 10.0, 11.0), c(11.0, 9.0, 10.0), c(10.0, 8.0, 8.0)];
121 let mut w = WilliamsR::new(3).unwrap();
122 let out = w.batch(&candles);
123 assert_relative_eq!(out[2].unwrap(), -100.0, epsilon = 1e-12);
124 }
125
126 #[test]
127 fn within_range() {
128 let candles: Vec<Candle> = (0..100)
129 .map(|i| {
130 let m = 50.0 + (f64::from(i) * 0.3).sin() * 5.0;
131 c(m + 1.0, m - 1.0, m)
132 })
133 .collect();
134 let mut w = WilliamsR::new(14).unwrap();
135 for v in w.batch(&candles).into_iter().flatten() {
136 assert!((-100.0..=0.0).contains(&v), "%R out of range: {v}");
137 }
138 }
139
140 #[test]
141 fn batch_equals_streaming() {
142 let candles: Vec<Candle> = (0..30)
143 .map(|i| c(f64::from(i) + 2.0, f64::from(i), f64::from(i) + 1.0))
144 .collect();
145 let mut a = WilliamsR::new(5).unwrap();
146 let mut b = WilliamsR::new(5).unwrap();
147 assert_eq!(
148 a.batch(&candles),
149 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
150 );
151 }
152
153 #[test]
154 fn rejects_zero_period() {
155 assert!(WilliamsR::new(0).is_err());
156 }
157
158 #[test]
162 fn accessors_and_metadata() {
163 let w = WilliamsR::new(14).unwrap();
164 assert_eq!(w.period(), 14);
165 assert_eq!(w.warmup_period(), 14);
166 assert_eq!(w.name(), "WilliamsR");
167 }
168
169 #[test]
175 fn zero_range_yields_minus_fifty() {
176 let candles: Vec<Candle> = (0..5).map(|_| c(10.0, 10.0, 10.0)).collect();
177 let mut w = WilliamsR::new(3).unwrap();
178 let last = w
179 .batch(&candles)
180 .into_iter()
181 .flatten()
182 .last()
183 .expect("emits");
184 assert_eq!(last, -50.0);
185 }
186
187 #[test]
188 fn reset_clears_state() {
189 let candles: Vec<Candle> = (0..20)
190 .map(|i| c(f64::from(i) + 2.0, f64::from(i), f64::from(i) + 1.0))
191 .collect();
192 let mut w = WilliamsR::new(5).unwrap();
193 w.batch(&candles);
194 assert!(w.is_ready());
195 w.reset();
196 assert!(!w.is_ready());
197 assert_eq!(w.update(candles[0]), None);
198 }
199}