use polars::prelude::*;
use crate::indicators::moving_averages::{calculate_sma, calculate_ema, calculate_bollinger_bands};
use crate::indicators::oscillators::calculate_rsi;
pub fn calculate_relative_strength_mean_reversion(
df: &DataFrame,
lookback_period: Option<usize>,
std_dev_period: Option<usize>,
oversold_threshold: Option<f64>,
overbought_threshold: Option<f64>,
) -> PolarsResult<Series> {
let lookback = lookback_period.unwrap_or(50);
let std_dev_len = std_dev_period.unwrap_or(100);
let oversold = oversold_threshold.unwrap_or(-2.0);
let overbought = overbought_threshold.unwrap_or(2.0);
let mean = calculate_sma(df, "close", lookback)?;
let mean_vals = mean.f64()?;
let close = df.column("close")?.f64()?;
let mut deviation = Vec::with_capacity(df.height());
for i in 0..df.height() {
let c = close.get(i).unwrap_or(f64::NAN);
let m = mean_vals.get(i).unwrap_or(f64::NAN);
if c.is_nan() || m.is_nan() || m == 0.0 {
deviation.push(f64::NAN);
} else {
deviation.push((c - m) / m * 100.0); }
}
let mut rsmr = Vec::with_capacity(df.height());
for i in 0..std_dev_len.min(df.height()) {
rsmr.push(f64::NAN);
}
for i in std_dev_len..df.height() {
let mut sum = 0.0;
let mut sum_sq = 0.0;
let mut count = 0;
for j in (i - std_dev_len + 1)..=i {
let dev = deviation[j];
if !dev.is_nan() {
sum += dev;
sum_sq += dev * dev;
count += 1;
}
}
if count > 0 {
let avg = sum / count as f64;
let variance = (sum_sq / count as f64) - (avg * avg);
let std_dev = variance.sqrt();
let z_score = if std_dev > 0.0 {
(deviation[i] - avg) / std_dev
} else {
0.0 };
rsmr.push(z_score);
} else {
rsmr.push(f64::NAN);
}
}
Ok(Series::new("rsmr", rsmr))
}
pub fn calculate_mean_reversion_signals(
df: &DataFrame,
oversold_threshold: Option<f64>,
overbought_threshold: Option<f64>,
) -> PolarsResult<Series> {
let oversold = oversold_threshold.unwrap_or(-2.0);
let overbought = overbought_threshold.unwrap_or(2.0);
if !df.schema().contains("rsmr") {
return Err(PolarsError::ComputeError(
"RSMR column not found. Calculate RSMR first.".into(),
));
}
let rsmr = df.column("rsmr")?.f64()?;
let mut signals = Vec::with_capacity(df.height());
let rsi = calculate_rsi(df, 14, "close")?;
let rsi_vals = rsi.f64()?;
for i in 0..df.height() {
let r = rsmr.get(i).unwrap_or(f64::NAN);
let rsi_val = rsi_vals.get(i).unwrap_or(f64::NAN);
if r.is_nan() || rsi_val.is_nan() {
signals.push(0);
} else if r <= oversold && rsi_val < 30.0 {
signals.push(1);
} else if r >= overbought && rsi_val > 70.0 {
signals.push(-1);
} else {
signals.push(0);
}
}
Ok(Series::new("mean_reversion_signal", signals))
}
pub fn calculate_bollinger_band_reversion(
df: &DataFrame,
band_period: Option<usize>,
band_std_dev: Option<f64>,
rsi_period: Option<usize>,
) -> PolarsResult<Series> {
let period = band_period.unwrap_or(20);
let std_dev = band_std_dev.unwrap_or(2.0);
let rsi_len = rsi_period.unwrap_or(14);
let (bb_middle, bb_upper, bb_lower) = calculate_bollinger_bands(df, period, std_dev, "close")?;
let middle_vals = bb_middle.f64()?;
let upper_vals = bb_upper.f64()?;
let lower_vals = bb_lower.f64()?;
let rsi = calculate_rsi(df, rsi_len, "close")?;
let rsi_vals = rsi.f64()?;
let close = df.column("close")?.f64()?;
let mut bb_reversion = Vec::with_capacity(df.height());
let min_periods = period.max(rsi_len);
for i in 0..min_periods.min(df.height()) {
bb_reversion.push(0);
}
for i in min_periods..df.height() {
let c = close.get(i).unwrap_or(f64::NAN);
let upper = upper_vals.get(i).unwrap_or(f64::NAN);
let lower = lower_vals.get(i).unwrap_or(f64::NAN);
let rsi_val = rsi_vals.get(i).unwrap_or(f64::NAN);
if c.is_nan() || upper.is_nan() || lower.is_nan() || rsi_val.is_nan() {
bb_reversion.push(0);
continue;
}
if c >= upper && rsi_val >= 70.0 {
bb_reversion.push(-1);
} else if c <= lower && rsi_val <= 30.0 {
bb_reversion.push(1);
} else {
bb_reversion.push(0);
}
}
Ok(Series::new("bollinger_band_reversion", bb_reversion))
}
pub fn calculate_mean_reversion_probability(
df: &DataFrame,
lookback_period: Option<usize>,
) -> PolarsResult<Series> {
let lookback = lookback_period.unwrap_or(60);
if !df.schema().contains("rsmr") {
return Err(PolarsError::ComputeError(
"RSMR column not found. Calculate RSMR first.".into(),
));
}
let rsmr = df.column("rsmr")?.f64()?;
let close = df.column("close")?.f64()?;
let has_volume = df.schema().contains("volume");
let volume = if has_volume {
Some(df.column("volume")?.f64()?)
} else {
None
};
let mut probability = Vec::with_capacity(df.height());
for i in 0..lookback.min(df.height()) {
probability.push(0.0);
}
for i in lookback..df.height() {
let current_rsmr = rsmr.get(i).unwrap_or(f64::NAN);
if current_rsmr.is_nan() {
probability.push(0.0);
continue;
}
let mut prob = 0.5;
let abs_rsmr = current_rsmr.abs();
if abs_rsmr >= 3.0 {
prob += 0.25; } else if abs_rsmr >= 2.0 {
prob += 0.15; } else if abs_rsmr >= 1.0 {
prob += 0.05; }
let mut success_count = 0;
let mut total_similar = 0;
for j in (i - lookback)..i {
let historical_rsmr = rsmr.get(j).unwrap_or(f64::NAN);
if j + 5 >= df.height() || historical_rsmr.is_nan() {
continue;
}
if (historical_rsmr * current_rsmr > 0.0) && (historical_rsmr.abs() > current_rsmr.abs() * 0.75) && (historical_rsmr.abs() < current_rsmr.abs() * 1.25) {
total_similar += 1;
let start_close = close.get(j).unwrap_or(f64::NAN);
let future_close = close.get(j + 5).unwrap_or(f64::NAN);
if start_close.is_nan() || future_close.is_nan() {
continue;
}
if (historical_rsmr < 0.0 && future_close > start_close) ||
(historical_rsmr > 0.0 && future_close < start_close) {
success_count += 1;
}
}
}
if total_similar > 0 {
let historical_probability = success_count as f64 / total_similar as f64;
prob = (prob + historical_probability) / 2.0; }
if let Some(vol) = &volume {
let mut vol_sum = 0.0;
let mut vol_count = 0;
for j in (i - 10.min(i))..i {
let v = vol.get(j).unwrap_or(f64::NAN);
if !v.is_nan() {
vol_sum += v;
vol_count += 1;
}
}
if vol_count > 0 {
let avg_vol = vol_sum / vol_count as f64;
let current_vol = vol.get(i).unwrap_or(f64::NAN);
if !current_vol.is_nan() && current_vol > avg_vol * 1.5 {
prob += 0.1;
}
}
}
probability.push(prob.min(0.95));
}
Ok(Series::new("mean_reversion_probability", probability))
}
pub fn add_mean_reversion_analysis(df: &mut DataFrame) -> PolarsResult<()> {
let rsmr = calculate_relative_strength_mean_reversion(df, None, None, None, None)?;
df.with_column(rsmr)?;
let signals = calculate_mean_reversion_signals(df, None, None)?;
df.with_column(signals)?;
let bb_reversion = calculate_bollinger_band_reversion(df, None, None, None)?;
df.with_column(bb_reversion)?;
let probability = calculate_mean_reversion_probability(df, None)?;
df.with_column(probability)?;
Ok(())
}