Skip to main content

egui_charts/studies/builtin/
williams_r.rs

1//! Williams %R Indicator
2//!
3//! Williams %R is a momentum oscillator that measures overbought/oversold levels.
4//! It's the inverse of the Fast Stochastic Oscillator, ranging from -100 to 0.
5//!
6//! # Interpretation
7//! - Readings from -20 to 0: Overbought
8//! - Readings from -100 to -80: Oversold
9//!
10//! # Calculation
11//! %R = (Highest High - Close) / (Highest High - Lowest Low) * -100
12//!
13//! # Example
14//! ```
15//! # let bars: Vec<egui_charts::model::Bar> = (0..60).map(|i| { let p = 100.0 + (i as f64 * 0.3).sin() * 5.0; egui_charts::model::Bar::new(chrono::Utc::now(), p, p + 1.0, p - 1.0, p + 0.5, 1_000.0) }).collect();
16//! use egui_charts::studies::{WilliamsR, Indicator};
17//!
18//! let mut williams = WilliamsR::new(14);
19//! williams.calculate(&bars);
20//! ```
21
22use super::over_finite_bars;
23use crate::model::Bar;
24use crate::studies::palette;
25use crate::studies::stepper::{rolling_max, rolling_min};
26use crate::studies::stepper::{update_window, usable_behind};
27use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
28use egui::Color32;
29
30/// Williams %R indicator
31#[derive(Clone)]
32pub struct WilliamsR {
33    period: usize,
34    values: IndicatorValues,
35    color: Color32,
36    visible: bool,
37}
38
39impl WilliamsR {
40    /// Create a new Williams %R indicator
41    ///
42    /// # Arguments
43    /// * `period` - Lookback period (typically 14)
44    pub fn new(period: usize) -> Self {
45        Self {
46            period: period.max(1),
47            values: IndicatorValues::new(),
48            color: palette::PURPLE,
49            visible: true,
50        }
51    }
52
53    /// Set the indicator color
54    pub fn with_color(mut self, color: Color32) -> Self {
55        self.color = color;
56        self
57    }
58}
59
60/// Construct with the conventional default parameters.
61impl Default for WilliamsR {
62    fn default() -> Self {
63        Self::new(14)
64    }
65}
66
67impl WilliamsR {
68    /// The values over `data`, every bar of which has finite inputs.
69    fn compute(&self, data: &[Bar]) -> IndicatorValues {
70        let mut values = IndicatorValues::new();
71        if data.len() < self.period {
72            for _ in 0..data.len() {
73                values.push(IndicatorValue::None);
74            }
75            return values;
76        }
77
78        let highest = rolling_max(data.iter().map(|b| b.high), self.period);
79        let lowest = rolling_min(data.iter().map(|b| b.low), self.period);
80        for i in 0..data.len() {
81            if i < self.period - 1 {
82                values.push(IndicatorValue::None);
83            } else {
84                let (highest, lowest) = (highest[i], lowest[i]);
85                let range = highest - lowest;
86
87                if range == 0.0 {
88                    values.push(IndicatorValue::Single(-50.0)); // Middle value
89                } else {
90                    let r = (highest - data[i].close) / range * -100.0;
91                    values.push(IndicatorValue::Single(r.clamp(-100.0, 0.0)));
92                }
93            }
94        }
95
96        values
97    }
98}
99
100impl Indicator for WilliamsR {
101    crate::studies::inputs::study_inputs!(self;
102        period: length("Period", 1, usize::MAX)
103    ; {});
104
105    fn value_format(&self) -> crate::scales::StudyFormat {
106        crate::scales::StudyFormat::Percent
107    }
108
109    fn name(&self) -> &str {
110        "Williams %R"
111    }
112
113    fn desc(&self) -> &str {
114        "Williams %R - Momentum oscillator (-100 to 0)"
115    }
116
117    fn calculate(&mut self, data: &[Bar]) {
118        self.update(data, 0);
119    }
120
121    fn update(&mut self, data: &[Bar], from: usize) {
122        let mut values = std::mem::take(&mut self.values);
123        update_window(
124            &mut values,
125            data,
126            from,
127            usable_behind(self.period - 1, |b| {
128                b.high.is_finite() && b.low.is_finite() && b.close.is_finite()
129            }),
130            |data| {
131                over_finite_bars(
132                    data,
133                    |b| b.high.is_finite() && b.low.is_finite() && b.close.is_finite(),
134                    |data| self.compute(data),
135                )
136            },
137        );
138        self.values = values;
139    }
140
141    fn is_incremental(&self) -> bool {
142        true
143    }
144
145    fn values(&self) -> &IndicatorValues {
146        &self.values
147    }
148
149    fn colors(&self) -> Vec<Color32> {
150        vec![self.color]
151    }
152
153    fn set_colors(&mut self, colors: Vec<Color32>) {
154        if !colors.is_empty() {
155            self.color = colors[0];
156        }
157    }
158
159    fn is_overlay(&self) -> bool {
160        false // Williams %R is drawn in separate pane
161    }
162
163    fn is_visible(&self) -> bool {
164        self.visible
165    }
166
167    fn set_visible(&mut self, visible: bool) {
168        self.visible = visible;
169    }
170
171    fn clone_box(&self) -> Box<dyn Indicator> {
172        Box::new(self.clone())
173    }
174
175    fn line_names(&self) -> Vec<String> {
176        vec![format!("%R({})", self.period)]
177    }
178}