#!/usr/bin/env python3
"""
MTS Trainer - Hierarchical Training for Multi-Timeframe Synthesis
==================================================================
Training module for MTS model with hierarchical loss and walk-forward validation.

Training Pipeline:
1. Load multi-TF data from database or files
2. Prepare features and targets
3. Train attention model (optional pre-training)
4. Train XGBoost on unified representations
5. Walk-forward validation
6. Evaluate alignment accuracy

Author: ML-Specialist (NEXUS v4.0)
Version: 1.0.0
Created: 2026-01-25
"""

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
from typing import Dict, List, Optional, Tuple, Any
from dataclasses import dataclass, field
from pathlib import Path
import joblib
from datetime import datetime, timedelta
from loguru import logger

from sklearn.metrics import (
    accuracy_score,
    f1_score,
    mean_squared_error,
    mean_absolute_error,
    classification_report
)
from sklearn.model_selection import TimeSeriesSplit

from .model import MTSModel, MTSConfig, MTSPrediction
from .feature_engineering import MTSFeatureEngineer


@dataclass
class MTSTrainerConfig:
    """Configuration for MTS Trainer."""
    # Data loading
    base_timeframe: str = '5m'
    target_horizon: int = 12  # 1 hour forward (12 * 5m)
    return_threshold: float = 0.001  # 0.1% for direction classification

    # Attention training
    attention_epochs: int = 30
    attention_batch_size: int = 32
    attention_lr: float = 0.001
    attention_weight_decay: float = 0.01

    # Walk-forward validation
    n_folds: int = 5
    test_size_ratio: float = 0.2
    min_train_samples: int = 5000
    gap_samples: int = 100  # Gap between train and test

    # Early stopping
    patience: int = 10
    min_delta: float = 0.001

    # Output
    model_dir: str = 'models/mts'
    save_checkpoints: bool = True


@dataclass
class MTSTrainingMetrics:
    """Training metrics for MTS model."""
    fold: int
    train_samples: int
    test_samples: int

    # Direction metrics
    direction_accuracy: float
    direction_f1_macro: float
    direction_f1_weighted: float

    # Confidence metrics
    confidence_mae: float
    confidence_mse: float

    # Entry TF metrics
    entry_tf_accuracy: float

    # Alignment-based metrics
    aligned_direction_accuracy: float  # Accuracy when alignment > 0.7
    conflicting_direction_accuracy: float  # Accuracy when alignment < 0.5

    # Overall
    signal_quality: float  # Combined metric

    def to_dict(self) -> Dict[str, Any]:
        return {
            'fold': self.fold,
            'train_samples': self.train_samples,
            'test_samples': self.test_samples,
            'direction_accuracy': float(self.direction_accuracy),
            'direction_f1_macro': float(self.direction_f1_macro),
            'direction_f1_weighted': float(self.direction_f1_weighted),
            'confidence_mae': float(self.confidence_mae),
            'confidence_mse': float(self.confidence_mse),
            'entry_tf_accuracy': float(self.entry_tf_accuracy),
            'aligned_direction_accuracy': float(self.aligned_direction_accuracy),
            'conflicting_direction_accuracy': float(self.conflicting_direction_accuracy),
            'signal_quality': float(self.signal_quality)
        }


class MTSDataset(Dataset):
    """PyTorch Dataset for MTS training."""

    def __init__(
        self,
        tf_features: Dict[str, np.ndarray],
        alignments: np.ndarray,
        conflicts: np.ndarray,
        direction_targets: np.ndarray,
        confidence_targets: np.ndarray,
        entry_tf_targets: np.ndarray
    ):
        """
        Initialize MTS Dataset.

        Args:
            tf_features: Dictionary of {timeframe: features}
            alignments: Alignment scores
            conflicts: Conflict scores
            direction_targets: Direction labels (0, 1, 2)
            confidence_targets: Confidence targets (0-1)
            entry_tf_targets: Entry TF labels
        """
        self.tf_features = {
            tf: torch.tensor(features, dtype=torch.float32)
            for tf, features in tf_features.items()
        }
        self.alignments = torch.tensor(alignments, dtype=torch.float32)
        self.conflicts = torch.tensor(conflicts, dtype=torch.float32)
        self.direction_targets = torch.tensor(direction_targets, dtype=torch.long)
        self.confidence_targets = torch.tensor(confidence_targets, dtype=torch.float32)
        self.entry_tf_targets = torch.tensor(entry_tf_targets, dtype=torch.long)

        # Get number of samples from any feature array
        self.n_samples = len(alignments)

    def __len__(self) -> int:
        return self.n_samples

    def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
        return {
            'tf_features': {tf: features[idx] for tf, features in self.tf_features.items()},
            'alignment': self.alignments[idx],
            'conflict': self.conflicts[idx],
            'direction': self.direction_targets[idx],
            'confidence': self.confidence_targets[idx],
            'entry_tf': self.entry_tf_targets[idx]
        }


class HierarchicalLoss(nn.Module):
    """
    Hierarchical loss function for MTS training.

    Combines:
    1. Direction classification loss (CE with class weights)
    2. Confidence regression loss (MSE)
    3. Entry TF classification loss (CE)
    4. Alignment consistency loss (encourage agreement with alignment)
    """

    def __init__(
        self,
        direction_weight: float = 1.0,
        confidence_weight: float = 0.5,
        entry_tf_weight: float = 0.3,
        alignment_weight: float = 0.2,
        direction_class_weights: Optional[torch.Tensor] = None
    ):
        super().__init__()

        self.direction_weight = direction_weight
        self.confidence_weight = confidence_weight
        self.entry_tf_weight = entry_tf_weight
        self.alignment_weight = alignment_weight

        self.direction_loss = nn.CrossEntropyLoss(weight=direction_class_weights)
        self.confidence_loss = nn.MSELoss()
        self.entry_tf_loss = nn.CrossEntropyLoss()

    def forward(
        self,
        direction_logits: torch.Tensor,
        confidence_pred: torch.Tensor,
        entry_tf_logits: torch.Tensor,
        direction_target: torch.Tensor,
        confidence_target: torch.Tensor,
        entry_tf_target: torch.Tensor,
        alignment: torch.Tensor
    ) -> Tuple[torch.Tensor, Dict[str, float]]:
        """
        Compute hierarchical loss.

        Returns:
            total_loss: Combined weighted loss
            loss_components: Dictionary of individual losses
        """
        # Direction loss
        dir_loss = self.direction_loss(direction_logits, direction_target)

        # Confidence loss
        conf_loss = self.confidence_loss(confidence_pred.squeeze(), confidence_target)

        # Entry TF loss
        entry_loss = self.entry_tf_loss(entry_tf_logits, entry_tf_target)

        # Alignment consistency loss
        # Higher alignment should correspond to higher confidence in prediction
        direction_probs = F.softmax(direction_logits, dim=-1)
        direction_confidence = direction_probs.max(dim=-1)[0]
        alignment_consistency = F.mse_loss(direction_confidence, alignment)

        # Combined loss
        total_loss = (
            self.direction_weight * dir_loss +
            self.confidence_weight * conf_loss +
            self.entry_tf_weight * entry_loss +
            self.alignment_weight * alignment_consistency
        )

        loss_components = {
            'direction': float(dir_loss),
            'confidence': float(conf_loss),
            'entry_tf': float(entry_loss),
            'alignment': float(alignment_consistency),
            'total': float(total_loss)
        }

        return total_loss, loss_components


class MTSTrainer:
    """
    Trainer for Multi-Timeframe Synthesis model.

    Supports:
    - Multi-timeframe data loading
    - Hierarchical loss training
    - Walk-forward validation
    - Alignment accuracy evaluation
    """

    def __init__(
        self,
        model: Optional[MTSModel] = None,
        config: Optional[MTSTrainerConfig] = None,
        device: Optional[str] = None
    ):
        """
        Initialize MTS Trainer.

        Args:
            model: MTSModel instance (created if None)
            config: Trainer configuration
            device: Device for training
        """
        self.config = config or MTSTrainerConfig()
        self.device = device or ('cuda' if torch.cuda.is_available() else 'cpu')

        self.model = model or MTSModel(device=self.device)
        self.feature_engineer = MTSFeatureEngineer(
            timeframes=list(self.model.config.timeframes)
        )

        self.training_history: List[Dict] = []
        self.fold_metrics: List[MTSTrainingMetrics] = []

        logger.info(f"Initialized MTSTrainer with device={self.device}")

    def load_multi_tf_data(
        self,
        symbol: str,
        data_source: Optional[str] = None,
        start_date: Optional[datetime] = None,
        end_date: Optional[datetime] = None
    ) -> pd.DataFrame:
        """
        Load multi-timeframe data for a symbol.

        Args:
            symbol: Trading symbol (e.g., 'EURUSD')
            data_source: Path to data file or database connection
            start_date: Start date for data
            end_date: End date for data

        Returns:
            DataFrame with 5m OHLCV data
        """
        logger.info(f"Loading data for {symbol}...")

        if data_source is not None and Path(data_source).exists():
            # Load from file
            df = pd.read_csv(data_source, parse_dates=['timestamp'])
            df = df.set_index('timestamp')
        else:
            # Generate synthetic data for testing
            logger.warning("No data source provided, generating synthetic data")
            df = self._generate_synthetic_data(symbol, start_date, end_date)

        # Filter by date if specified
        if start_date is not None:
            df = df[df.index >= start_date]
        if end_date is not None:
            df = df[df.index <= end_date]

        logger.info(f"Loaded {len(df)} samples for {symbol}")

        return df

    def _generate_synthetic_data(
        self,
        symbol: str,
        start_date: Optional[datetime] = None,
        end_date: Optional[datetime] = None
    ) -> pd.DataFrame:
        """Generate synthetic OHLCV data for testing."""
        start_date = start_date or datetime(2024, 1, 1)
        end_date = end_date or datetime(2025, 1, 1)

        # Generate timestamps
        timestamps = pd.date_range(start_date, end_date, freq='5min')
        n_samples = len(timestamps)

        # Generate price data with trend and seasonality
        np.random.seed(42)

        # Base price with trend
        trend = np.linspace(0, 0.1, n_samples)
        seasonality = 0.02 * np.sin(2 * np.pi * np.arange(n_samples) / (24 * 12))  # Daily cycle
        noise = np.random.randn(n_samples) * 0.001

        returns = trend / n_samples + seasonality / n_samples + noise
        close = 1.1000 * np.cumprod(1 + returns)

        # Generate OHLC from close
        high = close * (1 + np.abs(np.random.randn(n_samples) * 0.002))
        low = close * (1 - np.abs(np.random.randn(n_samples) * 0.002))
        open_price = np.roll(close, 1) * (1 + np.random.randn(n_samples) * 0.0005)
        open_price[0] = close[0]

        volume = np.random.randint(100, 10000, n_samples)

        return pd.DataFrame({
            'open': open_price,
            'high': high,
            'low': low,
            'close': close,
            'volume': volume
        }, index=timestamps)

    def prepare_training_data(
        self,
        df_5m: pd.DataFrame
    ) -> Tuple[Dict[str, np.ndarray], np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
        """
        Prepare training data from 5m DataFrame.

        Returns:
            tf_features: Per-TF features
            alignments: Alignment scores
            conflicts: Conflict scores
            direction_targets: Direction labels
            confidence_targets: Confidence targets
            entry_tf_targets: Entry TF labels
        """
        # Prepare hierarchical features
        hierarchical, direction_targets, magnitude_targets = self.feature_engineer.prepare_training_data(
            df_5m,
            target_horizon=self.config.target_horizon,
            return_threshold=self.config.return_threshold
        )

        tf_features = hierarchical['tf_features']
        alignments = hierarchical['alignment']
        conflicts = hierarchical['conflict']
        dominant_tf = hierarchical['dominant_tf']

        # Confidence targets based on magnitude and alignment
        confidence_targets = np.clip(magnitude_targets * 10 * alignments, 0, 1)

        # Entry TF targets (use dominant TF as proxy)
        entry_tf_targets = dominant_tf.astype(np.int64)

        # Convert direction to 3 classes (0=bearish, 1=neutral, 2=bullish)
        direction_3class = np.ones_like(direction_targets)  # Start with neutral
        direction_3class[direction_targets > 0.5] = 2  # Bullish
        direction_3class[direction_targets < 0.5] = 0  # Bearish

        # Make magnitudes determine neutral
        neutral_mask = magnitude_targets < self.config.return_threshold
        direction_3class[neutral_mask] = 1

        return (
            tf_features,
            alignments.astype(np.float32),
            conflicts.astype(np.float32),
            direction_3class.astype(np.int64),
            confidence_targets.astype(np.float32),
            entry_tf_targets.astype(np.int64)
        )

    def train(
        self,
        symbol: str,
        data: Optional[pd.DataFrame] = None,
        data_source: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Train MTS model on a symbol.

        Args:
            symbol: Trading symbol
            data: Pre-loaded DataFrame (optional)
            data_source: Path to data source

        Returns:
            Training results dictionary
        """
        logger.info(f"Training MTS model for {symbol}")

        # Load data
        if data is None:
            data = self.load_multi_tf_data(symbol, data_source)

        # Prepare training data
        (tf_features, alignments, conflicts, direction_targets,
         confidence_targets, entry_tf_targets) = self.prepare_training_data(data)

        # Build model if needed
        if self.model.input_dims is None:
            input_dims = {tf: features.shape[-1] for tf, features in tf_features.items()}
            self.model._build_attention_model(input_dims)

        # Train attention model
        self._train_attention(
            tf_features, alignments, conflicts,
            direction_targets, confidence_targets
        )

        # Extract unified representations
        unified_reps = self.model._extract_unified_representation(
            tf_features, alignments, conflicts
        )

        # Train XGBoost
        self.model.train_xgboost(
            unified_reps,
            direction_targets,
            confidence_targets,
            entry_tf_targets
        )

        logger.info(f"Training complete for {symbol}")

        return {
            'symbol': symbol,
            'n_samples': len(direction_targets),
            'tf_contributions': self.model.get_tf_contributions()
        }

    def _train_attention(
        self,
        tf_features: Dict[str, np.ndarray],
        alignments: np.ndarray,
        conflicts: np.ndarray,
        direction_targets: np.ndarray,
        confidence_targets: np.ndarray
    ):
        """Train attention model."""
        logger.info("Training attention model...")

        self.model.attention_model.train()

        # Prepare tensors
        tf_tensors = {
            tf: torch.tensor(features, dtype=torch.float32, device=self.device)
            for tf, features in tf_features.items()
        }

        alignment_tensor = torch.tensor(alignments, dtype=torch.float32, device=self.device)
        conflict_tensor = torch.tensor(conflicts, dtype=torch.float32, device=self.device)

        # Optimizer
        optimizer = torch.optim.AdamW(
            self.model.attention_model.parameters(),
            lr=self.config.attention_lr,
            weight_decay=self.config.attention_weight_decay
        )

        scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(
            optimizer, T_0=10, T_mult=2
        )

        best_loss = float('inf')
        patience_counter = 0

        for epoch in range(self.config.attention_epochs):
            optimizer.zero_grad()

            # Forward pass (using full batch for simplicity)
            unified = self.model.attention_model(
                {tf: t.unsqueeze(0) for tf, t in tf_tensors.items()},
                alignment_tensor.unsqueeze(0),
                conflict_tensor.unsqueeze(0)
            )

            # Compute loss (encourage representation to capture alignment)
            alignment_mean = alignment_tensor.mean()

            # Representation should be more confident when alignment is high
            rep_norm = unified.norm(dim=-1).mean()
            loss = -alignment_mean * rep_norm + 0.1 * conflict_tensor.mean() * rep_norm

            loss.backward()
            torch.nn.utils.clip_grad_norm_(self.model.attention_model.parameters(), 1.0)
            optimizer.step()
            scheduler.step()

            current_loss = loss.item()

            if current_loss < best_loss - self.config.min_delta:
                best_loss = current_loss
                patience_counter = 0
            else:
                patience_counter += 1

            if patience_counter >= self.config.patience:
                logger.info(f"Early stopping at epoch {epoch + 1}")
                break

            if (epoch + 1) % 10 == 0:
                logger.info(f"Epoch {epoch + 1}/{self.config.attention_epochs}, Loss: {current_loss:.4f}")

        self.model.attention_model.eval()
        logger.info("Attention training complete")

    def walk_forward_train(
        self,
        symbol: str,
        data: Optional[pd.DataFrame] = None,
        n_folds: Optional[int] = None
    ) -> List[MTSTrainingMetrics]:
        """
        Walk-forward training with multiple folds.

        Args:
            symbol: Trading symbol
            data: Pre-loaded DataFrame
            n_folds: Number of folds (default from config)

        Returns:
            List of metrics for each fold
        """
        n_folds = n_folds or self.config.n_folds

        logger.info(f"Walk-forward training for {symbol} with {n_folds} folds")

        # Load data
        if data is None:
            data = self.load_multi_tf_data(symbol)

        # Prepare full dataset
        (tf_features, alignments, conflicts, direction_targets,
         confidence_targets, entry_tf_targets) = self.prepare_training_data(data)

        n_samples = len(direction_targets)
        logger.info(f"Total samples: {n_samples}")

        # Time series split
        tscv = TimeSeriesSplit(n_splits=n_folds)

        self.fold_metrics = []

        for fold, (train_idx, test_idx) in enumerate(tscv.split(direction_targets)):
            logger.info(f"\n{'='*60}")
            logger.info(f"Fold {fold + 1}/{n_folds}")
            logger.info(f"{'='*60}")

            # Apply gap
            test_idx = test_idx[test_idx >= train_idx[-1] + self.config.gap_samples]

            if len(test_idx) < 100:
                logger.warning(f"Skipping fold {fold + 1}: insufficient test samples")
                continue

            # Split data
            train_tf_features = {tf: features[train_idx] for tf, features in tf_features.items()}
            test_tf_features = {tf: features[test_idx] for tf, features in tf_features.items()}

            train_alignments = alignments[train_idx]
            test_alignments = alignments[test_idx]

            train_conflicts = conflicts[train_idx]
            test_conflicts = conflicts[test_idx]

            train_direction = direction_targets[train_idx]
            test_direction = direction_targets[test_idx]

            train_confidence = confidence_targets[train_idx]
            test_confidence = confidence_targets[test_idx]

            train_entry_tf = entry_tf_targets[train_idx]
            test_entry_tf = entry_tf_targets[test_idx]

            logger.info(f"Train samples: {len(train_idx)}, Test samples: {len(test_idx)}")

            # Build fresh model for this fold
            input_dims = {tf: features.shape[-1] for tf, features in train_tf_features.items()}
            self.model._build_attention_model(input_dims)

            # Train attention
            self._train_attention(
                train_tf_features, train_alignments, train_conflicts,
                train_direction, train_confidence
            )

            # Extract representations
            train_reps = self.model._extract_unified_representation(
                train_tf_features, train_alignments, train_conflicts
            )
            test_reps = self.model._extract_unified_representation(
                test_tf_features, test_alignments, test_conflicts
            )

            # Train XGBoost
            self.model.train_xgboost(
                train_reps, train_direction, train_confidence, train_entry_tf,
                X_val=test_reps, y_val_direction=test_direction
            )

            # Evaluate
            metrics = self.evaluate_alignment_accuracy(
                test_reps,
                test_direction,
                test_confidence,
                test_entry_tf,
                test_alignments,
                fold=fold + 1
            )

            self.fold_metrics.append(metrics)

            logger.info(f"Fold {fold + 1} - Direction Acc: {metrics.direction_accuracy:.4f}, "
                       f"Aligned Acc: {metrics.aligned_direction_accuracy:.4f}")

        # Log summary
        self._log_walk_forward_summary()

        return self.fold_metrics

    def evaluate_alignment_accuracy(
        self,
        X_test: np.ndarray,
        y_direction: np.ndarray,
        y_confidence: np.ndarray,
        y_entry_tf: np.ndarray,
        alignments: np.ndarray,
        fold: int = 0
    ) -> MTSTrainingMetrics:
        """
        Evaluate model with alignment-aware metrics.

        Args:
            X_test: Test representations
            y_direction: True direction labels
            y_confidence: True confidence targets
            y_entry_tf: True entry TF labels
            alignments: Alignment scores
            fold: Fold number

        Returns:
            MTSTrainingMetrics object
        """
        # Predictions
        pred_direction = self.model.xgb_direction.predict(X_test)
        pred_confidence = self.model.xgb_confidence.predict(X_test)
        pred_entry_tf = self.model.xgb_entry_tf.predict(X_test)

        # Direction metrics
        direction_accuracy = accuracy_score(y_direction, pred_direction)
        direction_f1_macro = f1_score(y_direction, pred_direction, average='macro')
        direction_f1_weighted = f1_score(y_direction, pred_direction, average='weighted')

        # Confidence metrics
        confidence_mae = mean_absolute_error(y_confidence, pred_confidence)
        confidence_mse = mean_squared_error(y_confidence, pred_confidence)

        # Entry TF metrics
        entry_tf_accuracy = accuracy_score(y_entry_tf, pred_entry_tf)

        # Alignment-based metrics
        high_alignment_mask = alignments > 0.7
        low_alignment_mask = alignments < 0.5

        if high_alignment_mask.sum() > 0:
            aligned_direction_accuracy = accuracy_score(
                y_direction[high_alignment_mask],
                pred_direction[high_alignment_mask]
            )
        else:
            aligned_direction_accuracy = 0.0

        if low_alignment_mask.sum() > 0:
            conflicting_direction_accuracy = accuracy_score(
                y_direction[low_alignment_mask],
                pred_direction[low_alignment_mask]
            )
        else:
            conflicting_direction_accuracy = 0.0

        # Signal quality (combined metric)
        signal_quality = (
            0.4 * direction_accuracy +
            0.3 * (1 - confidence_mae) +
            0.2 * aligned_direction_accuracy +
            0.1 * entry_tf_accuracy
        )

        return MTSTrainingMetrics(
            fold=fold,
            train_samples=0,  # Set externally
            test_samples=len(y_direction),
            direction_accuracy=direction_accuracy,
            direction_f1_macro=direction_f1_macro,
            direction_f1_weighted=direction_f1_weighted,
            confidence_mae=confidence_mae,
            confidence_mse=confidence_mse,
            entry_tf_accuracy=entry_tf_accuracy,
            aligned_direction_accuracy=aligned_direction_accuracy,
            conflicting_direction_accuracy=conflicting_direction_accuracy,
            signal_quality=signal_quality
        )

    def _log_walk_forward_summary(self):
        """Log summary of walk-forward validation."""
        if not self.fold_metrics:
            return

        logger.info("\n" + "=" * 70)
        logger.info("WALK-FORWARD VALIDATION SUMMARY")
        logger.info("=" * 70)

        # Aggregate metrics
        direction_accs = [m.direction_accuracy for m in self.fold_metrics]
        aligned_accs = [m.aligned_direction_accuracy for m in self.fold_metrics]
        signal_qualities = [m.signal_quality for m in self.fold_metrics]

        logger.info(f"\nDirection Accuracy:")
        logger.info(f"  Mean: {np.mean(direction_accs):.4f} (+/- {np.std(direction_accs):.4f})")
        logger.info(f"  Min: {np.min(direction_accs):.4f}, Max: {np.max(direction_accs):.4f}")

        logger.info(f"\nAligned Direction Accuracy:")
        logger.info(f"  Mean: {np.mean(aligned_accs):.4f} (+/- {np.std(aligned_accs):.4f})")

        logger.info(f"\nSignal Quality:")
        logger.info(f"  Mean: {np.mean(signal_qualities):.4f} (+/- {np.std(signal_qualities):.4f})")

        logger.info("\n" + "=" * 70)

    def save_results(self, path: str):
        """Save training results."""
        path = Path(path)
        path.mkdir(parents=True, exist_ok=True)

        # Save model
        self.model.save(str(path / 'model'))

        # Save metrics
        metrics_data = [m.to_dict() for m in self.fold_metrics]
        joblib.dump(metrics_data, path / 'fold_metrics.joblib')

        # Save training history
        joblib.dump(self.training_history, path / 'training_history.joblib')

        logger.info(f"Saved results to {path}")


if __name__ == "__main__":
    # Test MTSTrainer
    np.random.seed(42)
    torch.manual_seed(42)

    # Initialize trainer
    config = MTSTrainerConfig(
        attention_epochs=10,
        n_folds=3
    )

    trainer = MTSTrainer(config=config)

    # Generate synthetic data
    df = trainer._generate_synthetic_data('EURUSD')
    print(f"Generated {len(df)} samples")

    # Test walk-forward training
    metrics = trainer.walk_forward_train('EURUSD', data=df)

    print("\nFinal Metrics:")
    for m in metrics:
        print(f"  Fold {m.fold}: Direction={m.direction_accuracy:.4f}, "
              f"Aligned={m.aligned_direction_accuracy:.4f}")

    print("\nMTSTrainer test complete!")
