use polars::prelude::*;
pub fn calculate_rsi(df: &DataFrame, window: usize, column: &str) -> PolarsResult<Series> {
if df.height() < window + 1 {
return Err(PolarsError::ComputeError(
format!(
"Not enough data points for RSI calculation with window size {}",
window
)
.into(),
));
}
let close = df.column(column)?.f64()?.clone().into_series();
let prev_close = close.shift(1);
let price_diff: Vec<f64> = close
.f64()?
.iter()
.zip(prev_close.f64()?.iter())
.map(|(curr, prev)| match (curr, prev) {
(Some(c), Some(p)) => c - p,
_ => f64::NAN,
})
.collect();
let mut gains: Vec<f64> = Vec::with_capacity(df.height());
let mut losses: Vec<f64> = Vec::with_capacity(df.height());
gains.push(0.0);
losses.push(0.0);
for &diff in &price_diff[1..] {
if diff.is_nan() {
gains.push(f64::NAN);
losses.push(f64::NAN);
} else if diff > 0.0 {
gains.push(diff);
losses.push(0.0);
} else {
gains.push(0.0);
losses.push(diff.abs());
}
}
let mut avg_gain = 0.0;
let mut avg_loss = 0.0;
let mut rsi: Vec<f64> = Vec::with_capacity(df.height());
for _i in 0..window {
rsi.push(f64::NAN);
}
for i in 1..=window {
avg_gain += gains[i];
avg_loss += losses[i];
}
avg_gain /= window as f64;
avg_loss /= window as f64;
let rs = if avg_loss == 0.0 {
100.0 } else {
avg_gain / avg_loss
};
let rsi_val = 100.0 - (100.0 / (1.0 + rs));
rsi[window - 1] = rsi_val;
for i in window + 1..df.height() {
avg_gain = ((avg_gain * (window - 1) as f64) + gains[i]) / window as f64;
avg_loss = ((avg_loss * (window - 1) as f64) + losses[i]) / window as f64;
let rs = if avg_loss == 0.0 {
100.0 } else {
avg_gain / avg_loss
};
let rsi_val = 100.0 - (100.0 / (1.0 + rs));
rsi.push(rsi_val);
}
Ok(Series::new(format!("rsi_{}", window).into(), rsi))
}