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}