use polars::prelude::*;
use crate::indicators::moving_averages::{calculate_sma, calculate_ema};
use crate::indicators::trend::calculate_adx;
pub fn calculate_long_term_trend_strength(
df: &DataFrame,
period: Option<usize>,
adx_period: Option<usize>,
smooth_period: Option<usize>,
) -> PolarsResult<Series> {
let base_period = period.unwrap_or(200);
let adx_len = adx_period.unwrap_or(50);
let smoothing = smooth_period.unwrap_or(20);
let adx = calculate_adx(df, adx_len)?;
let adx_values = adx.f64()?;
let sma_long = calculate_sma(df, "close", base_period)?;
let sma_medium = calculate_sma(df, "close", base_period / 2)?; let sma_short = calculate_sma(df, "close", base_period / 4)?;
let long_ma_vals = sma_long.f64()?;
let medium_ma_vals = sma_medium.f64()?;
let short_ma_vals = sma_short.f64()?;
let close = df.column("close")?.f64()?;
let mut trend_strength = Vec::with_capacity(df.height());
let min_periods = base_period.max(adx_len);
for i in 0..min_periods.min(df.height()) {
trend_strength.push(f64::NAN);
}
for i in min_periods..df.height() {
let adx_val = adx_values.get(i).unwrap_or(f64::NAN);
let long_ma = long_ma_vals.get(i).unwrap_or(f64::NAN);
let medium_ma = medium_ma_vals.get(i).unwrap_or(f64::NAN);
let short_ma = short_ma_vals.get(i).unwrap_or(f64::NAN);
let close_val = close.get(i).unwrap_or(f64::NAN);
if adx_val.is_nan() || long_ma.is_nan() || medium_ma.is_nan() || short_ma.is_nan() || close_val.is_nan() {
trend_strength.push(f64::NAN);
continue;
}
let mut strength = adx_val;
let long_ma_prev = long_ma_vals.get(i - smoothing).unwrap_or(long_ma);
let medium_ma_prev = medium_ma_vals.get(i - smoothing).unwrap_or(medium_ma);
let short_ma_prev = short_ma_vals.get(i - smoothing).unwrap_or(short_ma);
let long_slope = (long_ma - long_ma_prev) / long_ma_prev * 100.0;
let medium_slope = (medium_ma - medium_ma_prev) / medium_ma_prev * 100.0;
let short_slope = (short_ma - short_ma_prev) / short_ma_prev * 100.0;
let slopes_aligned = if (long_slope > 0.0 && medium_slope > 0.0 && short_slope > 0.0) ||
(long_slope < 0.0 && medium_slope < 0.0 && short_slope < 0.0) {
1.25
} else if (long_slope * medium_slope > 0.0) && (medium_slope * short_slope > 0.0) {
1.1
} else if long_slope * short_slope > 0.0 {
0.9
} else {
0.75
};
let price_position = if close_val > short_ma && short_ma > medium_ma && medium_ma > long_ma {
1.2
} else if close_val < short_ma && short_ma < medium_ma && medium_ma < long_ma {
1.2
} else if (close_val > long_ma && medium_ma > long_ma) ||
(close_val < long_ma && medium_ma < long_ma) {
1.0
} else {
0.8
};
strength = strength * slopes_aligned * price_position;
if i >= min_periods + smoothing {
let mut sum = strength;
for j in 1..=smoothing {
sum += trend_strength[i - j];
}
strength = sum / (smoothing as f64 + 1.0);
}
trend_strength.push(strength.min(100.0));
}
Ok(Series::new("trend_strength", trend_strength))
}
pub fn determine_trend_direction(
df: &DataFrame,
period: Option<usize>,
) -> PolarsResult<Series> {
let base_period = period.unwrap_or(200);
let sma_long = calculate_sma(df, "close", base_period)?;
let sma_medium = calculate_sma(df, "close", base_period / 2)?;
let sma_short = calculate_sma(df, "close", base_period / 4)?;
let long_ma_vals = sma_long.f64()?;
let medium_ma_vals = sma_medium.f64()?;
let short_ma_vals = sma_short.f64()?;
let close = df.column("close")?.f64()?;
let mut trend_direction = Vec::with_capacity(df.height());
for i in 0..base_period.min(df.height()) {
trend_direction.push(0);
}
for i in base_period..df.height() {
let long_ma = long_ma_vals.get(i).unwrap_or(f64::NAN);
let medium_ma = medium_ma_vals.get(i).unwrap_or(f64::NAN);
let short_ma = short_ma_vals.get(i).unwrap_or(f64::NAN);
let close_val = close.get(i).unwrap_or(f64::NAN);
if long_ma.is_nan() || medium_ma.is_nan() || short_ma.is_nan() || close_val.is_nan() {
trend_direction.push(0);
continue;
}
let lookback = 20.min(i);
let long_ma_prev = long_ma_vals.get(i - lookback).unwrap_or(long_ma);
let long_slope = (long_ma - long_ma_prev) / long_ma_prev * 100.0;
if close_val > long_ma && short_ma > medium_ma && medium_ma > long_ma && long_slope > 0.0 {
trend_direction.push(1);
} else if close_val < long_ma && short_ma < medium_ma && medium_ma < long_ma && long_slope < 0.0 {
trend_direction.push(-1);
} else if close_val > long_ma && short_ma > long_ma && long_slope >= 0.0 {
trend_direction.push(1);
} else if close_val < long_ma && short_ma < long_ma && long_slope <= 0.0 {
trend_direction.push(-1);
} else {
trend_direction.push(0);
}
}
Ok(Series::new("trend_direction", trend_direction))
}
pub fn calculate_trend_persistence(df: &DataFrame) -> PolarsResult<Series> {
if !df.schema().contains("trend_direction") {
return Err(PolarsError::ComputeError(
"trend_direction column not found. Calculate trend direction first.".into(),
));
}
let direction = df.column("trend_direction")?.i32()?;
let mut persistence = Vec::with_capacity(df.height());
if df.height() > 0 {
persistence.push(0);
}
for i in 1..df.height() {
let current_dir = direction.get(i).unwrap_or(0);
let prev_dir = direction.get(i - 1).unwrap_or(0);
if current_dir == prev_dir && current_dir != 0 {
persistence.push(persistence[i - 1] + 1);
} else {
persistence.push(0);
}
}
Ok(Series::new("trend_persistence", persistence))
}
pub fn add_trend_analysis(df: &mut DataFrame, period: usize) -> PolarsResult<()> {
let trend_strength = calculate_long_term_trend_strength(df, Some(period), None, None)?;
df.with_column(trend_strength)?;
let trend_direction = determine_trend_direction(df, Some(period))?;
df.with_column(trend_direction)?;
let trend_persistence = calculate_trend_persistence(df)?;
df.with_column(trend_persistence)?;
Ok(())
}