ta_lib_in_rust/indicators/oscillators/
williams_r.rs

1use polars::prelude::*;
2
3/// Calculates the Williams %R oscillator
4///
5/// Williams %R is a momentum indicator that moves between 0 and -100 and
6/// measures overbought/oversold levels. It's particularly useful for intraday trading
7/// to identify potential reversals.
8///
9/// # Arguments
10///
11/// * `df` - DataFrame containing OHLCV data with "high", "low", and "close" columns
12/// * `window` - Lookback period for the calculation (typically 14)
13///
14/// # Returns
15///
16/// * `PolarsResult<Series>` - Series containing Williams %R values named "williams_r_{window}"
17///
18/// # Formula
19///
20/// Williams %R = ((Highest High - Close) / (Highest High - Lowest Low)) * -100
21///
22/// # Example
23///
24/// ```
25/// use polars::prelude::*;
26/// use ta_lib_in_rust::indicators::oscillators::calculate_williams_r;
27///
28/// // Create or load a DataFrame with OHLCV data
29/// let df = DataFrame::default(); // Replace with actual data
30///
31/// // Calculate Williams %R with period 14
32/// let williams_r = calculate_williams_r(&df, 14).unwrap();
33/// ```
34pub fn calculate_williams_r(df: &DataFrame, window: usize) -> PolarsResult<Series> {
35    // Validate required columns
36    if !df.schema().contains("high")
37        || !df.schema().contains("low")
38        || !df.schema().contains("close")
39    {
40        return Err(PolarsError::ShapeMismatch(
41            "Missing required columns for Williams %R calculation. Required: high, low, close"
42                .to_string()
43                .into(),
44        ));
45    }
46
47    // Extract required columns
48    let high = df.column("high")?.f64()?;
49    let low = df.column("low")?.f64()?;
50    let close = df.column("close")?.f64()?;
51
52    // Calculate Williams %R
53    let mut williams_r_values = Vec::with_capacity(df.height());
54
55    // Fill initial values with NaN
56    for _ in 0..window - 1 {
57        williams_r_values.push(f64::NAN);
58    }
59
60    // Calculate Williams %R for the remaining data points
61    for i in window - 1..df.height() {
62        let mut highest_high = f64::NEG_INFINITY;
63        let mut lowest_low = f64::INFINITY;
64        let mut valid_data = true;
65
66        // Find highest high and lowest low in the window
67        for j in i - (window - 1)..=i {
68            let h = high.get(j).unwrap_or(f64::NAN);
69            let l = low.get(j).unwrap_or(f64::NAN);
70
71            if h.is_nan() || l.is_nan() {
72                valid_data = false;
73                break;
74            }
75
76            highest_high = highest_high.max(h);
77            lowest_low = lowest_low.min(l);
78        }
79
80        if !valid_data || (highest_high - lowest_low).abs() < 1e-10 {
81            williams_r_values.push(f64::NAN);
82        } else {
83            let c = close.get(i).unwrap_or(f64::NAN);
84            if c.is_nan() {
85                williams_r_values.push(f64::NAN);
86            } else {
87                let williams_r = ((highest_high - c) / (highest_high - lowest_low)) * -100.0;
88                williams_r_values.push(williams_r);
89            }
90        }
91    }
92
93    // Create Series with Williams %R values
94    let name = format!("williams_r_{}", window);
95    Ok(Series::new(name.into(), williams_r_values))
96}