use polars::prelude::*;
pub fn calculate_stochastic(
df: &DataFrame,
k_period: usize,
d_period: usize,
slowing: usize,
) -> PolarsResult<(Series, Series)> {
if !df.schema().contains("high")
|| !df.schema().contains("low")
|| !df.schema().contains("close")
{
return Err(PolarsError::ShapeMismatch(
"Missing required columns for Stochastic 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 raw_k_values = Vec::with_capacity(df.height());
for _ in 0..k_period - 1 {
raw_k_values.push(f64::NAN);
}
for i in k_period - 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 - (k_period - 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 {
raw_k_values.push(f64::NAN);
} else {
let c = close.get(i).unwrap_or(f64::NAN);
if c.is_nan() {
raw_k_values.push(f64::NAN);
} else {
let raw_k = 100.0 * (c - lowest_low) / (highest_high - lowest_low);
raw_k_values.push(raw_k);
}
}
}
let mut k_values = Vec::with_capacity(df.height());
let k_offset = k_period + slowing - 1;
for _ in 0..k_offset {
k_values.push(f64::NAN);
}
for i in k_offset..df.height() {
let mut sum = 0.0;
let mut count = 0;
let mut has_nan = false;
for j in 0..slowing {
let val = raw_k_values[i - j];
if val.is_nan() {
has_nan = true;
break;
}
sum += val;
count += 1;
}
if has_nan || count == 0 {
k_values.push(f64::NAN);
} else {
k_values.push(sum / count as f64);
}
}
let mut d_values = Vec::with_capacity(df.height());
let d_offset = k_offset + d_period - 1;
for _ in 0..d_offset {
d_values.push(f64::NAN);
}
for i in d_offset..df.height() {
let mut sum = 0.0;
let mut count = 0;
let mut has_nan = false;
for j in 0..d_period {
let val = k_values[i - j];
if val.is_nan() {
has_nan = true;
break;
}
sum += val;
count += 1;
}
if has_nan || count == 0 {
d_values.push(f64::NAN);
} else {
d_values.push(sum / count as f64);
}
}
let k_name = format!("stoch_k_{}_{}_{}", k_period, slowing, d_period);
let d_name = format!("stoch_d_{}_{}_{}", k_period, slowing, d_period);
Ok((
Series::new(k_name.into(), k_values),
Series::new(d_name.into(), d_values),
))
}