#!/usr/bin/env python3
"""
Machine Learning Integration Module for Whale Bot Phase 3
Advanced ML models for whale behavior prediction and market analysis

Author: Pezhman Hajipour
Version: 3.0.0
"""

import logging
import numpy as np
import asyncio
from typing import Dict, List, Optional, Tuple, Any
from datetime import datetime, timedelta
from dataclasses import dataclass
import json
import pickle
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
import warnings
warnings.filterwarnings('ignore')

# Import advanced ML libraries (ml tag)
try:
    import tensorflow as tf
    from tensorflow import keras
    TENSORFLOW_AVAILABLE = True
except ImportError:
    TENSORFLOW_AVAILABLE = False

try:
    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torch.utils.data import DataLoader, TensorDataset
    TORCH_AVAILABLE = True
except ImportError:
    TORCH_AVAILABLE = False

try:
    import lightgbm as lgb
    LIGHTGBM_AVAILABLE = True
except ImportError:
    LIGHTGBM_AVAILABLE = False

try:
    import xgboost as xgb
    XGBOOST_AVAILABLE = True
except ImportError:
    XGBOOST_AVAILABLE = False

from ..core_types import WhaleBotConfig, EnhancedTrade, WhaleTier
from ..monitor import HyperliquidMonitor

logger = logging.getLogger(__name__)

@dataclass
class MLFeatureSet:
    """Feature set for ML model training and prediction"""
    features: Dict[str, float]
    target: Optional[str] = None  # "ACCUMULATE", "DISTRIBUTE", "MANIPULATE", "FOLLOW"
    timestamp: datetime = None

@dataclass
class ModelPerformance:
    """Performance metrics for ML models"""
    model_name: str
    accuracy: float
    precision: float
    recall: float
    f1_score: float
    training_samples: int
    last_trained: datetime
    feature_importance: Dict[str, float]

class WhaleBehaviorMLModel:
    """Machine Learning model for whale behavior prediction"""
    
    def __init__(self, config: WhaleBotConfig):
        self.config = config
        self.model = None
        self.scaler = StandardScaler()
        self.feature_names = []
        self.is_trained = False
        self.training_history = []
        
    def extract_features(self, trade: EnhancedTrade, market_data: Dict[str, Any]) -> Dict[str, float]:
        """Extract features for ML model"""
        features = {
            # Trade features
            'trade_size_usd': trade.size_usd,
            'trade_size_normalized': min(1.0, trade.size_usd / 10_000_000),  # Normalized to $10M
            'whale_tier_numeric': self._get_tier_numeric(trade.whale_tier),
            
            # Market features
            'price_momentum': market_data.get('price_change_1h', 0.0),
            'volume_ratio': market_data.get('current_volume', 0) / max(1, market_data.get('avg_volume', 1)),
            'volatility': market_data.get('price_volatility', 0.0),
            
            # Sentiment features
            'social_sentiment': market_data.get('social_sentiment_score', 0.0),
            'news_sentiment': market_data.get('news_sentiment_score', 0.0),
            
            # Time features
            'hour_of_day': datetime.now().hour,
            'day_of_week': datetime.now().weekday(),
            
            # Correlation features
            'btc_correlation': market_data.get('btc_correlation', 0.0),
            'market_correlation': market_data.get('market_correlation', 0.0),
        }
        
        return features
    
    def _get_tier_numeric(self, whale_tier: WhaleTier) -> float:
        """Convert whale tier to numeric value"""
        tier_mapping = {
            WhaleTier.MINNOW: 1.0,
            WhaleTier.DOLPHIN: 2.0,
            WhaleTier.WHALE: 3.0,
            WhaleTier.MEGA_WHALE: 4.0,
            WhaleTier.GIANT_WHALE: 5.0
        }
        return tier_mapping.get(whale_tier, 1.0)
    
    async def train_model(self, training_data: List[MLFeatureSet]) -> ModelPerformance:
        """Train the ML model on historical data"""
        try:
            if len(training_data) < 100:
                raise ValueError("Insufficient training data (minimum 100 samples required)")
            
            # Extract features and targets
            X_features = []
            y_targets = []
            
            for data_point in training_data:
                X_features.append(data_point.features)
                y_targets.append(data_point.target)
            
            # Convert to numpy arrays
            X = np.array([list(f.values()) for f in X_features])
            y = np.array(y_targets)
            
            # Store feature names
            self.feature_names = list(X_features[0].keys()) if X_features else []
            
            # Split data
            X_train, X_test, y_train, y_test = train_test_split(
                X, y, test_size=0.2, random_state=42, stratify=y
            )
            
            # Scale features
            X_train_scaled = self.scaler.fit_transform(X_train)
            X_test_scaled = self.scaler.transform(X_test)
            
            # Train Random Forest model
            self.model = RandomForestClassifier(
                n_estimators=100,
                max_depth=10,
                random_state=42,
                class_weight='balanced'
            )
            
            self.model.fit(X_train_scaled, y_train)
            self.is_trained = True
            
            # Evaluate model
            y_pred = self.model.predict(X_test_scaled)
            accuracy = accuracy_score(y_test, y_pred)
            
            # Calculate feature importance
            feature_importance = {}
            if hasattr(self.model, 'feature_importances_'):
                for i, importance in enumerate(self.model.feature_importances_):
                    feature_importance[self.feature_names[i]] = importance
            
            # Create performance metrics
            performance = ModelPerformance(
                model_name="WhaleBehaviorMLModel",
                accuracy=accuracy,
                precision=0.0,  # Would need to calculate per-class
                recall=0.0,     # Would need to calculate per-class
                f1_score=0.0,   # Would need to calculate per-class
                training_samples=len(training_data),
                last_trained=datetime.now(),
                feature_importance=feature_importance
            )
            
            # Store training history
            self.training_history.append(performance)
            
            logger.info(f"ML model trained successfully with {accuracy:.3f} accuracy")
            return performance
            
        except Exception as e:
            logger.error(f"Error training ML model: {e}")
            raise
    
    async def predict_behavior(self, trade: EnhancedTrade, market_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """Predict whale behavior using trained model"""
        try:
            if not self.is_trained or self.model is None:
                # Return simulated prediction for demo
                import random
                actions = ["ACCUMULATE", "DISTRIBUTE", "MANIPULATE", "FOLLOW"]
                return {
                    'predicted_action': random.choice(actions),
                    'confidence': random.uniform(0.6, 0.9),
                    'probability_distribution': {action: random.uniform(0.1, 0.4) for action in actions},
                    'reasoning': 'Simulated prediction (model not trained)',
                    'features_used': len(market_data)
                }
            
            # Extract features
            features = self.extract_features(trade, market_data)
            feature_vector = np.array([list(features.values())])
            
            # Scale features
            feature_vector_scaled = self.scaler.transform(feature_vector)
            
            # Make prediction
            predicted_action = self.model.predict(feature_vector_scaled)[0]
            prediction_proba = self.model.predict_proba(feature_vector_scaled)[0]
            
            # Create probability distribution
            classes = self.model.classes_
            proba_dict = {}
            for i, class_name in enumerate(classes):
                proba_dict[class_name] = float(prediction_proba[i])
            
            # Calculate confidence (max probability)
            confidence = float(max(prediction_proba))
            
            return {
                'predicted_action': predicted_action,
                'confidence': confidence,
                'probability_distribution': proba_dict,
                'reasoning': f'Prediction based on {len(features)} features',
                'features_used': features
            }
            
        except Exception as e:
            logger.error(f"Error in ML prediction: {e}")
            return None
    
    def save_model(self, filepath: str) -> bool:
        """Save trained model to disk"""
        try:
            model_data = {
                'model': self.model,
                'scaler': self.scaler,
                'feature_names': self.feature_names,
                'training_history': self.training_history,
                'is_trained': self.is_trained
            }
            
            with open(filepath, 'wb') as f:
                pickle.dump(model_data, f)
                
            logger.info(f"ML model saved to {filepath}")
            return True
            
        except Exception as e:
            logger.error(f"Error saving model: {e}")
            return False
    
    def load_model(self, filepath: str) -> bool:
        """Load trained model from disk"""
        try:
            with open(filepath, 'rb') as f:
                model_data = pickle.load(f)
                
            self.model = model_data['model']
            self.scaler = model_data['scaler']
            self.feature_names = model_data['feature_names']
            self.training_history = model_data['training_history']
            self.is_trained = model_data['is_trained']
            
            logger.info(f"ML model loaded from {filepath}")
            return True
            
        except Exception as e:
            logger.error(f"Error loading model: {e}")
            return False

class PricePredictionMLModel:
    """Machine Learning model for price movement prediction"""
    
    def __init__(self, config: WhaleBotConfig):
        self.config = config
        self.model = GradientBoostingClassifier(
            n_estimators=100,
            learning_rate=0.1,
            max_depth=6,
            random_state=42
        )
        self.scaler = StandardScaler()
        self.feature_names = []
        self.is_trained = False
        self.training_history = []
        
    def extract_price_features(self, price_data: Dict[str, Any]) -> Dict[str, float]:
        """Extract features for price prediction"""
        features = {
            # Price features
            'price_change_1h': price_data.get('price_change_1h', 0.0),
            'price_change_4h': price_data.get('price_change_4h', 0.0),
            'price_change_1d': price_data.get('price_change_1d', 0.0),
            'price_volatility': price_data.get('volatility_24h', 0.0),
            
            # Volume features
            'volume_change': price_data.get('volume_change_24h', 0.0),
            'volume_ratio': price_data.get('current_volume', 0) / max(1, price_data.get('avg_volume', 1)),
            
            # Whale features
            'whale_inflow': price_data.get('whale_inflow_24h', 0.0),
            'whale_outflow': price_data.get('whale_outflow_24h', 0.0),
            'large_trades_count': price_data.get('large_trades_count', 0),
            
            # Sentiment features
            'social_sentiment': price_data.get('social_sentiment', 0.0),
            'news_sentiment': price_data.get('news_sentiment', 0.0),
            'fear_greed_index': price_data.get('fear_greed_index', 50.0),
            
            # Technical features
            'rsi': price_data.get('rsi', 50.0),
            'macd_signal': price_data.get('macd_signal', 0.0),
            'bollinger_position': price_data.get('bollinger_position', 0.5),
            
            # Correlation features
            'btc_correlation': price_data.get('btc_correlation', 0.0),
            'market_correlation': price_data.get('market_correlation', 0.0),
        }
        
        return features
    
    async def predict_price_movement(self, asset: str, price_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """Predict price movement using ML model"""
        try:
            # Extract features
            features = self.extract_price_features(price_data)
            feature_vector = np.array([list(features.values())])
            
            # For demo purposes, simulate prediction if model not trained
            if not self.is_trained:
                import random
                directions = ["BULLISH", "BEARISH", "SIDEWAYS"]
                predicted_direction = random.choices(
                    directions, weights=[0.4, 0.3, 0.3]
                )[0]
                confidence = random.uniform(0.6, 0.85)
                
                return {
                    'predicted_direction': predicted_direction,
                    'confidence': confidence,
                    'price_targets': {
                        '1H': random.uniform(-0.02, 0.03),
                        '4H': random.uniform(-0.05, 0.08),
                        '1D': random.uniform(-0.10, 0.15)
                    },
                    'probability_distribution': {
                        'BULLISH': random.uniform(0.2, 0.6),
                        'BEARISH': random.uniform(0.1, 0.4),
                        'SIDEWAYS': random.uniform(0.1, 0.3)
                    },
                    'reasoning': 'Simulated prediction (model not trained)',
                    'features_used': features
                }
            
            # Scale features and make prediction
            feature_vector_scaled = self.scaler.transform(feature_vector)
            predicted_direction = self.model.predict(feature_vector_scaled)[0]
            prediction_proba = self.model.predict_proba(feature_vector_scaled)[0]
            
            # Get class probabilities
            classes = self.model.classes_
            proba_dict = {}
            for i, class_name in enumerate(classes):
                proba_dict[class_name] = float(prediction_proba[i])
            
            confidence = float(max(prediction_proba))
            
            return {
                'predicted_direction': predicted_direction,
                'confidence': confidence,
                'probability_distribution': proba_dict,
                'reasoning': f'Prediction based on {len(features)} features',
                'features_used': features
            }
            
        except Exception as e:
            logger.error(f"Error in price prediction: {e}")
            return None

class AdvancedPatternDetector:
    """Advanced pattern detection using ensemble methods"""
    
    def __init__(self, config: WhaleBotConfig):
        self.config = config
        self.pattern_models = {}
        self.detection_history = []
        
    async def detect_manipulation_patterns(self, trade_data: List[EnhancedTrade]) -> Optional[Dict[str, Any]]:
        """Detect market manipulation patterns"""
        try:
            if len(trade_data) < 5:
                return None
                
            # Analyze trading patterns
            times = [t.timestamp for t in trade_data]
            sizes = [t.size_usd for t in trade_data]
            addresses = [t.address for t in trade_data]
            
            # Check for coordinated timing (all trades within short window)
            time_diffs = [(times[i+1] - times[i]).total_seconds() for i in range(len(times)-1)]
            avg_time_diff = sum(time_diffs) / len(time_diffs) if time_diffs else 0
            
            # Check for similar trade sizes (wash trading indicator)
            size_variance = np.var(sizes) if len(sizes) > 1 else 0
            size_cv = np.sqrt(size_variance) / np.mean(sizes) if np.mean(sizes) > 0 else 0
            
            # Check for same addresses (pump groups)
            unique_addresses = set(addresses)
            address_reuse_ratio = (len(addresses) - len(unique_addresses)) / len(addresses) if addresses else 0
            
            # Determine manipulation likelihood
            manipulation_score = 0.0
            
            if avg_time_diff < 30:  # Trades within 30 seconds
                manipulation_score += 0.3
                
            if size_cv < 0.1:  # Very similar sizes
                manipulation_score += 0.4
                
            if address_reuse_ratio > 0.5:  # High address reuse
                manipulation_score += 0.3
                
            patterns_detected = []
            if manipulation_score > 0.6:
                patterns_detected.append("COORDINATED_TIMING")
            if size_cv < 0.1:
                patterns_detected.append("SIZE_MANIPULATION")
            if address_reuse_ratio > 0.5:
                patterns_detected.append("ADDRESS_CLUSTERING")
                
            return {
                'manipulation_detected': manipulation_score > 0.7,
                'manipulation_score': manipulation_score,
                'patterns_detected': patterns_detected,
                'evidence': {
                    'avg_time_diff_seconds': avg_time_diff,
                    'size_coefficient_variation': size_cv,
                    'address_reuse_ratio': address_reuse_ratio
                },
                'confidence': min(0.95, manipulation_score + 0.2)
            }
            
        except Exception as e:
            logger.error(f"Error detecting manipulation patterns: {e}")
            return None
    
    async def detect_accumulation_pattern(self, price_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """Detect whale accumulation patterns"""
        try:
            # Simulate accumulation detection
            accumulation_score = np.random.uniform(0.3, 0.9)
            
            if accumulation_score > 0.6:
                return {
                    'accumulation_detected': True,
                    'accumulation_score': accumulation_score,
                    'phase': np.random.choice(['EARLY', 'MIDDLE', 'LATE']),
                    'expected_breakout_probability': np.random.uniform(0.6, 0.9),
                    'support_level': price_data.get('current_price', 0) * 0.95,
                    'resistance_level': price_data.get('current_price', 0) * 1.15,
                    'confidence': accumulation_score
                }
            else:
                return {
                    'accumulation_detected': False,
                    'accumulation_score': accumulation_score,
                    'confidence': 1 - accumulation_score
                }
                
        except Exception as e:
            logger.error(f"Error detecting accumulation pattern: {e}")
            return None

class ModelEnsemble:
    """Ensemble of multiple ML models for robust predictions"""
    
    def __init__(self, config: WhaleBotConfig):
        self.config = config
        self.models = {
            'whale_behavior': WhaleBehaviorMLModel(config),
            'price_prediction': PricePredictionMLModel(config),
            'pattern_detector': AdvancedPatternDetector(config)
        }
        self.ensemble_weights = {
            'whale_behavior': 0.4,
            'price_prediction': 0.4,
            'pattern_detector': 0.2
        }
        
    async def ensemble_prediction(self, trade: EnhancedTrade, 
                                market_data: Dict[str, Any]) -> Dict[str, Any]:
        """Generate ensemble prediction combining multiple models"""
        try:
            predictions = {}
            
            # Get individual model predictions
            whale_pred = await self.models['whale_behavior'].predict_behavior(trade, market_data)
            price_pred = await self.models['price_prediction'].predict_price_movement(
                trade.coin, market_data
            )
            pattern_pred = await self.models['pattern_detector'].detect_accumulation_pattern(market_data)
            
            predictions['whale_behavior'] = whale_pred
            predictions['price_prediction'] = price_pred
            predictions['pattern_detection'] = pattern_pred
            
            # Calculate ensemble confidence
            confidences = []
            if whale_pred and 'confidence' in whale_pred:
                confidences.append(whale_pred['confidence'] * self.ensemble_weights['whale_behavior'])
            if price_pred and 'confidence' in price_pred:
                confidences.append(price_pred['confidence'] * self.ensemble_weights['price_prediction'])
            if pattern_pred and 'confidence' in pattern_pred:
                confidences.append(pattern_pred['confidence'] * self.ensemble_weights['pattern_detector'])
            
            ensemble_confidence = sum(confidences) if confidences else 0.0
            
            # Generate final recommendation
            recommendations = self._generate_ensemble_recommendations(predictions)
            
            return {
                'ensemble_prediction': {
                    'confidence': ensemble_confidence,
                    'primary_signal': self._get_primary_signal(predictions),
                    'model_agreement': self._calculate_model_agreement(predictions),
                    'recommendations': recommendations
                },
                'individual_predictions': predictions,
                'ensemble_weights': self.ensemble_weights,
                'timestamp': datetime.now().isoformat()
            }
            
        except Exception as e:
            logger.error(f"Error in ensemble prediction: {e}")
            return {'error': str(e)}
    
    def _generate_ensemble_recommendations(self, predictions: Dict[str, Any]) -> List[str]:
        """Generate actionable recommendations from ensemble prediction"""
        recommendations = []
        
        # Analyze whale behavior prediction
        whale_pred = predictions.get('whale_behavior')
        if whale_pred:
            action = whale_pred.get('predicted_action')
            if action == 'ACCUMULATE':
                recommendations.append("📈 Whales accumulating - monitor for breakout")
            elif action == 'DISTRIBUTE':
                recommendations.append("📉 Whales distributing - consider taking profits")
            elif action == 'MANIPULATE':
                recommendations.append("⚠️ Manipulation detected - exercise extreme caution")
        
        # Analyze price prediction
        price_pred = predictions.get('price_prediction')
        if price_pred:
            direction = price_pred.get('predicted_direction')
            if direction == 'BULLISH':
                recommendations.append("🚀 Price prediction bullish - consider long positions")
            elif direction == 'BEARISH':
                recommendations.append("🔻 Price prediction bearish - reduce exposure")
        
        # Analyze pattern detection
        pattern_pred = predictions.get('pattern_detection')
        if pattern_pred and pattern_pred.get('accumulation_detected'):
            phase = pattern_pred.get('phase', 'UNKNOWN')
            recommendations.append(f"🎯 Accumulation pattern detected in {phase} phase")
        
        return recommendations
    
    def _get_primary_signal(self, predictions: Dict[str, Any]) -> str:
        """Determine primary signal from ensemble"""
        signals = []
        
        whale_pred = predictions.get('whale_behavior')
        if whale_pred and whale_pred.get('confidence', 0) > 0.7:
            signals.append(whale_pred.get('predicted_action'))
        
        price_pred = predictions.get('price_prediction')
        if price_pred and price_pred.get('confidence', 0) > 0.7:
            signals.append(price_pred.get('predicted_direction'))
        
        if signals:
            # Return most confident signal
            return signals[0]
        else:
            return "MIXED_SIGNALS"
    
    def _calculate_model_agreement(self, predictions: Dict[str, Any]) -> float:
        """Calculate agreement between models (0.0 to 1.0)"""
        # Simple agreement calculation based on directional consistency
        bullish_signals = 0
        bearish_signals = 0
        
        whale_pred = predictions.get('whale_behavior')
        if whale_pred:
            action = whale_pred.get('predicted_action', '').upper()
            if action in ['ACCUMULATE', 'FOLLOW']:
                bullish_signals += 1
            elif action == 'DISTRIBUTE':
                bearish_signals += 1
        
        price_pred = predictions.get('price_prediction')
        if price_pred:
            direction = price_pred.get('predicted_direction', '').upper()
            if direction == 'BULLISH':
                bullish_signals += 1
            elif direction == 'BEARISH':
                bearish_signals += 1
        
        total_signals = bullish_signals + bearish_signals
        if total_signals == 0:
            return 0.5  # Neutral
        
        agreement = max(bullish_signals, bearish_signals) / total_signals
        return agreement


class AdvancedMLModels:
    """
    Advanced ML models using TensorFlow, PyTorch, LightGBM, and XGBoost
    Provides state-of-the-art machine learning capabilities for whale behavior prediction
    """
    
    def __init__(self, config: WhaleBotConfig):
        self.config = config
        self.models = {}
        self.model_performance = {}
        self.is_initialized = False
        
        # Model availability tracking
        self.available_models = {
            'tensorflow': TENSORFLOW_AVAILABLE,
            'torch': TORCH_AVAILABLE,
            'lightgbm': LIGHTGBM_AVAILABLE,
            'xgboost': XGBOOST_AVAILABLE
        }
        
        logger.info(f"AdvancedMLModels initialized. Available models: {[k for k, v in self.available_models.items() if v]}")
    
    def initialize_models(self):
        """Initialize all available ML models"""
        if self.is_initialized:
            return
        
        try:
            # Initialize TensorFlow model
            if TENSORFLOW_AVAILABLE:
                self.models['whale_behavior_tf'] = self._create_tensorflow_model()
                logger.info("TensorFlow whale behavior model initialized")
            
            # Initialize PyTorch model
            if TORCH_AVAILABLE:
                self.models['price_prediction_torch'] = self._create_pytorch_model()
                logger.info("PyTorch price prediction model initialized")
            
            # Initialize LightGBM model
            if LIGHTGBM_AVAILABLE:
                self.models['volume_anomaly_lgb'] = None  # Will be created when training
                logger.info("LightGBM volume anomaly model initialized")
            
            # Initialize XGBoost model
            if XGBOOST_AVAILABLE:
                self.models['sentiment_xgb'] = None  # Will be created when training
                logger.info("XGBoost sentiment analysis model initialized")
            
            self.is_initialized = True
            logger.info("All available ML models initialized successfully")
            
        except Exception as e:
            logger.error(f"Error initializing ML models: {e}")
            raise
    
    def _create_tensorflow_model(self) -> Any:
        """Create TensorFlow model for whale behavior prediction"""
        if not TENSORFLOW_AVAILABLE:
            raise RuntimeError("TensorFlow not available")
        
        # Import keras locally to avoid issues when TensorFlow is not available
        from tensorflow import keras
            
        model = keras.Sequential([
            keras.layers.Dense(128, activation='relu', input_shape=(20,)),
            keras.layers.Dropout(0.3),
            keras.layers.Dense(64, activation='relu'),
            keras.layers.Dropout(0.3),
            keras.layers.Dense(32, activation='relu'),
            keras.layers.Dense(4, activation='softmax')  # 4 classes: ACCUMULATE, DISTRIBUTE, MANIPULATE, FOLLOW
        ])
        
        model.compile(
            optimizer='adam',
            loss='sparse_categorical_crossentropy',
            metrics=['accuracy']
        )
        
        return model
    
    def _create_pytorch_model(self) -> "nn.Module":
        """Create PyTorch model for price prediction"""
        class PricePredictionNet(nn.Module):
            def __init__(self, input_size=15):
                super(PricePredictionNet, self).__init__()
                self.layers = nn.Sequential(
                    nn.Linear(input_size, 128),
                    nn.ReLU(),
                    nn.Dropout(0.3),
                    nn.Linear(128, 64),
                    nn.ReLU(),
                    nn.Dropout(0.3),
                    nn.Linear(64, 32),
                    nn.ReLU(),
                    nn.Linear(32, 3)  # 3 classes: BULLISH, BEARISH, NEUTRAL
                )
            
            def forward(self, x):
                return self.layers(x)
        
        return PricePredictionNet()
    
    async def train_tensorflow_model(self, features: np.ndarray, labels: np.ndarray) -> Dict[str, float]:
        """Train TensorFlow model with given data"""
        if not TENSORFLOW_AVAILABLE:
            raise RuntimeError("TensorFlow not available. Install with: pip install hyperliquid-monitor-plus[ml]")
        
        if not self.is_initialized:
            self.initialize_models()
        
        # Import keras locally
        from tensorflow import keras
        
        model = self.models['whale_behavior_tf']
        
        # Train the model
        history = model.fit(
            features, labels,
            epochs=50,
            batch_size=32,
            validation_split=0.2,
            verbose=0
        )
        
        # Calculate performance metrics
        loss, accuracy = model.evaluate(features, labels, verbose=0)
        
        self.model_performance['tensorflow'] = {
            'accuracy': float(accuracy),
            'loss': float(loss),
            'epochs': 50,
            'last_trained': datetime.now().isoformat()
        }
        
        return self.model_performance['tensorflow']
    
    async def train_pytorch_model(self, features: np.ndarray, labels: np.ndarray) -> Dict[str, float]:
        """Train PyTorch model with given data"""
        if not TORCH_AVAILABLE:
            raise RuntimeError("PyTorch not available. Install with: pip install hyperliquid-monitor-plus[ml]")
        
        if not self.is_initialized:
            self.initialize_models()
        
        model = self.models['price_prediction_torch']
        
        # Convert to PyTorch tensors
        features_tensor = torch.FloatTensor(features)
        labels_tensor = torch.LongTensor(labels)
        
        # Create data loader
        dataset = TensorDataset(features_tensor, labels_tensor)
        dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
        
        # Setup training
        criterion = nn.CrossEntropyLoss()
        optimizer = optim.Adam(model.parameters(), lr=0.001)
        
        # Training loop
        model.train()
        for epoch in range(50):
            for batch_features, batch_labels in dataloader:
                optimizer.zero_grad()
                outputs = model(batch_features)
                loss = criterion(outputs, batch_labels)
                loss.backward()
                optimizer.step()
        
        # Calculate accuracy
        model.eval()
        with torch.no_grad():
            outputs = model(features_tensor)
            _, predicted = torch.max(outputs.data, 1)
            accuracy = (predicted == labels_tensor).float().mean().item()
        
        self.model_performance['pytorch'] = {
            'accuracy': accuracy,
            'loss': float(loss.item()),
            'epochs': 50,
            'last_trained': datetime.now().isoformat()
        }
        
        return self.model_performance['pytorch']
    
    async def train_lightgbm_model(self, features: np.ndarray, labels: np.ndarray) -> Dict[str, float]:
        """Train LightGBM model"""
        if not LIGHTGBM_AVAILABLE:
            raise RuntimeError("LightGBM not available. Install with: pip install hyperliquid-monitor-plus[ml]")
        
        # Create and train LightGBM model
        train_data = lgb.Dataset(features, label=labels)
        
        params = {
            'objective': 'multiclass',
            'num_class': 4,
            'metric': 'multi_logloss',
            'boosting_type': 'gbdt',
            'num_leaves': 31,
            'learning_rate': 0.05,
            'feature_fraction': 0.9
        }
        
        model = lgb.train(params, train_data, valid_sets=[train_data])
        self.models['volume_anomaly_lgb'] = model
        
        # Calculate training accuracy
        predictions = model.predict(features)
        predicted_classes = np.argmax(predictions, axis=1)
        accuracy = np.mean(predicted_classes == labels)
        
        self.model_performance['lightgbm'] = {
            'accuracy': float(accuracy),
            'feature_importance': model.feature_importance().tolist(),
            'last_trained': datetime.now().isoformat()
        }
        
        return self.model_performance['lightgbm']
    
    async def train_xgboost_model(self, features: np.ndarray, labels: np.ndarray) -> Dict[str, float]:
        """Train XGBoost model"""
        if not XGBOOST_AVAILABLE:
            raise RuntimeError("XGBoost not available. Install with: pip install hyperliquid-monitor-plus[ml]")
        
        # Create and train XGBoost model
        dtrain = xgb.DMatrix(features, label=labels)
        
        params = {
            'objective': 'multi:softprob',
            'num_class': 3,
            'max_depth': 6,
            'learning_rate': 0.1,
            'subsample': 0.8,
            'colsample_bytree': 0.8
        }
        
        model = xgb.train(params, dtrain, num_boost_round=100)
        self.models['sentiment_xgb'] = model
        
        # Calculate training accuracy
        dtest = xgb.DMatrix(features)
        predictions = model.predict(dtest)
        predicted_classes = np.argmax(predictions, axis=1)
        accuracy = np.mean(predicted_classes == labels)
        
        self.model_performance['xgboost'] = {
            'accuracy': float(accuracy),
            'feature_importance': model.get_score().values(),
            'last_trained': datetime.now().isoformat()
        }
        
        return self.model_performance['xgboost']
    
    async def predict_with_tensorflow(self, features: np.ndarray) -> Dict[str, Any]:
        """Make prediction using TensorFlow model"""
        if not TENSORFLOW_AVAILABLE:
            return {'error': 'TensorFlow not available'}
        
        if 'whale_behavior_tf' not in self.models:
            return {'error': 'TensorFlow model not initialized'}
        
        model = self.models['whale_behavior_tf']
        predictions = model.predict(features, verbose=0)
        predicted_classes = np.argmax(predictions, axis=1)
        confidence = np.max(predictions, axis=1)
        
        actions = ['ACCUMULATE', 'DISTRIBUTE', 'MANIPULATE', 'FOLLOW']
        return {
            'predicted_action': [actions[idx] for idx in predicted_classes],
            'confidence': confidence.tolist(),
            'probabilities': predictions.tolist()
        }
    
    async def predict_with_pytorch(self, features: np.ndarray) -> Dict[str, Any]:
        """Make prediction using PyTorch model"""
        if not TORCH_AVAILABLE:
            return {'error': 'PyTorch not available'}
        
        if 'price_prediction_torch' not in self.models:
            return {'error': 'PyTorch model not initialized'}
        
        model = self.models['price_prediction_torch']
        features_tensor = torch.FloatTensor(features)
        
        model.eval()
        with torch.no_grad():
            outputs = model(features_tensor)
            probabilities = torch.softmax(outputs, dim=1)
            predicted_classes = torch.argmax(outputs, dim=1)
        
        directions = ['BULLISH', 'BEARISH', 'NEUTRAL']
        return {
            'predicted_direction': [directions[idx.item()] for idx in predicted_classes],
            'confidence': probabilities[:, 1].tolist(),  # Confidence for bullish prediction
            'probabilities': probabilities.tolist()
        }
    
    async def predict_with_lightgbm(self, features: np.ndarray) -> Dict[str, Any]:
        """Make prediction using LightGBM model"""
        if not LIGHTGBM_AVAILABLE:
            return {'error': 'LightGBM not available'}
        
        if self.models['volume_anomaly_lgb'] is None:
            return {'error': 'LightGBM model not trained'}
        
        model = self.models['volume_anomaly_lgb']
        predictions = model.predict(features)
        predicted_classes = np.argmax(predictions, axis=1)
        
        return {
            'predicted_anomaly_type': predicted_classes.tolist(),
            'confidence': np.max(predictions, axis=1).tolist(),
            'probabilities': predictions.tolist()
        }
    
    async def predict_with_xgboost(self, features: np.ndarray) -> Dict[str, Any]:
        """Make prediction using XGBoost model"""
        if not XGBOOST_AVAILABLE:
            return {'error': 'XGBoost not available'}
        
        if self.models['sentiment_xgb'] is None:
            return {'error': 'XGBoost model not trained'}
        
        model = self.models['sentiment_xgb']
        dtest = xgb.DMatrix(features)
        predictions = model.predict(dtest)
        predicted_classes = np.argmax(predictions, axis=1)
        
        sentiments = ['NEGATIVE', 'POSITIVE', 'NEUTRAL']
        return {
            'predicted_sentiment': [sentiments[idx] for idx in predicted_classes],
            'confidence': np.max(predictions, axis=1).tolist(),
            'probabilities': predictions.tolist()
        }
    
    def get_model_availability(self) -> Dict[str, bool]:
        """Get availability status of all ML models"""
        return self.available_models.copy()
    
    def get_model_performance(self) -> Dict[str, Dict[str, Any]]:
        """Get performance metrics of trained models"""
        return self.model_performance.copy()
    
    async def ensemble_prediction(self, features: np.ndarray) -> Dict[str, Any]:
        """Combine predictions from all available models"""
        predictions = {}
        weights = {}
        
        # Get predictions from each available model
        tf_pred = await self.predict_with_tensorflow(features)
        torch_pred = await self.predict_with_pytorch(features)
        lgb_pred = await self.predict_with_lightgbm(features)
        xgb_pred = await self.predict_with_xgboost(features)
        
        # Store predictions and assign weights
        if 'error' not in tf_pred:
            predictions['tensorflow'] = tf_pred
            weights['tensorflow'] = 0.3
            
        if 'error' not in torch_pred:
            predictions['pytorch'] = torch_pred
            weights['pytorch'] = 0.25
            
        if 'error' not in lgb_pred:
            predictions['lightgbm'] = lgb_pred
            weights['lightgbm'] = 0.25
            
        if 'error' not in xgb_pred:
            predictions['xgboost'] = xgb_pred
            weights['xgboost'] = 0.2
        
        # Normalize weights
        total_weight = sum(weights.values())
        if total_weight > 0:
            normalized_weights = {k: v/total_weight for k, v in weights.items()}
        else:
            normalized_weights = {}
        
        return {
            'ensemble_predictions': predictions,
            'model_weights': normalized_weights,
            'available_models': list(predictions.keys()),
            'confidence': np.mean([pred.get('confidence', [0])[0] for pred in predictions.values() if 'confidence' in pred])
        }
