import numpy as np from typing import Dict, List, Tuple try: from .custom_xgboost import CustomXGBoostGPU except ImportError: from custom_xgboost import CustomXGBoostGPU from sklearn.metrics import mean_squared_error, r2_score def _compute_metrics(y_true: np.ndarray, y_pred: np.ndarray) -> Tuple[float, float, float, float]: """Compute RMSE, MAPE, R2, and directional accuracy. Returns: (rmse, mape, r2, directional_accuracy) """ rmse = float(np.sqrt(mean_squared_error(y_true, y_pred))) with np.errstate(divide='ignore', invalid='ignore'): mape_arr = np.abs((y_true - y_pred) / np.where(y_true == 0, np.nan, y_true)) mape = float(np.nanmean(mape_arr) * 100.0) r2 = float(r2_score(y_true, y_pred)) direction_actual = np.sign(np.diff(y_true)) direction_pred = np.sign(np.diff(y_pred)) min_len = min(len(direction_actual), len(direction_pred)) if min_len == 0: dir_acc = 0.0 else: dir_acc = float((direction_actual[:min_len] == direction_pred[:min_len]).mean()) return rmse, mape, r2, dir_acc def walk_forward_cv( X: np.ndarray, y: np.ndarray, feature_names: List[str], n_splits: int = 5, ) -> Tuple[Dict[str, float], Dict[str, float]]: """Run simple walk-forward CV and aggregate metrics and feature importances. Returns: metrics_avg: Average metrics across folds {rmse, mape, r2, dir_acc} importance_avg: Average feature importance across folds {feature -> importance} """ num_samples = len(X) fold_size = num_samples // (n_splits + 1) if fold_size <= 0: raise ValueError("Not enough samples for walk-forward CV") metrics_accum = {"rmse": [], "mape": [], "r2": [], "dir_acc": []} importance_sum = {name: 0.0 for name in feature_names} for i in range(1, n_splits + 1): train_end = i * fold_size test_end = (i + 1) * fold_size if i < n_splits else num_samples X_train, y_train = X[:train_end], y[:train_end] X_test, y_test = X[train_end:test_end], y[train_end:test_end] if len(X_test) == 0: continue model = CustomXGBoostGPU(X_train, X_test, y_train, y_test) model.train(eval_metric='rmse') preds = model.predict(X_test) rmse, mape, r2, dir_acc = _compute_metrics(y_test, preds) metrics_accum["rmse"].append(rmse) metrics_accum["mape"].append(mape) metrics_accum["r2"].append(r2) metrics_accum["dir_acc"].append(dir_acc) fold_importance = model.get_feature_importance(feature_names) for name, val in fold_importance.items(): importance_sum[name] += float(val) metrics_avg = {k: float(np.mean(v)) if len(v) > 0 else float('nan') for k, v in metrics_accum.items()} importance_avg = {k: (importance_sum[k] / n_splits) for k in feature_names} return metrics_avg, importance_avg