#!/usr/bin/env python3
"""
Metamodel Trainer
=================
Trainer for Asset Metamodels (Nivel 2 of hierarchical architecture).

This trainer orchestrates the full pipeline:
1. Load trained Attention Models (Nivel 0)
2. Load trained Base Models (Nivel 1)
3. Generate OOS (out-of-sample) predictions
4. Train Metamodels per asset

Key Design Decisions:
1. Uses ONLY out-of-sample predictions to avoid leakage
2. Trains one metamodel per asset (XAUUSD, EURUSD, etc.)
3. Combines 5m and 15m predictions

Temporal Split for OOS:
- Train Base Models: 2019-01 to 2023-12
- Generate OOS predictions: 2024-01 to 2024-08
- Train Metamodel on OOS: 2024-01 to 2024-08
- Final Evaluation: 2024-09 onwards

Author: ML Pipeline
Version: 1.0.0
Created: 2026-01-07
"""

import sys
import numpy as np
import pandas as pd
from typing import Dict, List, Tuple, Optional, Any, Union
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from pathlib import Path
import joblib
from loguru import logger
import importlib.util


@dataclass
class MetamodelTrainerConfig:
    """Configuration for the metamodel trainer"""

    # Assets to train
    symbols: List[str] = field(default_factory=lambda: [
        'XAUUSD', 'EURUSD', 'BTCUSD', 'GBPUSD', 'USDJPY'
    ])

    # Timeframes for base models
    timeframes: List[str] = field(default_factory=lambda: ['5m', '15m'])

    # Model paths
    attention_model_path: str = 'models/attention'
    base_model_path: str = 'models/base'
    output_path: str = 'models/metamodels'

    # OOS date ranges
    oos_start_date: str = '2024-01-01'  # Start of OOS period
    oos_end_date: str = '2024-08-31'    # End of OOS (training for metamodel)
    eval_start_date: str = '2024-09-01'  # Start of final evaluation

    # Training parameters
    min_oos_samples: int = 2000  # Minimum OOS samples for training
    val_split: float = 0.15

    # Context feature configuration
    atr_window: int = 50
    volume_window: int = 20


class MetamodelTrainer:
    """
    Trainer that orchestrates the full metamodel training pipeline.

    This class:
    1. Loads pre-trained Nivel 0 (attention) and Nivel 1 (base) models
    2. Generates OOS predictions from base models
    3. Trains Nivel 2 (metamodel) on OOS predictions

    Usage:
        trainer = MetamodelTrainer(config)
        trainer.load_models()

        # Train all metamodels
        results = trainer.train_all(data_dict)

        # Or train for specific symbol
        result = trainer.train_single(df_5m, df_15m, 'XAUUSD')

        # Save trained metamodels
        trainer.save()
    """

    def __init__(self, config: MetamodelTrainerConfig = None):
        self.config = config or MetamodelTrainerConfig()

        # Model storage
        self.attention_models: Dict[str, Any] = {}  # {symbol_timeframe: model}
        self.base_models: Dict[str, Any] = {}       # {symbol_timeframe_target: model}
        self.metamodels: Dict[str, Any] = {}        # {symbol: metamodel}

        # Results storage
        self.training_results: Dict[str, Any] = {}

        # State
        self._models_loaded = False
        self._AssetMetamodel = None

        logger.info(f"Initialized MetamodelTrainer")
        logger.info(f"  Symbols: {self.config.symbols}")
        logger.info(f"  Timeframes: {self.config.timeframes}")
        logger.info(f"  OOS period: {self.config.oos_start_date} to {self.config.oos_end_date}")

    def _import_metamodel_class(self):
        """Import AssetMetamodel class dynamically to avoid circular imports."""
        if self._AssetMetamodel is not None:
            return self._AssetMetamodel

        models_dir = Path(__file__).parent.parent / 'models'
        metamodel_path = models_dir / 'asset_metamodel.py'

        if not metamodel_path.exists():
            raise FileNotFoundError(f"asset_metamodel.py not found at {metamodel_path}")

        module_name = "models.asset_metamodel"
        if module_name not in sys.modules:
            spec = importlib.util.spec_from_file_location(module_name, metamodel_path)
            metamodel_module = importlib.util.module_from_spec(spec)
            sys.modules[module_name] = metamodel_module
            spec.loader.exec_module(metamodel_module)
        else:
            metamodel_module = sys.modules[module_name]

        self._AssetMetamodel = metamodel_module.AssetMetamodel
        self._MetamodelConfig = metamodel_module.MetamodelConfig
        return self._AssetMetamodel

    def load_models(self) -> bool:
        """
        Load pre-trained attention and base models.

        Returns:
            True if models loaded successfully
        """
        logger.info("\n" + "="*60)
        logger.info("Loading pre-trained models")
        logger.info("="*60)

        # Import attention model class
        models_dir = Path(__file__).parent.parent / 'models'
        attention_model_path = models_dir / 'attention_score_model.py'

        if not attention_model_path.exists():
            logger.error(f"attention_score_model.py not found at {attention_model_path}")
            return False

        # Load AttentionScoreModel class
        module_name = "models.attention_score_model"
        if module_name not in sys.modules:
            spec = importlib.util.spec_from_file_location(module_name, attention_model_path)
            attention_module = importlib.util.module_from_spec(spec)
            sys.modules[module_name] = attention_module
            spec.loader.exec_module(attention_module)
        else:
            attention_module = sys.modules[module_name]

        AttentionScoreModel = attention_module.AttentionScoreModel

        # Load attention models
        attention_path = Path(self.config.attention_model_path)
        logger.info(f"\nLoading attention models from {attention_path}")

        for symbol in self.config.symbols:
            for timeframe in self.config.timeframes:
                key = f"{symbol}_{timeframe}_attention"
                model_path = attention_path / key

                if model_path.exists():
                    try:
                        self.attention_models[key] = AttentionScoreModel.load(str(model_path))
                        logger.info(f"  Loaded: {key}")
                    except Exception as e:
                        logger.warning(f"  Failed to load {key}: {e}")
                else:
                    logger.warning(f"  Not found: {model_path}")

        logger.info(f"Loaded {len(self.attention_models)} attention models")

        # Load base models
        base_path = Path(self.config.base_model_path)
        logger.info(f"\nLoading base models from {base_path}")

        for symbol in self.config.symbols:
            for timeframe in self.config.timeframes:
                for target in ['high', 'low']:
                    # Model key format: XAUUSD_15m_high_h3
                    key = f"{symbol}_{timeframe}_{target}_h3"
                    model_file = base_path / f"{key}.joblib"

                    if model_file.exists():
                        try:
                            self.base_models[key] = joblib.load(model_file)
                            logger.info(f"  Loaded: {key}")
                        except Exception as e:
                            logger.warning(f"  Failed to load {key}: {e}")
                    else:
                        logger.warning(f"  Not found: {model_file}")

        logger.info(f"Loaded {len(self.base_models)} base models")

        self._models_loaded = len(self.attention_models) > 0 and len(self.base_models) > 0
        return self._models_loaded

    def _filter_oos_period(self, df: pd.DataFrame) -> pd.DataFrame:
        """Filter DataFrame to OOS period."""
        oos_start = pd.to_datetime(self.config.oos_start_date)
        oos_end = pd.to_datetime(self.config.oos_end_date)

        if isinstance(df.index, pd.DatetimeIndex):
            mask = (df.index >= oos_start) & (df.index <= oos_end)
        else:
            for col in ['timestamp', 'datetime', 'date', 'time']:
                if col in df.columns:
                    timestamps = pd.to_datetime(df[col])
                    mask = (timestamps >= oos_start) & (timestamps <= oos_end)
                    break
            else:
                logger.warning("No datetime column found, using all data")
                return df

        filtered = df[mask].copy()
        logger.info(f"OOS period: {len(filtered)} samples ({oos_start} to {oos_end})")
        return filtered

    def _generate_attention_features(
        self,
        df: pd.DataFrame,
        symbol: str,
        timeframe: str
    ) -> Tuple[np.ndarray, np.ndarray]:
        """
        Generate attention score and class from attention model.

        Returns:
            Tuple of (attention_score, attention_class)
        """
        key = f"{symbol}_{timeframe}_attention"

        if key not in self.attention_models:
            logger.warning(f"No attention model for {key}, using defaults")
            return np.ones(len(df)), np.ones(len(df), dtype=int)

        model = self.attention_models[key]
        prediction = model.predict(df)

        return prediction.attention_score, prediction.flow_class

    def _generate_base_predictions(
        self,
        features: np.ndarray,
        symbol: str,
        timeframe: str
    ) -> Dict[str, np.ndarray]:
        """
        Generate predictions from base models.

        Returns:
            Dict with 'high' and 'low' predictions
        """
        key_high = f"{symbol}_{timeframe}_high_h3"
        key_low = f"{symbol}_{timeframe}_low_h3"

        if key_high not in self.base_models or key_low not in self.base_models:
            logger.warning(f"Base models not found for {symbol} {timeframe}")
            return {'high': np.zeros(len(features)), 'low': np.zeros(len(features))}

        pred_high = self.base_models[key_high].predict(features)
        pred_low = self.base_models[key_low].predict(features)

        return {
            'high': pred_high,
            'low': pred_low
        }

    def _compute_context_features(self, df: pd.DataFrame) -> Dict[str, np.ndarray]:
        """Compute context features (ATR_ratio, volume_z)."""
        # Normalize column names
        high = df['High'] if 'High' in df.columns else df['high']
        low = df['Low'] if 'Low' in df.columns else df['low']
        close = df['Close'] if 'Close' in df.columns else df['close']
        volume = df['Volume'] if 'Volume' in df.columns else df.get('volume', pd.Series(1, index=df.index))

        # ATR
        tr1 = high - low
        tr2 = abs(high - close.shift(1))
        tr3 = abs(low - close.shift(1))
        true_range = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
        atr = true_range.rolling(14).mean()
        atr_median = atr.rolling(self.config.atr_window).median()
        atr_ratio = atr / (atr_median + 1e-10)

        # Volume z-score
        vol_mean = volume.rolling(self.config.volume_window).mean()
        vol_std = volume.rolling(self.config.volume_window).std()
        volume_z = (volume - vol_mean) / (vol_std + 1e-10)

        return {
            'ATR_ratio': atr_ratio.fillna(1.0).values,
            'volume_z': volume_z.fillna(0.0).values
        }

    def _compute_targets(
        self,
        df: pd.DataFrame,
        horizon_bars: int = 3
    ) -> Tuple[np.ndarray, np.ndarray]:
        """Compute actual targets for metamodel training."""
        # Normalize column names
        high = df['High'] if 'High' in df.columns else df['high']
        low = df['Low'] if 'Low' in df.columns else df['low']
        close = df['Close'] if 'Close' in df.columns else df['close']

        n = len(df)
        target_high = np.full(n, np.nan)
        target_low = np.full(n, np.nan)

        for i in range(n - horizon_bars):
            future_high = high.iloc[i+1:i+1+horizon_bars].max()
            future_low = low.iloc[i+1:i+1+horizon_bars].min()
            target_high[i] = future_high - close.iloc[i]
            target_low[i] = close.iloc[i] - future_low

        return target_high, target_low

    def _prepare_features_for_base_model(self, df: pd.DataFrame) -> np.ndarray:
        """
        Prepare features matching what base models were trained on.

        This should match the feature preparation in symbol_timeframe_trainer.
        """
        # Exclude known non-feature columns
        exclude_patterns = [
            'target_', 'high', 'low', 'open', 'close', 'volume',
            'High', 'Low', 'Open', 'Close', 'Volume',
            'timestamp', 'datetime', 'date', 'time',
            'rr_', 'direction', 'is_valid',
            'attention_score', 'attention_class'  # Exclude if base models weren't trained with attention
        ]

        feature_cols = []
        for col in df.columns:
            if not any(pat.lower() in col.lower() for pat in exclude_patterns):
                if df[col].dtype in [np.float64, np.float32, np.int64, np.int32, float, int]:
                    feature_cols.append(col)

        if len(feature_cols) == 0:
            logger.warning("No feature columns found, using all numeric columns")
            feature_cols = df.select_dtypes(include=[np.number]).columns.tolist()

        return df[feature_cols].fillna(0).values

    def train_single(
        self,
        df_5m: pd.DataFrame,
        df_15m: pd.DataFrame,
        symbol: str
    ) -> Dict[str, Any]:
        """
        Train metamodel for a single asset.

        Args:
            df_5m: 5-minute OHLCV data with features
            df_15m: 15-minute OHLCV data with features
            symbol: Trading symbol

        Returns:
            Training result dictionary
        """
        logger.info(f"\n{'='*60}")
        logger.info(f"Training Metamodel for {symbol}")
        logger.info(f"{'='*60}")

        # Import AssetMetamodel
        AssetMetamodel = self._import_metamodel_class()

        # Filter to OOS period
        df_5m_oos = self._filter_oos_period(df_5m)
        df_15m_oos = self._filter_oos_period(df_15m)

        if len(df_5m_oos) < self.config.min_oos_samples:
            logger.warning(f"Insufficient OOS 5m data: {len(df_5m_oos)}")
            return {'status': 'failed', 'reason': 'insufficient_5m_data'}

        if len(df_15m_oos) < self.config.min_oos_samples // 3:
            logger.warning(f"Insufficient OOS 15m data: {len(df_15m_oos)}")
            return {'status': 'failed', 'reason': 'insufficient_15m_data'}

        # ===== Generate OOS predictions for 5m =====
        logger.info("\nGenerating 5m OOS predictions...")

        # Attention features
        attention_score_5m, attention_class_5m = self._generate_attention_features(
            df_5m_oos, symbol, '5m'
        )

        # Add attention features to df for base model features
        df_5m_oos_features = df_5m_oos.copy()
        df_5m_oos_features['attention_score'] = attention_score_5m
        df_5m_oos_features['attention_class'] = attention_class_5m

        # Base model features
        features_5m = self._prepare_features_for_base_model(df_5m_oos_features)

        # Base predictions
        pred_5m = self._generate_base_predictions(features_5m, symbol, '5m')

        logger.info(f"  5m predictions: high mean={pred_5m['high'].mean():.2f}, low mean={pred_5m['low'].mean():.2f}")

        # ===== Generate OOS predictions for 15m =====
        logger.info("\nGenerating 15m OOS predictions...")

        # Attention features
        attention_score_15m, attention_class_15m = self._generate_attention_features(
            df_15m_oos, symbol, '15m'
        )

        # Add attention features
        df_15m_oos_features = df_15m_oos.copy()
        df_15m_oos_features['attention_score'] = attention_score_15m
        df_15m_oos_features['attention_class'] = attention_class_15m

        # Base model features
        features_15m = self._prepare_features_for_base_model(df_15m_oos_features)

        # Base predictions
        pred_15m = self._generate_base_predictions(features_15m, symbol, '15m')

        logger.info(f"  15m predictions: high mean={pred_15m['high'].mean():.2f}, low mean={pred_15m['low'].mean():.2f}")

        # ===== Align timestamps =====
        logger.info("\nAligning 5m and 15m data...")

        # Resample 5m to 15m by taking every 3rd row (approximate alignment)
        # In production, use proper timestamp alignment
        if len(df_5m_oos) >= len(df_15m_oos) * 3:
            # Downsample 5m to match 15m
            step = len(df_5m_oos) // len(df_15m_oos)
            indices_5m = np.arange(0, len(df_5m_oos), step)[:len(df_15m_oos)]

            pred_high_5m_aligned = pred_5m['high'][indices_5m]
            pred_low_5m_aligned = pred_5m['low'][indices_5m]
            attention_5m_aligned = attention_score_5m[indices_5m]
            attention_class_5m_aligned = attention_class_5m[indices_5m]

            # Use 15m data as base for context
            context_features = self._compute_context_features(df_15m_oos)
            target_high, target_low = self._compute_targets(df_15m_oos)

            n_samples = min(
                len(pred_high_5m_aligned),
                len(pred_15m['high']),
                len(context_features['ATR_ratio']),
                len(target_high)
            )
        else:
            # Use 15m data directly
            n_samples = len(df_15m_oos)
            pred_high_5m_aligned = np.interp(
                np.arange(n_samples),
                np.linspace(0, n_samples-1, len(pred_5m['high'])),
                pred_5m['high']
            )
            pred_low_5m_aligned = np.interp(
                np.arange(n_samples),
                np.linspace(0, n_samples-1, len(pred_5m['low'])),
                pred_5m['low']
            )
            attention_5m_aligned = np.interp(
                np.arange(n_samples),
                np.linspace(0, n_samples-1, len(attention_score_5m)),
                attention_score_5m
            )
            attention_class_5m_aligned = np.round(np.interp(
                np.arange(n_samples),
                np.linspace(0, n_samples-1, len(attention_class_5m)),
                attention_class_5m
            )).astype(int)

            context_features = self._compute_context_features(df_15m_oos)
            target_high, target_low = self._compute_targets(df_15m_oos)

        # ===== Create meta features DataFrame =====
        logger.info(f"\nCreating meta features ({n_samples} samples)...")

        meta_features = pd.DataFrame({
            'pred_high_5m': pred_high_5m_aligned[:n_samples],
            'pred_low_5m': pred_low_5m_aligned[:n_samples],
            'pred_high_15m': pred_15m['high'][:n_samples],
            'pred_low_15m': pred_15m['low'][:n_samples],
            'attention_5m': attention_5m_aligned[:n_samples],
            'attention_15m': attention_score_15m[:n_samples],
            'attention_class_5m': attention_class_5m_aligned[:n_samples],
            'attention_class_15m': attention_class_15m[:n_samples],
            'ATR_ratio': context_features['ATR_ratio'][:n_samples],
            'volume_z': context_features['volume_z'][:n_samples]
        })

        target_high = target_high[:n_samples]
        target_low = target_low[:n_samples]

        logger.info(f"Meta features shape: {meta_features.shape}")
        logger.info(f"Target high valid: {(~np.isnan(target_high)).sum()}")
        logger.info(f"Target low valid: {(~np.isnan(target_low)).sum()}")

        # ===== Train metamodel =====
        logger.info("\nTraining metamodel...")

        metamodel = AssetMetamodel(symbol, self._MetamodelConfig(
            min_train_samples=min(1000, n_samples // 2),
            val_split=self.config.val_split
        ))

        try:
            metamodel.fit(meta_features, target_high, target_low)
            self.metamodels[symbol] = metamodel

            # Save training data for Neural Gating training
            training_data_path = Path(self.config.output_path) / symbol / 'training_data.joblib'
            training_data_path.parent.mkdir(parents=True, exist_ok=True)
            joblib.dump({
                'meta_features': meta_features,
                'target_high': target_high,
                'target_low': target_low,
                'symbol': symbol,
                'n_samples': n_samples
            }, training_data_path)
            logger.info(f"Saved training data to {training_data_path}")

            result = {
                'status': 'success',
                'symbol': symbol,
                'n_samples': n_samples,
                'metrics': metamodel.get_training_summary()['metrics']
            }

            self.training_results[symbol] = result
            logger.info(f"\nMetamodel training complete for {symbol}")

            return result

        except Exception as e:
            logger.error(f"Failed to train metamodel for {symbol}: {e}")
            import traceback
            traceback.print_exc()
            return {'status': 'failed', 'reason': str(e)}

    def train_all(
        self,
        data_dict: Dict[str, Dict[str, pd.DataFrame]]
    ) -> Dict[str, Any]:
        """
        Train metamodels for all configured symbols.

        Args:
            data_dict: Dictionary structured as {symbol: {timeframe: df}}
                       e.g., {'XAUUSD': {'5m': df_5m, '15m': df_15m}}

        Returns:
            Dictionary of training results per symbol
        """
        all_results = {}

        for symbol in self.config.symbols:
            if symbol not in data_dict:
                logger.warning(f"No data for symbol: {symbol}")
                continue

            symbol_data = data_dict[symbol]

            if '5m' not in symbol_data or '15m' not in symbol_data:
                logger.warning(f"Missing 5m or 15m data for {symbol}")
                continue

            result = self.train_single(
                symbol_data['5m'],
                symbol_data['15m'],
                symbol
            )
            all_results[symbol] = result

        return all_results

    def predict(
        self,
        meta_features: pd.DataFrame,
        symbol: str
    ) -> Dict[str, Any]:
        """
        Generate predictions using trained metamodel.

        Args:
            meta_features: DataFrame with meta features
            symbol: Trading symbol

        Returns:
            Dictionary with predictions
        """
        if symbol not in self.metamodels:
            raise ValueError(f"No trained metamodel for {symbol}")

        prediction = self.metamodels[symbol].predict(meta_features)

        return {
            'delta_high': prediction.delta_high_final,
            'delta_low': prediction.delta_low_final,
            'confidence': prediction.confidence,
            'confidence_proba': prediction.confidence_proba,
            'symbol': symbol
        }

    def get_training_summary(self) -> pd.DataFrame:
        """Get summary of all trained metamodels."""
        rows = []
        for symbol, result in self.training_results.items():
            if result['status'] == 'success':
                metrics = result.get('metrics', {})
                rows.append({
                    'symbol': symbol,
                    'status': 'success',
                    'n_samples': result.get('n_samples', 0),
                    'mae_high': metrics.get('mae_high', np.nan),
                    'mae_low': metrics.get('mae_low', np.nan),
                    'r2_high': metrics.get('r2_high', np.nan),
                    'r2_low': metrics.get('r2_low', np.nan),
                    'confidence_accuracy': metrics.get('confidence_accuracy', np.nan),
                    'improvement_over_avg': metrics.get('improvement_over_avg', np.nan)
                })
            else:
                rows.append({
                    'symbol': symbol,
                    'status': 'failed',
                    'reason': result.get('reason', 'unknown')
                })

        return pd.DataFrame(rows)

    def save(self, path: str = None):
        """Save all trained metamodels."""
        path = Path(path or self.config.output_path)
        path.mkdir(parents=True, exist_ok=True)

        for symbol, metamodel in self.metamodels.items():
            model_path = path / symbol
            metamodel.save(str(model_path))

        # Save trainer metadata
        metadata = {
            'config': {
                'symbols': self.config.symbols,
                'timeframes': self.config.timeframes,
                'oos_start_date': self.config.oos_start_date,
                'oos_end_date': self.config.oos_end_date
            },
            'trained_symbols': list(self.metamodels.keys()),
            'training_results': self.training_results
        }
        joblib.dump(metadata, path / 'trainer_metadata.joblib')

        logger.info(f"Saved {len(self.metamodels)} metamodels to {path}")

    def load(self, path: str = None):
        """Load trained metamodels."""
        AssetMetamodel = self._import_metamodel_class()

        path = Path(path or self.config.output_path)

        metadata = joblib.load(path / 'trainer_metadata.joblib')
        self.training_results = metadata.get('training_results', {})

        for symbol in metadata['trained_symbols']:
            model_path = path / symbol
            if model_path.exists():
                self.metamodels[symbol] = AssetMetamodel.load(str(model_path))

        logger.info(f"Loaded {len(self.metamodels)} metamodels from {path}")


if __name__ == "__main__":
    # Test the module
    print("Testing MetamodelTrainer...")

    np.random.seed(42)

    # Generate sample data
    n_5m = 10000
    n_15m = n_5m // 3

    dates_5m = pd.date_range('2024-01-01', periods=n_5m, freq='5min')
    dates_15m = pd.date_range('2024-01-01', periods=n_15m, freq='15min')

    # XAUUSD sample data
    price_5m = 2650 + np.cumsum(np.random.randn(n_5m) * 0.5)
    price_15m = 2650 + np.cumsum(np.random.randn(n_15m) * 0.8)

    df_5m = pd.DataFrame({
        'Open': price_5m,
        'High': price_5m + np.abs(np.random.randn(n_5m)) * 3,
        'Low': price_5m - np.abs(np.random.randn(n_5m)) * 3,
        'Close': price_5m + np.random.randn(n_5m) * 0.3,
        'Volume': np.random.randint(100, 1000, n_5m),
        'rsi': 50 + np.random.randn(n_5m) * 10,
        'macd': np.random.randn(n_5m),
        'bb_width': 10 + np.random.randn(n_5m)
    }, index=dates_5m)

    df_15m = pd.DataFrame({
        'Open': price_15m,
        'High': price_15m + np.abs(np.random.randn(n_15m)) * 5,
        'Low': price_15m - np.abs(np.random.randn(n_15m)) * 5,
        'Close': price_15m + np.random.randn(n_15m) * 0.5,
        'Volume': np.random.randint(300, 3000, n_15m),
        'rsi': 50 + np.random.randn(n_15m) * 10,
        'macd': np.random.randn(n_15m),
        'bb_width': 10 + np.random.randn(n_15m)
    }, index=dates_15m)

    # Test trainer (without loading actual models - will use defaults)
    config = MetamodelTrainerConfig(
        symbols=['XAUUSD'],
        oos_start_date='2024-01-01',
        oos_end_date='2024-12-31',
        min_oos_samples=500
    )

    trainer = MetamodelTrainer(config)

    # Skip model loading for test (uses default zeros)
    print("\n[Note: Testing with synthetic data, skipping model loading]")

    # Test train_single with mock models
    print("\nTesting metamodel training...")
    result = trainer.train_single(df_5m, df_15m, 'XAUUSD')

    print(f"\nTraining result: {result['status']}")
    if result['status'] == 'success':
        print(f"  Samples: {result['n_samples']}")
        print(f"  MAE High: {result['metrics']['mae_high']:.4f}")
        print(f"  MAE Low: {result['metrics']['mae_low']:.4f}")

    print("\nTest complete!")
