use crate::indicators::Indicator;
use crate::types::Candle;
use crate::utils::ringbuf::RingBuf;
#[derive(Debug, Clone)]
pub struct WilliamsR {
period: usize,
highs: RingBuf<f64>,
lows: RingBuf<f64>,
}
impl WilliamsR {
pub fn new(period: usize) -> Self {
assert!(period > 0, "period must be > 0");
Self {
period,
highs: RingBuf::new(period, 0.0),
lows: RingBuf::new(period, 0.0),
}
}
#[inline]
fn update(&mut self, high: f64, low: f64, close: f64) -> Option<f64> {
self.highs.push(high);
self.lows.push(low);
if self.highs.len() < self.period {
return None;
}
let highest_high = self.highs.iter().copied().fold(f64::NEG_INFINITY, f64::max);
let lowest_low = self.lows.iter().copied().fold(f64::INFINITY, f64::min);
let range = highest_high - lowest_low;
if range.abs() < 1e-10 {
Some(0.0)
} else {
Some(((highest_high - close) / range) * -100.0)
}
}
}
impl Indicator for WilliamsR {
type Output = f64;
fn next(&mut self, candle: &Candle) -> Option<Self::Output> {
self.update(candle.high, candle.low, candle.close)
}
fn reset(&mut self) {
self.highs = RingBuf::new(self.period, 0.0);
self.lows = RingBuf::new(self.period, 0.0);
}
fn warmup_period(&self) -> usize {
self.period
}
fn clone_boxed(&self) -> Box<dyn Indicator<Output = Self::Output>> {
Box::new(self.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn computes_williams_r_after_warmup() {
let mut wr = WilliamsR::new(3);
let candles = vec![
Candle {
timestamp: 0,
open: 100.0,
high: 102.0,
low: 99.0,
close: 100.0,
volume: 0.0,
},
Candle {
timestamp: 1,
open: 101.0,
high: 103.0,
low: 100.0,
close: 101.0,
volume: 0.0,
},
Candle {
timestamp: 2,
open: 102.0,
high: 104.0,
low: 101.0,
close: 102.0,
volume: 0.0,
},
Candle {
timestamp: 3,
open: 103.0,
high: 105.0,
low: 102.0,
close: 103.0,
volume: 0.0,
},
];
let mut outputs = Vec::new();
for c in &candles {
outputs.push(wr.next(c));
}
assert_eq!(outputs[0], None);
assert_eq!(outputs[1], None);
assert!(outputs[2].is_some());
assert!(outputs[3].is_some());
assert!((outputs[2].unwrap() - (-40.0)).abs() < 0.01);
}
#[test]
fn williams_r_reset_clears_state() {
let mut wr = WilliamsR::new(3);
let candle = Candle {
timestamp: 0,
open: 100.0,
high: 102.0,
low: 99.0,
close: 100.0,
volume: 0.0,
};
wr.next(&candle);
wr.next(&candle);
wr.next(&candle);
assert!(wr.next(&candle).is_some());
wr.reset();
assert_eq!(wr.next(&candle), None);
}
#[test]
fn williams_r_at_high() {
let mut wr = WilliamsR::new(2);
let candles = vec![
Candle {
timestamp: 0,
open: 100.0,
high: 100.0,
low: 100.0,
close: 100.0,
volume: 0.0,
},
Candle {
timestamp: 1,
open: 105.0,
high: 105.0,
low: 100.0,
close: 105.0,
volume: 0.0,
},
];
let outputs: Vec<_> = candles.iter().map(|c| wr.next(c)).collect();
assert_eq!(outputs[0], None);
assert_eq!(outputs[1], Some(0.0));
}
#[test]
fn williams_r_warmup_period() {
let wr = WilliamsR::new(5);
assert_eq!(wr.warmup_period(), 5);
}
}