use polars::prelude::*;
pub fn calculate_williams_r(df: &DataFrame, window: usize) -> PolarsResult<Series> {
if !df.schema().contains("high")
|| !df.schema().contains("low")
|| !df.schema().contains("close")
{
return Err(PolarsError::ShapeMismatch(
"Missing required columns for Williams %R calculation. Required: high, low, close"
.to_string()
.into(),
));
}
let high = df.column("high")?.f64()?;
let low = df.column("low")?.f64()?;
let close = df.column("close")?.f64()?;
let mut williams_r_values = Vec::with_capacity(df.height());
for _ in 0..window - 1 {
williams_r_values.push(f64::NAN);
}
for i in window - 1..df.height() {
let mut highest_high = f64::NEG_INFINITY;
let mut lowest_low = f64::INFINITY;
let mut valid_data = true;
for j in i - (window - 1)..=i {
let h = high.get(j).unwrap_or(f64::NAN);
let l = low.get(j).unwrap_or(f64::NAN);
if h.is_nan() || l.is_nan() {
valid_data = false;
break;
}
highest_high = highest_high.max(h);
lowest_low = lowest_low.min(l);
}
if !valid_data || (highest_high - lowest_low).abs() < 1e-10 {
williams_r_values.push(f64::NAN);
} else {
let c = close.get(i).unwrap_or(f64::NAN);
if c.is_nan() {
williams_r_values.push(f64::NAN);
} else {
let williams_r = ((highest_high - c) / (highest_high - lowest_low)) * -100.0;
williams_r_values.push(williams_r);
}
}
}
let name = format!("williams_r_{}", window);
Ok(Series::new(name.into(), williams_r_values))
}