use polars::prelude::*;
pub fn calculate_psar(df: &DataFrame, af_step: f64, af_max: f64) -> PolarsResult<Series> {
if !df.schema().contains("high") || !df.schema().contains("low") {
return Err(PolarsError::ShapeMismatch(
"Missing required columns for PSAR calculation. Required: high, low"
.to_string()
.into(),
));
}
let high = df.column("high")?.f64()?;
let low = df.column("low")?.f64()?;
let height = df.height();
if height < 2 {
return Err(PolarsError::ShapeMismatch(
"Not enough data points for PSAR calculation. Need at least 2."
.to_string()
.into(),
));
}
let mut psar_values = Vec::with_capacity(height);
psar_values.push(f64::NAN);
let mut is_uptrend = true; let mut current_psar = low.get(0).unwrap_or(0.0); let mut extreme_point = high.get(0).unwrap_or(0.0); let mut acceleration_factor = af_step;
for i in 1..height {
let high_val = high.get(i).unwrap_or(f64::NAN);
let low_val = low.get(i).unwrap_or(f64::NAN);
let prev_high = high.get(i - 1).unwrap_or(f64::NAN);
let prev_low = low.get(i - 1).unwrap_or(f64::NAN);
if high_val.is_nan() || low_val.is_nan() || prev_high.is_nan() || prev_low.is_nan() {
psar_values.push(f64::NAN);
continue;
}
if is_uptrend {
current_psar = current_psar + acceleration_factor * (extreme_point - current_psar);
current_psar = current_psar.min(prev_low).min(low_val);
if current_psar > low_val {
is_uptrend = false;
current_psar = extreme_point;
extreme_point = low_val;
acceleration_factor = af_step;
} else {
if high_val > extreme_point {
extreme_point = high_val;
acceleration_factor = (acceleration_factor + af_step).min(af_max);
}
}
} else {
current_psar = current_psar - acceleration_factor * (current_psar - extreme_point);
current_psar = current_psar.max(prev_high).max(high_val);
if current_psar < high_val {
is_uptrend = true;
current_psar = extreme_point;
extreme_point = high_val;
acceleration_factor = af_step;
} else {
if low_val < extreme_point {
extreme_point = low_val;
acceleration_factor = (acceleration_factor + af_step).min(af_max);
}
}
}
psar_values.push(current_psar);
}
let name = format!("psar_{:.2}_{:.2}", af_step, af_max).replace(".", "_");
Ok(Series::new(name.into(), psar_values))
}