use crate::forecast_trade::error::{ForecastError, Result};
use crate::forecast_trade::data::TimeSeriesData;
use chrono::{DateTime, Duration, Utc};
pub fn train_test_split(data: &[f64], test_ratio: f64) -> (Vec<f64>, Vec<f64>) {
if data.is_empty() || test_ratio <= 0.0 || test_ratio >= 1.0 {
return (data.to_vec(), Vec::new());
}
let test_size = (data.len() as f64 * test_ratio).round() as usize;
let train_size = data.len() - test_size;
let train = data[..train_size].to_vec();
let test = data[train_size..].to_vec();
(train, test)
}
pub fn future_timestamps(last_timestamp: DateTime<Utc>, horizon: usize, frequency: &str) -> Result<Vec<DateTime<Utc>>> {
let mut timestamps = Vec::with_capacity(horizon);
let mut current = last_timestamp;
let duration = match frequency {
"daily" | "d" | "1d" => Duration::days(1),
"weekly" | "w" | "1w" => Duration::weeks(1),
"monthly" | "m" | "1m" => Duration::days(30),
"hourly" | "h" | "1h" => Duration::hours(1),
"minute" | "min" | "1min" => Duration::minutes(1),
_ => return Err(ForecastError::ValidationError(format!("Unsupported frequency: {}", frequency))),
};
for _ in 0..horizon {
current = current + duration;
timestamps.push(current);
}
Ok(timestamps)
}
pub fn forecast_accuracy(forecast: &[f64], actual: &[f64]) -> Result<ForecastAccuracy> {
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 n = forecast.len() as f64;
let errors: Vec<f64> = forecast.iter()
.zip(actual.iter())
.map(|(&f, &a)| a - f)
.collect();
let mae = errors.iter().map(|e| e.abs()).sum::<f64>() / n;
let mse = errors.iter().map(|e| e.powi(2)).sum::<f64>() / n;
let rmse = mse.sqrt();
let mape = actual.iter()
.zip(errors.iter())
.filter(|(&a, _)| a != 0.0)
.map(|(&a, &e)| (e.abs() / a.abs()) * 100.0)
.sum::<f64>() / n;
let smape = actual.iter()
.zip(forecast.iter())
.map(|(&a, &f)| {
let abs_a = a.abs();
let abs_f = f.abs();
if abs_a + abs_f == 0.0 {
0.0
} else {
200.0 * (a - f).abs() / (abs_a + abs_f)
}
})
.sum::<f64>() / n;
Ok(ForecastAccuracy {
mae,
mse,
rmse,
mape,
smape,
})
}
#[derive(Debug, Clone)]
pub struct ForecastAccuracy {
pub mae: f64,
pub mse: f64,
pub rmse: f64,
pub mape: f64,
pub smape: f64,
}
impl std::fmt::Display for ForecastAccuracy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Forecast Accuracy 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)?;
Ok(())
}
}