use polars::prelude::*;
use crate::indicators::moving_averages::{calculate_ema, calculate_sma};
use crate::indicators::oscillators::calculate_rsi;
mod trend_analysis;
mod cycle_identification;
mod fundamental_price_ratio;
mod secular_trend;
mod value_zones;
pub use trend_analysis::*;
pub use cycle_identification::*;
pub use fundamental_price_ratio::*;
pub use secular_trend::*;
pub use value_zones::*;
pub fn add_long_term_indicators(df: &DataFrame) -> PolarsResult<DataFrame> {
let mut result = df.clone();
trend_analysis::add_trend_analysis(&mut result, 200)?;
cycle_identification::add_cycle_analysis(&mut result)?;
fundamental_price_ratio::add_price_ratio_analysis(&mut result)?;
secular_trend::add_secular_trend_analysis(&mut result)?;
value_zones::add_value_zones_analysis(&mut result)?;
Ok(result)
}
pub fn generate_position_trading_signals(df: &DataFrame) -> PolarsResult<Series> {
let required_indicators = [
"trend_direction", "trend_strength", "cycle_phase",
"price_to_ma_ratio", "secular_momentum"
];
for indicator in required_indicators {
if !df.schema().contains(indicator) {
return Err(PolarsError::ComputeError(
format!("Required indicator '{}' not found", indicator).into(),
));
}
}
let trend_direction = df.column("trend_direction")?.i32()?;
let trend_strength = df.column("trend_strength")?.f64()?;
let cycle_phase = df.column("cycle_phase")?.i32()?;
let price_ratio = df.column("price_to_ma_ratio")?.f64()?;
let secular_momentum = df.column("secular_momentum")?.i32()?;
let has_value_rating = df.schema().contains("value_rating");
let value_rating = if has_value_rating {
Some(df.column("value_rating")?.i32()?)
} else {
None
};
let mut combined_signals = Vec::with_capacity(df.height());
for i in 0..df.height() {
let direction = trend_direction.get(i).unwrap_or(0);
let strength = trend_strength.get(i).unwrap_or(f64::NAN);
let cycle = cycle_phase.get(i).unwrap_or(0);
let ratio = price_ratio.get(i).unwrap_or(f64::NAN);
let momentum = secular_momentum.get(i).unwrap_or(0);
if strength.is_nan() || ratio.is_nan() {
combined_signals.push(0);
continue;
}
let mut bullish_count = 0;
let mut bearish_count = 0;
if direction > 0 { bullish_count += 2; }
if direction < 0 { bearish_count += 2; }
if cycle == 1 || cycle == 2 { bullish_count += 1; } if cycle == 3 || cycle == 4 { bearish_count += 1; }
if ratio < 0.8 { bullish_count += 1; } if ratio > 1.2 { bearish_count += 1; }
if momentum > 0 { bullish_count += 1; }
if momentum < 0 { bearish_count += 1; }
if let Some(vr) = &value_rating {
let vr_val = vr.get(i).unwrap_or(0);
if vr_val >= 4 { bullish_count += 1; } if vr_val <= 2 { bearish_count += 1; } }
if bullish_count >= 3 && bearish_count == 0 {
combined_signals.push(2); } else if bullish_count > bearish_count {
combined_signals.push(1); } else if bearish_count >= 3 && bullish_count == 0 {
combined_signals.push(-2); } else if bearish_count > bullish_count {
combined_signals.push(-1); } else {
combined_signals.push(0); }
}
Ok(Series::new("position_trading_signal", combined_signals))
}
pub fn calculate_position_sizing(
df: &DataFrame,
base_allocation: Option<f64>,
) -> PolarsResult<Series> {
let base_alloc = base_allocation.unwrap_or(10.0) / 100.0;
let signals = match generate_position_trading_signals(df) {
Ok(s) => s,
Err(_) => {
if !df.schema().contains("trend_direction") {
return Err(PolarsError::ComputeError(
"No trading signals found for position sizing".into(),
));
}
df.column("trend_direction")?.clone()
}
};
let signal_vals = signals.i32()?;
let has_trend_strength = df.schema().contains("trend_strength");
let trend_strength = if has_trend_strength {
Some(df.column("trend_strength")?.f64()?)
} else {
None
};
let has_value_rating = df.schema().contains("value_rating");
let value_rating = if has_value_rating {
Some(df.column("value_rating")?.i32()?)
} else {
None
};
let mut position_sizes = Vec::with_capacity(df.height());
for i in 0..df.height() {
let signal = signal_vals.get(i).unwrap_or(0);
if signal == 0 {
position_sizes.push(0.0);
continue;
}
let mut size = if signal.abs() == 2 {
base_alloc * 1.0 } else {
base_alloc * 0.75 };
if let Some(strength) = &trend_strength {
let strength_val = strength.get(i).unwrap_or(f64::NAN);
if !strength_val.is_nan() {
if strength_val > 50.0 {
size *= 1.0 + (strength_val - 50.0) / 100.0; } else if strength_val < 30.0 {
size *= 0.8; }
}
}
if let Some(vr) = &value_rating {
let vr_val = vr.get(i).unwrap_or(3);
match vr_val {
5 => size *= 1.2, 4 => size *= 1.1, 2 => size *= 0.9, 1 => size *= 0.8, _ => {} }
}
size = size.min(0.25);
position_sizes.push(size);
}
Ok(Series::new("position_size", position_sizes))
}