use polars::prelude::*;
use std::str::FromStr;
pub fn calculate_opening_range(
df: &DataFrame,
time_col: &str,
range_minutes: Option<usize>,
market_open_time: Option<&str>,
) -> PolarsResult<(Series, Series)> {
let minutes = range_minutes.unwrap_or(30);
let open_time = market_open_time.unwrap_or("09:30");
for col in ["high", "low", time_col].iter() {
if !df.schema().contains(*col) {
return Err(PolarsError::ComputeError(
format!("Required column '{}' not found", col).into(),
));
}
}
let high = df.column("high")?.f64()?;
let low = df.column("low")?.f64()?;
let time_data = df.column(time_col)?;
let mut opening_range_high = f64::MIN;
let mut opening_range_low = f64::MAX;
let mut in_opening_range = false;
let mut range_end_idx = 0;
for i in 0..df.height() {
let time_str = match time_data.dtype() {
DataType::Utf8 => time_data.str()?.get(i).unwrap_or("").to_string(),
DataType::Time => format!("{:02}:{:02}",
time_data.time()?.get(i).unwrap_or(0) / 3600000,
(time_data.time()?.get(i).unwrap_or(0) / 60000) % 60),
_ => return Err(PolarsError::ComputeError(
"Time column must be string or time type".into(),
)),
};
if !in_opening_range && time_str >= open_time {
in_opening_range = true;
}
if in_opening_range {
let h = high.get(i).unwrap_or(f64::NAN);
let l = low.get(i).unwrap_or(f64::NAN);
if !h.is_nan() {
opening_range_high = opening_range_high.max(h);
}
if !l.is_nan() {
opening_range_low = opening_range_low.min(l);
}
range_end_idx += 1;
if range_end_idx >= minutes {
break;
}
}
}
let mut or_high = Vec::with_capacity(df.height());
let mut or_low = Vec::with_capacity(df.height());
for _ in 0..df.height() {
or_high.push(opening_range_high);
or_low.push(opening_range_low);
}
Ok((
Series::new("opening_range_high", or_high),
Series::new("opening_range_low", or_low),
))
}
pub fn add_opening_range_analysis(df: &mut DataFrame, time_col: &str) -> PolarsResult<()> {
let (or_high, or_low) = calculate_opening_range(df, time_col, None, None)?;
df.with_column(or_high.clone())?;
df.with_column(or_low.clone())?;
let close = df.column("close")?.f64()?;
let or_high_values = or_high.f64()?;
let or_low_values = or_low.f64()?;
let mut breakout_signals = Vec::with_capacity(df.height());
for i in 0..df.height() {
let c = close.get(i).unwrap_or(f64::NAN);
let h = or_high_values.get(i).unwrap_or(f64::NAN);
let l = or_low_values.get(i).unwrap_or(f64::NAN);
if c.is_nan() || h.is_nan() || l.is_nan() {
breakout_signals.push(0);
} else if c > h {
breakout_signals.push(1);
} else if c < l {
breakout_signals.push(-1);
} else {
breakout_signals.push(0);
}
}
df.with_column(Series::new("opening_range_breakout", breakout_signals))?;
Ok(())
}
pub fn calculate_opening_range_success_rate(
df: &DataFrame,
forward_bars: usize,
) -> PolarsResult<f64> {
if !df.schema().contains("opening_range_breakout") {
return Err(PolarsError::ComputeError(
"opening_range_breakout column not found. Calculate opening range analysis first.".into(),
));
}
let breakout_signals = df.column("opening_range_breakout")?.i32()?;
let close = df.column("close")?.f64()?;
let mut total_signals = 0;
let mut successful_signals = 0;
for i in 0..(df.height().saturating_sub(forward_bars)) {
let signal = breakout_signals.get(i).unwrap_or(0);
if signal == 0 {
continue;
}
let current_close = close.get(i).unwrap_or(f64::NAN);
let future_close = close.get(i + forward_bars).unwrap_or(f64::NAN);
if current_close.is_nan() || future_close.is_nan() {
continue;
}
total_signals += 1;
if (signal > 0 && future_close > current_close) ||
(signal < 0 && future_close < current_close) {
successful_signals += 1;
}
}
if total_signals > 0 {
Ok((successful_signals as f64 / total_signals as f64) * 100.0)
} else {
Ok(0.0) }
}