use crate::forecast_trade::error::{ForecastError, Result};
use crate::forecast_trade::models::ForecastModel;
use crate::utils::forecast_accuracy;
use crate::day_trade::Signal;
pub fn evaluate_forecast(forecast: &[f64], actual: &[f64]) -> Result<ForecastMetrics> {
if forecast.len() != actual.len() || forecast.is_empty() {
return Err(ForecastError::ValidationError(
"Forecast and actual values must have the same non-zero length".to_string(),
));
}
let accuracy = forecast_accuracy(forecast, actual)?;
let direction_correct = forecast.windows(2)
.zip(actual.windows(2))
.filter(|(f, a)| (f[1] - f[0]).abs() > 1e-10 && (a[1] - a[0]).abs() > 1e-10)
.map(|(f, a)| (f[1] > f[0]) == (a[1] > a[0]))
.filter(|&correct| correct)
.count();
let direction_total = forecast.windows(2)
.zip(actual.windows(2))
.filter(|(f, a)| (f[1] - f[0]).abs() > 1e-10 && (a[1] - a[0]).abs() > 1e-10)
.count();
let direction_accuracy = if direction_total > 0 {
direction_correct as f64 / direction_total as f64 * 100.0
} else {
0.0
};
Ok(ForecastMetrics {
mae: accuracy.mae,
mse: accuracy.mse,
rmse: accuracy.rmse,
mape: accuracy.mape,
smape: accuracy.smape,
direction_accuracy,
})
}
pub fn evaluate_model<M: ForecastModel>(
model: &mut M,
train_data: &[f64],
test_data: &[f64],
horizon: usize
) -> Result<ForecastMetrics> {
model.fit(train_data)?;
let forecast = model.forecast(horizon)?;
let min_len = forecast.values.len().min(test_data.len());
evaluate_forecast(&forecast.values[..min_len], &test_data[..min_len])
}
pub fn evaluate_strategy(signals: &[Signal], prices: &[f64]) -> Result<StrategyMetrics> {
if signals.len() != prices.len() || signals.is_empty() {
return Err(ForecastError::ValidationError(
"Signals and prices must have the same non-zero length".to_string(),
));
}
let mut returns = Vec::with_capacity(signals.len() - 1);
let mut positions = Vec::with_capacity(signals.len());
let mut position = 0;
positions.push(position);
for i in 0..signals.len() - 1 {
match signals[i] {
Signal::Buy => position = 1,
Signal::Sell => position = -1,
Signal::Hold => (), }
positions.push(position);
let price_return = (prices[i + 1] - prices[i]) / prices[i];
let strategy_return = position as f64 * price_return;
returns.push(strategy_return);
}
let total_return: f64 = returns.iter().sum();
let mean_return = total_return / returns.len() as f64;
let variance = returns.iter()
.map(|&r| (r - mean_return).powi(2))
.sum::<f64>() / returns.len() as f64;
let std_dev = variance.sqrt();
let sharpe = if std_dev > 0.0 {
mean_return / std_dev * (252_f64).sqrt() } else {
0.0
};
let mut max_value = 1.0;
let mut current_value = 1.0;
let mut max_drawdown = 0.0;
for &ret in &returns {
current_value *= 1.0 + ret;
if current_value > max_value {
max_value = current_value;
}
let drawdown = (max_value - current_value) / max_value;
if drawdown > max_drawdown {
max_drawdown = drawdown;
}
}
Ok(StrategyMetrics {
total_return,
sharpe_ratio: sharpe,
max_drawdown,
win_rate: calculate_win_rate(&returns),
})
}
fn calculate_win_rate(returns: &[f64]) -> f64 {
if returns.is_empty() {
return 0.0;
}
let wins = returns.iter().filter(|&&r| r > 0.0).count();
wins as f64 / returns.len() as f64 * 100.0
}
#[derive(Debug, Clone)]
pub struct ForecastMetrics {
pub mae: f64,
pub mse: f64,
pub rmse: f64,
pub mape: f64,
pub smape: f64,
pub direction_accuracy: f64,
}
impl std::fmt::Display for ForecastMetrics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Forecast Performance Metrics:")?;
writeln!(f, " MAE: {:.4}", self.mae)?;
writeln!(f, " MSE: {:.4}", self.mse)?;
writeln!(f, " RMSE: {:.4}", self.rmse)?;
writeln!(f, " MAPE: {:.4}%", self.mape)?;
writeln!(f, " SMAPE: {:.4}%", self.smape)?;
writeln!(f, " Direction: {:.2}%", self.direction_accuracy)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct StrategyMetrics {
pub total_return: f64,
pub sharpe_ratio: f64,
pub max_drawdown: f64,
pub win_rate: f64,
}
impl std::fmt::Display for StrategyMetrics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Strategy Performance Metrics:")?;
writeln!(f, " Total Return: {:.2}%", self.total_return * 100.0)?;
writeln!(f, " Sharpe Ratio: {:.2}", self.sharpe_ratio)?;
writeln!(f, " Max Drawdown: {:.2}%", self.max_drawdown * 100.0)?;
writeln!(f, " Win Rate: {:.2}%", self.win_rate)?;
Ok(())
}
}
pub fn maximum_drawdown(values: &[f64]) -> f64 {
if values.is_empty() {
return 0.0;
}
let mut max_value: f64 = 1.0;
let mut current_value = 1.0;
let mut max_drawdown: f64 = 0.0;
for &val in values {
let price_movement = if val > -1.0 { 1.0 + val } else { 0.0 };
current_value *= price_movement;
if current_value > max_value {
max_value = current_value;
}
let drawdown = 1.0 - (current_value / max_value);
if drawdown > max_drawdown {
max_drawdown = drawdown;
}
}
max_drawdown
}