# source: https://raw.githubusercontent.com/RoboticAutomations/freqtradestrategies/52c4bd40414645f079a980cf4486dc7af4061efb/dirty%20LA/from-AIRA/machine-learning/ALEXGKD_FisherTransformV61_ML.py
# directory_url: https://github.com/RoboticAutomations/freqtradestrategies/blob/main/dirty LA/from-AIRA/machine-learning/
# User: RoboticAutomations
# Repository: freqtradestrategies
# --------------------"""
# Enhanced Fisher Transform Strategy with ML/RL Integration - V1.2 BALANCED
# FIXED: Balanced long/short entries, tightened stoploss, removed restrictive Goldie Locks
# 
# Changelog v1.2 (2026-02-17):
# - FIXED: Stoploss tightened from -0.524 (52%!) to -0.15 (15%)
# - FIXED: max_open_trades reduced from 20 to 5 to limit risk
# - FIXED: Balanced ML conditions - both longs and shorts use same confidence logic
# - FIXED: Short entry removed restrictive Goldie Locks zone requirement
# - FIXED: Short entry now uses fisher < -0.5 (symmetric to long) instead of < 2.89
# - FIXED: Both directions require baseline trend in their favor
# - FIXED: Long entry threshold relaxed to 1.0 (was 0.65)
#
# Changelog v1.1 (2026-02-17):
# - FIXED: fisher_trend_long was identical to fisher_trend_short
# - FIXED: Long entry used fisher_sell_threshold instead of fisher_buy_threshold
# - FIXED: Long entry momentum check was inverted
#
# Usage:
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy GKD_FisherTransformV4_ML_V1_2_Balanced \
#     --spaces buy sell roi stoploss trailing --config user_data/config_binance_futures_backtest_usdt.json \
#     --epochs 1000 --timerange 20241001-20250501 --timeframe-detail 5m --max-open-trades 3 -timeframe 1h
"""

import datetime
import logging
import math
import os
import pickle
import warnings
from typing import Any, Dict, List, Optional, Tuple

import numpy as np
import optuna
import pandas as pd
import talib
from freqtrade.exchange import timeframe_to_prev_date
from freqtrade.persistence import Trade
from freqtrade.strategy import (BooleanParameter, CategoricalParameter,
                                DecimalParameter, IntParameter, IStrategy,
                                RealParameter, informative,
                                merge_informative_pair)
from pandas import DataFrame
from pandas_ta import ema
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler

logger = logging.getLogger(__name__)
warnings.filterwarnings('ignore')

ASCII_ART = '''
    ╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
    ║                                                                                                                        ║
    ║  ██████╗ ██╗     ███████╗██╗  ██╗ ██████╗██████╗ ██╗   ██╗██████╗ ████████╗ ██████╗ ██╗  ██╗██╗███╗   ██╗ ██████╗      ║
    ║  ██╔══██╗██║     ██╔════╝╚██╗██╔╝██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗╚══██╔══╝██╔═══██╗██║ ██╔╝██║████╗  ██║██╔════╝      ║
    ║  ███████║██║     █████╗   ╚███╔╝ ██║     ██████╔╝ ╚████╔╝ ██████╔╝   ██║   ██║   ██║█████╔╝ ██║██╔██╗ ██║██║  ███╗     ║
    ║  ██╔══██║██║     ██╔══╝   ██╔██╗ ██║     ██╔══██╗  ╚██╔╝  ██╔═══╝    ██║   ██║   ██║██╔═██╗ ██║██║╚██╗██║██║   ██║     ║
    ║  ██║  ██║███████╗███████╗██╔╝ ██╗╚██████╗██║  ██║   ██║   ██║        ██║   ╚██████╔╝██║  ██╗██║██║ ╚████║╚██████╔╝     ║
    ║  ╚═╝  ╚═╝╚══════╝╚══════╝╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝   ╚═╝   ╚═╝        ╚═╝    ╚═════╝ ╚═╝  ╚═╝╚═╝╚═╝  ╚═══╝ ╚═════╝      ║
    ║                                                                                                                        ║
    ║                                               ALEX GKD_FISHER TRANSOFRM                                                ║
    ║                                               VERSION 5                                                                ║
    ║                                               20.02.2026                                                               ║
    ║                                                                                                                        ║
    ╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
'''
def lerp(a: float, b: float, t: float) -> float:
    """Linear interpolation between a and b by factor t"""
    return a + t * (b - a)


class EnhancedLogger:
    """Enhanced logging class with emojis and better formatting"""
    
    @staticmethod
    def log_banner(message: str, emoji: str = "🚀"):
        """Log a banner message"""
        border = "═" * (len(message) + 6)
        logger.info(f"{emoji} {border}")
        logger.info(f"{emoji} ║  {message}  ║")
        logger.info(f"{emoji} {border}")
    
    @staticmethod
    def log_section(title: str, emoji: str = "📊"):
        """Log a section header"""
        logger.info(f"\n{emoji} ═══ {title} ═══")
    
    @staticmethod
    def log_subsection(title: str, emoji: str = "▶️"):
        """Log a subsection"""
        logger.info(f"{emoji} {title}")
    
    @staticmethod
    def log_parameter(name: str, value: Any, emoji: str = "⚙️"):
        """Log a parameter with formatting"""
        if isinstance(value, float):
            logger.info(f"  {emoji} {name}: {value:.4f}")
        else:
            logger.info(f"  {emoji} {name}: {value}")
    
    @staticmethod
    def log_performance(metric: str, value: float, emoji: str = "📈"):
        """Log performance metrics"""
        color_emoji = "🟢" if value > 0 else "🔴" if value < 0 else "🟡"
        logger.info(f"{emoji} {color_emoji} {metric}: {value:.4f}")
    
    @staticmethod
    def log_trade_action(action: str, pair: str, rate: float, emoji: str = "💰"):
        """Log trade actions"""
        logger.info(f"{emoji} {action} {pair} @ {rate:.6f}")
    
    @staticmethod
    def log_ml_status(message: str, confidence: float = None, emoji: str = "🤖"):
        """Log ML related messages"""
        if confidence is not None:
            conf_emoji = "🟢" if confidence > 0.7 else "🟡" if confidence > 0.5 else "🔴"
            logger.info(f"{emoji} {conf_emoji} {message} (Confidence: {confidence:.2%})")
        else:
            logger.info(f"{emoji} {message}")
    
    @staticmethod
    def log_error(message: str, emoji: str = "❌"):
        """Log error messages"""
        logger.error(f"{emoji} ERROR: {message}")
    
    @staticmethod
    def log_warning(message: str, emoji: str = "⚠️"):
        """Log warning messages"""
        logger.warning(f"{emoji} WARNING: {message}")
    
    @staticmethod
    def log_success(message: str, emoji: str = "✅"):
        """Log success messages"""
        logger.info(f"{emoji} SUCCESS: {message}")


class MLOptimizer:
    """Machine Learning optimizer for strategy parameters with enhanced logging"""
    
    def __init__(self, strategy_name: str = "GKD_FisherV5"):
        self.strategy_name = strategy_name
        self.model_path = f"user_data/strategies/ml_models/{strategy_name}_model.pkl"
        self.scaler_path = f"user_data/strategies/ml_models/{strategy_name}_scaler.pkl"
        self.study_path = f"user_data/strategies/ml_models/{strategy_name}_study.pkl"
        self.model = None
        self.scaler = None
        self.study = None
        self.performance_history = []
        
        # Ensure directory exists
        os.makedirs("user_data/strategies/ml_models", exist_ok=True)
        
        # Enhanced logging for initialization
        EnhancedLogger.log_section("ML OPTIMIZER INITIALIZATION", "🤖")
        EnhancedLogger.log_parameter("Strategy Name", strategy_name, "🏷️")
        EnhancedLogger.log_parameter("Model Path", self.model_path, "📁")
        
        # Load existing models if available
        self.load_models()
    
    def load_models(self):
        """Load existing ML models and Optuna study with enhanced logging"""
        try:
            models_loaded = 0
            
            if os.path.exists(self.model_path):
                with open(self.model_path, 'rb') as f:
                    self.model = pickle.load(f)
                models_loaded += 1
                EnhancedLogger.log_success("ML Model loaded successfully", "🧠")
            else:
                EnhancedLogger.log_warning("No existing ML model found", "🤖")
            
            if os.path.exists(self.scaler_path):
                with open(self.scaler_path, 'rb') as f:
                    self.scaler = pickle.load(f)
                models_loaded += 1
                EnhancedLogger.log_success("Feature Scaler loaded successfully", "📏")
            else:
                EnhancedLogger.log_warning("No existing scaler found", "📏")
                    
            if os.path.exists(self.study_path):
                with open(self.study_path, 'rb') as f:
                    self.study = pickle.load(f)
                models_loaded += 1
                EnhancedLogger.log_success(f"Optuna Study loaded ({len(self.study.trials)} trials)", "🔬")
            else:
                EnhancedLogger.log_warning("No existing Optuna study found", "🔬")
            
            if models_loaded > 0:
                EnhancedLogger.log_success(f"Loaded {models_loaded}/3 ML components", "✨")
            
        except Exception as e:
            EnhancedLogger.log_error(f"Error loading models: {e}")
    
    def save_models(self):
        """Save ML models and Optuna study with enhanced logging"""
        try:
            saved_models = 0
            
            if self.model:
                with open(self.model_path, 'wb') as f:
                    pickle.dump(self.model, f)
                saved_models += 1
                EnhancedLogger.log_success("ML Model saved", "💾")
            
            if self.scaler:
                with open(self.scaler_path, 'wb') as f:
                    pickle.dump(self.scaler, f)
                saved_models += 1
                EnhancedLogger.log_success("Feature Scaler saved", "💾")
                    
            if self.study:
                with open(self.study_path, 'wb') as f:
                    pickle.dump(self.study, f)
                saved_models += 1
                EnhancedLogger.log_success("Optuna Study saved", "💾")
            
            if saved_models > 0:
                EnhancedLogger.log_success(f"Saved {saved_models} ML components", "🎯")
                
        except Exception as e:
            EnhancedLogger.log_error(f"Error saving models: {e}")
    
    def create_features(self, dataframe: DataFrame) -> np.ndarray:
        """Create features for ML model with enhanced logging - FIXED FEATURE COUNT"""
        features = []
        
        try:
            EnhancedLogger.log_subsection("Creating ML Features", "🔧")
            
            # Check if required columns exist, if not calculate them
            if 'atr' not in dataframe.columns or dataframe['atr'].isna().all():
                EnhancedLogger.log_warning("ATR column missing, calculating...", "📊")
                try:
                    import talib
                    dataframe['atr'] = talib.ATR(dataframe['high'], dataframe['low'], dataframe['close'], timeperiod=14)
                except:
                    # Fallback ATR calculation
                    high_low = dataframe['high'] - dataframe['low']
                    high_close = abs(dataframe['high'] - dataframe['close'].shift())
                    low_close = abs(dataframe['low'] - dataframe['close'].shift())
                    true_range = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
                    dataframe['atr'] = true_range.rolling(window=14).mean()
                EnhancedLogger.log_success("ATR calculated successfully", "📊")
            
            # Check if fisher exists, if not create a simple version
            if 'fisher' not in dataframe.columns or dataframe['fisher'].isna().all():
                EnhancedLogger.log_warning("Fisher Transform missing, calculating...", "🎯")
                # Simple Fisher Transform calculation
                median_price = (dataframe['high'] + dataframe['low']) / 2
                period = 14
                fisher = pd.Series(0.0, index=dataframe.index)
                
                for i in range(period, len(dataframe)):
                    price_window = median_price.iloc[i-period:i]
                    price_min = price_window.min()
                    price_max = price_window.max()
                    if price_max != price_min:
                        norm = (median_price.iloc[i] - price_min) / (price_max - price_min)
                        norm = 2 * norm - 1
                        norm = max(min(norm, 0.999), -0.999)
                        fisher.iloc[i] = 0.5 * np.log((1 + norm) / (1 - norm))
                
                dataframe['fisher'] = fisher
                EnhancedLogger.log_success("Fisher Transform calculated", "🎯")
            
            # Check if baseline_diff exists
            if 'baseline_diff' not in dataframe.columns:
                EnhancedLogger.log_warning("Baseline diff missing, calculating...", "📈")
                try:
                    from pandas_ta import ema
                    dataframe['baseline'] = ema(dataframe['close'], length=14)
                    dataframe['baseline_diff'] = dataframe['baseline'].diff()
                except:
                    dataframe['baseline'] = dataframe['close'].ewm(span=14).mean()
                    dataframe['baseline_diff'] = dataframe['baseline'].diff()
                EnhancedLogger.log_success("Baseline calculated", "📈")
            
            # NOW CREATE EXACTLY 12 FEATURES (FIXED COUNT)
            feature_names = []
            
            # 1. Market volatility features (2 features)
            atr_mean = dataframe['atr'].rolling(14).mean().iloc[-1]
            features.append(atr_mean if not pd.isna(atr_mean) else 0.01)
            feature_names.append("ATR_mean")
            
            atr_std = dataframe['atr'].rolling(7).std().iloc[-1]
            features.append(atr_std if not pd.isna(atr_std) else 0.001)
            feature_names.append("ATR_std")
            
            # 2. Price momentum features (3 features)
            for period in [5, 10, 20]:
                pct_change = dataframe['close'].pct_change(period).iloc[-1]
                features.append(pct_change if not pd.isna(pct_change) else 0.0)
                feature_names.append(f"momentum_{period}")
            
            # 3. Volume features (2 features)
            if 'volume' in dataframe.columns and not dataframe['volume'].isna().all():
                vol_mean = dataframe['volume'].rolling(14).mean().iloc[-1]
                features.append(vol_mean if not pd.isna(vol_mean) else 1000.0)
                feature_names.append("volume_mean")
                
                vol_pct = dataframe['volume'].pct_change().iloc[-1]
                features.append(vol_pct if not pd.isna(vol_pct) else 0.0)
                feature_names.append("volume_change")
                
                EnhancedLogger.log_success("Volume features added", "📊")
            else:
                features.extend([1000.0, 0.0])
                feature_names.extend(["volume_mean_default", "volume_change_default"])
                EnhancedLogger.log_warning("Using default volume features", "📊")
            
            # 4. Fisher transform features (3 features)
            fisher_current = dataframe['fisher'].iloc[-1]
            features.append(fisher_current if not pd.isna(fisher_current) else 0.0)
            feature_names.append("fisher_current")
            
            fisher_mean = dataframe['fisher'].rolling(5).mean().iloc[-1]
            features.append(fisher_mean if not pd.isna(fisher_mean) else 0.0)
            feature_names.append("fisher_mean")
            
            fisher_std = dataframe['fisher'].rolling(5).std().iloc[-1]
            features.append(fisher_std if not pd.isna(fisher_std) else 1.0)
            feature_names.append("fisher_std")
            
            # 5. Baseline trend features (1 feature - COMBINED TO SAVE SPACE)
            baseline_diff_mean = dataframe['baseline_diff'].rolling(5).mean().iloc[-1]
            baseline_diff_sum = dataframe['baseline_diff'].rolling(10).sum().iloc[-1]
            
            # COMBINE baseline features into one normalized feature
            if not pd.isna(baseline_diff_mean) and not pd.isna(baseline_diff_sum):
                combined_baseline = (baseline_diff_mean + baseline_diff_sum * 0.1)  # Weighted combination
            else:
                combined_baseline = 0.0
            
            features.append(combined_baseline)
            feature_names.append("baseline_combined")
            
            # 6. Market regime features (1 feature)
            sma_50 = dataframe['close'].rolling(50).mean().iloc[-1]
            sma_200 = dataframe['close'].rolling(200).mean().iloc[-1]
            
            if not pd.isna(sma_50) and not pd.isna(sma_200) and sma_200 != 0:
                regime_feature = 1.0 if sma_50 > sma_200 else 0.0
                regime_status = "BULL 🐂" if sma_50 > sma_200 else "BEAR 🐻"
            else:
                regime_feature = 0.5
                regime_status = "NEUTRAL ⚖️"
            
            features.append(regime_feature)
            feature_names.append("market_regime")
            
            # VERIFY EXACTLY 12 FEATURES
            if len(features) != 12:
                EnhancedLogger.log_error(f"Feature count error: Expected 12, got {len(features)}", "❌")
                # Force exactly 12 features
                if len(features) > 12:
                    features = features[:12]
                    feature_names = feature_names[:12]
                    EnhancedLogger.log_warning("Trimmed features to 12", "✂️")
                else:
                    while len(features) < 12:
                        features.append(0.0)
                        feature_names.append(f"padding_{len(features)}")
                    EnhancedLogger.log_warning("Padded features to 12", "📋")
            
            EnhancedLogger.log_success(f"Created exactly {len(features)} ML features", "✨")
            EnhancedLogger.log_parameter("Market Regime", regime_status, "🏛️")
            
            # Debug log feature names (optional)
            if len(features) == 12:
                EnhancedLogger.log_success("Feature count verified: 12/12", "✅")
            else:
                EnhancedLogger.log_error(f"Feature count still wrong: {len(features)}/12", "❌")
            
            return np.array(features).reshape(1, -1)
            
        except Exception as e:
            EnhancedLogger.log_error(f"Error in create_features: {e}")
            # Return exactly 12 zero features as fallback
            return np.zeros((1, 12))
    
    def optimize_parameters(self, dataframe: DataFrame, current_performance: float):
        """Use Optuna to optimize strategy parameters with enhanced logging"""
        
        EnhancedLogger.log_banner("STARTING OPTUNA OPTIMIZATION", "🔬")
        EnhancedLogger.log_performance("Current Performance", current_performance, "📊")
        
        def objective(trial):
            # Entry parameters
            fisher_period = trial.suggest_int('fisher_period', 10, 15)
            fisher_smooth_long = trial.suggest_int('fisher_smooth_long', 3, 10)
            fisher_smooth_short = trial.suggest_int('fisher_smooth_short', 3, 10)
            fisher_buy_threshold = trial.suggest_float('fisher_buy_threshold', -1.0, 2.5)
            baseline_period = trial.suggest_int('baseline_period', 5, 21)
            atr_period = trial.suggest_int('atr_period', 7, 21)
            goldie_locks = trial.suggest_float('goldie_locks', 1.5, 3.0)
            
            # EXIT PARAMETERS
            fisher_long_exit = trial.suggest_float('fisher_long_exit', -1.0, 1.0)
            fisher_short_exit = trial.suggest_float('fisher_short_exit', -1.0, 1.0)
            fisher_sell_threshold = trial.suggest_float('fisher_sell_threshold', 2.0, 3.9)
            
            # Risk management parameters
            atr_sl_long_multip = trial.suggest_float('atr_sl_long_multip', 1.0, 6.0)
            atr_sl_short_multip = trial.suggest_float('atr_sl_short_multip', 1.0, 6.0)
            rr_long = trial.suggest_float('rr_long', 1.0, 4.0)
            rr_short = trial.suggest_float('rr_short', 1.0, 4.0)
            
            complete_params = {
                'fisher_period': fisher_period,
                'fisher_smooth_long': fisher_smooth_long,
                'fisher_smooth_short': fisher_smooth_short,
                'fisher_buy_threshold': fisher_buy_threshold,
                'baseline_period': baseline_period,
                'atr_period': atr_period,
                'goldie_locks': goldie_locks,
                'fisher_long_exit': fisher_long_exit,
                'fisher_short_exit': fisher_short_exit,
                'fisher_sell_threshold': fisher_sell_threshold,
                'atr_sl_long_multip': atr_sl_long_multip,
                'atr_sl_short_multip': atr_sl_short_multip,
                'rr_long': rr_long,
                'rr_short': rr_short
            }
            
            # Log trial progress
            if len(self.study.trials) % 5 == 0:
                trial_num = len(self.study.trials) + 1
                EnhancedLogger.log_subsection(f"Trial #{trial_num}", "🧪")
                EnhancedLogger.log_parameter("Long Exit", f"{fisher_long_exit:.3f}", "📤")
                EnhancedLogger.log_parameter("Short Exit", f"{fisher_short_exit:.3f}", "📤")
            
            score = self.simulate_performance(dataframe, complete_params)
            return score
        
        # Create or load study
        if self.study is None:
            self.study = optuna.create_study(direction='maximize')
            EnhancedLogger.log_success("New Optuna study created", "🔬")
        
        # Optimize for a few trials
        start_time = datetime.datetime.now()
        self.study.optimize(objective, n_trials=10, timeout=30)
        optimization_time = (datetime.datetime.now() - start_time).total_seconds()
        
        EnhancedLogger.log_banner("OPTIMIZATION COMPLETED", "🎯")
        EnhancedLogger.log_performance("Best Score", self.study.best_value, "🏆")
        EnhancedLogger.log_parameter("Optimization Time", f"{optimization_time:.1f}s", "⏱️")
        EnhancedLogger.log_parameter("Total Trials", len(self.study.trials), "🔢")
        
        # Get best parameters
        best_params = self.study.best_params.copy()
        
        # Verify all exit parameters are present
        required_exit_params = ['fisher_long_exit', 'fisher_short_exit', 'fisher_sell_threshold']
        missing_params = []
        for param in required_exit_params:
            if param not in best_params:
                missing_params.append(param)
                if param == 'fisher_long_exit':
                    best_params[param] = -0.5
                elif param == 'fisher_short_exit':
                    best_params[param] = 0.5
                elif param == 'fisher_sell_threshold':
                    best_params[param] = 2.5
        
        if missing_params:
            EnhancedLogger.log_warning(f"Added defaults for missing params: {missing_params}", "🔧")
        
        EnhancedLogger.log_success(f"Parameters verified: {len(best_params)} total", "✅")
        
        # Save the study
        self.save_models()
        
        return best_params
    def simulate_performance(self, dataframe: DataFrame, params: dict) -> float:
        """Simulate strategy performance with enhanced logging - FIXED"""
        try:
            recent_data = dataframe.tail(50)
            fisher = self.calculate_fisher_simple(recent_data, params['fisher_period'])
            baseline = ema(recent_data['close'], length=params['baseline_period'])
            
            buy_signals = (fisher > params['fisher_buy_threshold']).astype(int)
            
            exit_signals = pd.Series(0, index=recent_data.index)
            if 'fisher_long_exit' in params:
                exit_signals = (fisher < params['fisher_long_exit']).astype(int)
            
            returns = recent_data['close'].pct_change().shift(-1)
            position = 0
            strategy_returns = []
            
            for i in range(len(recent_data)):
                if buy_signals.iloc[i] == 1 and position == 0:
                    position = 1
                elif exit_signals.iloc[i] == 1 and position == 1:
                    position = 0
                
                return_val = returns.iloc[i] if not pd.isna(returns.iloc[i]) else 0
                strategy_returns.append(position * return_val)
            
            # FIX: Ensure we return a scalar float
            performance = sum(strategy_returns)
            if hasattr(performance, 'item'):  # numpy scalar
                performance = performance.item()
            
            return float(performance)
            
        except Exception as e:
            EnhancedLogger.log_error(f"Performance simulation error: {e}")
            return -1.0

    def calculate_fisher_simple(self, dataframe: DataFrame, period: int) -> pd.Series:
        """Simplified Fisher Transform calculation"""
        median_price = (dataframe['high'] + dataframe['low']) / 2
        fisher = pd.Series(0.0, index=dataframe.index)
        
        for i in range(period, len(dataframe)):
            price_window = median_price.iloc[i-period:i]
            price_min = price_window.min()
            price_max = price_window.max()
            if price_max != price_min:
                norm = (median_price.iloc[i] - price_min) / (price_max - price_min)
                norm = 2 * norm - 1
                norm = max(min(norm, 0.999), -0.999)
                fisher.iloc[i] = 0.5 * np.log((1 + norm) / (1 - norm))
        
        return fisher
    
    def predict_optimal_params(self, dataframe: DataFrame) -> dict:
        """Predict optimal parameters using ML model with enhanced logging - COMPLETE VERSION"""
        if self.model is None or self.scaler is None:
            EnhancedLogger.log_warning("ML model or scaler not available", "🤖")
            return {}
        
        try:
            EnhancedLogger.log_subsection("ML Parameter Prediction", "🔮")
            
            features = self.create_features(dataframe)
            features_scaled = self.scaler.transform(features)
            
            predictions = self.model.predict(features_scaled)
            
            # Fix: Ensure predictions is a 2D array and handle single prediction
            if predictions.ndim == 1:
                predictions = predictions.reshape(1, -1)
            elif predictions.ndim == 0:
                predictions = np.array([[predictions]])
            
            # EXPANDED: Predict ALL required parameters instead of just 6
            param_names = [
                'fisher_period', 'fisher_smooth_long', 'fisher_smooth_short',
                'baseline_period', 'atr_period', 'goldie_locks',
                'fisher_buy_threshold', 'fisher_sell_threshold',
                'fisher_long_exit', 'fisher_short_exit',
                'atr_sl_long_multip', 'atr_sl_short_multip',
                'rr_long', 'rr_short'
            ]
            
            param_dict = {}
            
            # Handle case where model only predicts 6 values but we need 14
            if len(predictions) > 0 and len(predictions[0]) > 0:
                model_predictions = predictions[0]
                
                for i, name in enumerate(param_names):
                    if i < len(model_predictions):
                        # Direct ML prediction available
                        param_value = model_predictions[i]
                        if hasattr(param_value, 'item'):
                            param_value = param_value.item()
                        param_dict[name] = max(0.1, float(param_value))
                    else:
                        # Generate derived/interpolated values for missing parameters
                        param_dict[name] = self._generate_derived_parameter(name, param_dict)
            
            if param_dict:
                EnhancedLogger.log_success(f"ML predicted {len(param_dict)} parameters", "🔮")
                # Log which were direct predictions vs derived
                direct_count = min(len(param_names), len(predictions[0]) if len(predictions) > 0 else 0)
                derived_count = len(param_dict) - direct_count
                if derived_count > 0:
                    EnhancedLogger.log_warning(f"Derived {derived_count} parameters from ML base", "🔄")
            else:
                EnhancedLogger.log_warning("ML prediction returned empty results", "⚠️")
            
            return param_dict
            
        except Exception as e:
            EnhancedLogger.log_error(f"ML prediction error: {e}")
            return {}
    def _generate_derived_parameter(self, param_name: str, existing_params: dict) -> float:
        """Generate derived parameters based on existing ML predictions"""
        
        # Use intelligent defaults based on parameter relationships
        if param_name == 'fisher_smooth_short':
            # Base on fisher_smooth_long if available
            if 'fisher_smooth_long' in existing_params:
                return max(3, min(10, existing_params['fisher_smooth_long'] - 1))
            return 6.0
        
        elif param_name == 'fisher_buy_threshold':
            # Typically opposite sign of fisher_long_exit
            if 'fisher_long_exit' in existing_params:
                return abs(existing_params['fisher_long_exit']) + 1.0
            return 1.5
        
        elif param_name == 'fisher_sell_threshold':
            # Usually higher than buy threshold
            if 'fisher_buy_threshold' in existing_params:
                return existing_params['fisher_buy_threshold'] + 1.0
            return 2.8
        
        elif param_name == 'goldie_locks':
            # Related to ATR period
            if 'atr_period' in existing_params:
                return 1.5 + (existing_params['atr_period'] - 14) * 0.1
            return 2.0
        
        elif param_name == 'atr_sl_long_multip':
            return 2.5  # Conservative default
        
        elif param_name == 'atr_sl_short_multip':
            return 2.5  # Conservative default
        
        elif param_name == 'rr_long':
            return 3.0  # Good risk/reward ratio
        
        elif param_name == 'rr_short':
            return 3.0  # Good risk/reward ratio
        
        else:
            # Fallback for any other parameters
            return 1.0
    def update_model(self, dataframe: DataFrame, performance: float):
        """Update ML model with enhanced logging - FIXED"""
        try:
            EnhancedLogger.log_section("ML MODEL UPDATE", "🧠")
            
            features = self.create_features(dataframe)
            
            # FIX: Ensure features is properly flattened
            if features.ndim > 1:
                features_flat = features.flatten()
            else:
                features_flat = features
            
            self.performance_history.append({
                'features': features_flat,
                'performance': float(performance),  # Ensure scalar
                'timestamp': datetime.datetime.now()
            })
            
            # Keep only recent history
            if len(self.performance_history) > 100:
                self.performance_history = self.performance_history[-100:]
                EnhancedLogger.log_warning("Trimmed history to last 100 samples", "📊")
            
            # Train model if we have enough data
            if len(self.performance_history) >= 20:
                X = np.array([h['features'] for h in self.performance_history])
                y = np.array([h['performance'] for h in self.performance_history])
                
                # FIX: Ensure proper array shapes
                if X.ndim == 1:
                    X = X.reshape(1, -1)
                if y.ndim > 1:
                    y = y.flatten()
                
                # Scale features
                if self.scaler is None:
                    self.scaler = StandardScaler()
                    EnhancedLogger.log_success("Created new feature scaler", "📏")
                    
                X_scaled = self.scaler.fit_transform(X)
                
                # Train model
                if self.model is None:
                    self.model = RandomForestRegressor(n_estimators=50, random_state=42)
                    EnhancedLogger.log_success("Created new RandomForest model", "🌲")
                
                self.model.fit(X_scaled, y)
                model_score = self.model.score(X_scaled, y)
                
                EnhancedLogger.log_success(f"ML Model updated with {len(self.performance_history)} samples", "🧠")
                EnhancedLogger.log_performance("Model R² Score", model_score, "📊")
                
                # Feature importance analysis
                if hasattr(self.model, 'feature_importances_'):
                    top_features = np.argsort(self.model.feature_importances_)[-3:]
                    EnhancedLogger.log_subsection("Top 3 Feature Importance", "🔍")
                    for i, feat_idx in enumerate(reversed(top_features)):
                        importance = self.model.feature_importances_[feat_idx]
                        EnhancedLogger.log_parameter(f"Feature #{feat_idx}", f"{importance:.3f}", "⭐")
                
                self.save_models()
            else:
                samples_needed = 20 - len(self.performance_history)
                EnhancedLogger.log_warning(f"Need {samples_needed} more samples to train model", "📊")
                
        except Exception as e:
            EnhancedLogger.log_error(f"Model update error: {e}")


class Github_RoboticAutomations_freqtradestrategies__ALEXGKD_FisherTransformV61_ML__20260915_233254(IStrategy):
    # Strategy parameters
    timeframe = "1h"
    startup_candle_count = 200
    minimal_roi = {}
    stoploss = -0.15
    use_custom_stoploss = True
    trailing_stop = False
    trailing_stop_positive = 0.02
    trailing_stop_positive_offset = 0.03
    
    can_short = True
    set_leverage = 1.0
    max_dca_multiplier = 1.0  # 1.0 = same as initial, 1.5 = 50% more, 2.0 = double
    
    def __init__(self, config: dict = None):
        super().__init__(config)
        self.ml_optimizers = {}
        self.last_ml_update = None
        self.ml_update_frequency = 24
        self.trade_performance_cache = {}
        self.enable_ml_optimization = True
        self.initial_training_completed = {}  # Track per-pair training status
        
        # 🚀 STARTUP TRAINING CONFIGURATION
        self.startup_training_enabled = True
        self.startup_training_periods = 1000  # Use last 1000 candles for training
        self.startup_training_pairs = []  # Will be populated with active pairs
        
        logger.info("🤖 Fisher Transform ML Strategy v4 - Enhanced Startup Training")
        logger.info("🎯 Startup training will begin immediately upon first data analysis")
        # Enhanced initialization logging
        EnhancedLogger.log_banner("FISHER TRANSFORM ML STRATEGY INITIALIZED", "🚀")
        EnhancedLogger.log_parameter("Timeframe", self.timeframe, "⏰")
        EnhancedLogger.log_parameter("Can Short", self.can_short, "📊")
        EnhancedLogger.log_parameter("Leverage", self.set_leverage, "⚖️")
        EnhancedLogger.log_parameter("ML Optimization", self.enable_ml_optimization, "🤖")

    # Hyperparameters with ML integration
    if can_short:
        buy_params = {
            "atr_period": 20, "baseline_period": 5, "fisher_buy_threshold": 1.0,  # V1.2: Relaxed from 2.39 to 1.0
            "fisher_period": 14, "fisher_smooth_long": 9, "fisher_smooth_short": 9,
            "goldie_locks": 2.85,
        }
        sell_params = {
            "fisher_long_exit": -0.5, "fisher_short_exit": 0.5, "fisher_sell_threshold": -0.5,  # V1.2: Symmetric thresholds
        }
        minimal_roi = {"0": 0.373, "1019": 0.22, "3124": 0.076, "4482": 0}
        stoploss = -0.10  # V1.2: CRITICAL FIX - Tightened from -0.524 (52%!) to -0.15 (15%)
        trailing_stop = True  # V1.2: Enable trailing stop
        trailing_stop_positive = 0.02
        trailing_stop_positive_offset = 0.05
        trailing_only_offset_is_reached = True
        max_open_trades = 5  # V1.2: Reduced from 20 to limit risk
    else:
        buy_params = {
            "atr_period": 21, "baseline_period": 11, "fisher_buy_threshold": 0.65,
            "fisher_period": 13, "fisher_smooth_long": 7, "goldie_locks": 1.6,
            "fisher_smooth_short": 6,
        }
        sell_params = {
            "fisher_long_exit": 0.837, "fisher_sell_threshold": 2.89, "fisher_short_exit": 0.293,
        }
        minimal_roi = {"0": 0.871, "1787": 0.323, "2415": 0.118, "5669": 0}
        stoploss = -0.10
        trailing_stop = False
        trailing_stop_positive = 0.345
        trailing_stop_positive_offset = 0.373
        trailing_only_offset_is_reached = True
        max_open_trades = 3

    # ML-enhanced parameters with dynamic optimization
    fisher_period = IntParameter(10, 15, default=buy_params.get('fisher_period'), space="buy", optimize=True)
    fisher_smooth_long = IntParameter(3, 10, default=buy_params.get('fisher_smooth_long'), space="buy", optimize=True)
    fisher_smooth_short = IntParameter(3, 10, default=buy_params.get('fisher_smooth_short'), space="buy", optimize=can_short)
    fisher_short_exit = DecimalParameter(-1.0, 1.0, default=sell_params.get('fisher_short_exit'), decimals=3, space="sell", optimize=can_short)
    fisher_long_exit = DecimalParameter(-1.0, 1.0, default=sell_params.get('fisher_long_exit'), decimals=3, space="sell", optimize=True)
    fisher_sell_threshold = DecimalParameter(-2.0, -0.5, default=-1.0, decimals=2, space="sell", optimize=True)  # V1.2: Changed to negative for symmetry
    fisher_buy_threshold = DecimalParameter(0.5, 2.0, default=1.0, decimals=2, space="buy", optimize=True)  # V1.2: Positive for long entries
    baseline_period = IntParameter(5, 21, default=buy_params.get('baseline_period'), space="buy", optimize=True)
    atr_period = IntParameter(7, 21, default=buy_params.get('atr_period'), space="buy", optimize=True)
    goldie_locks = DecimalParameter(1.5, 3.0, default=buy_params.get('goldie_locks'), decimals=2, space="buy", optimize=True)
    
    # ML confidence parameters
    ml_confidence_threshold = DecimalParameter(0.3, 0.5, default=0.4, decimals=2, space="buy", optimize=True)
    ml_adaptation_rate = DecimalParameter(0.1, 0.5, default=0.2, decimals=2, space="buy", optimize=True)
    ml_signal_threshold = DecimalParameter(0.1, 0.8, default=0.4, decimals=2, space="buy", optimize=True)  # Add this line

    # Risk management with ML
    ATR_SL_short_Multip = DecimalParameter(1.0, 6.0, decimals=1, default=1.5, space="sell", optimize=True)
    ATR_SL_long_Multip = DecimalParameter(1.0, 6.0, decimals=1, default=1.5, space="sell", optimize=True)
    ATR_Multip = DecimalParameter(1.0, 6.0, decimals=1, default=1.5, space="sell", optimize=True)
    rr_long = DecimalParameter(1.0, 4.0, decimals=1, default=2.0, space="sell", optimize=True)
    rr_short = DecimalParameter(1.0, 4.0, decimals=1, default=2.0, space="sell", optimize=True)
    
    # DCA Configuration
    overbuy_factor = 1.295
    position_adjustment_enable = False
    initial_safety_order_trigger = -0.02
    max_so_multiplier_orig = 3
    safety_order_step_scale = 2
    safety_order_volume_scale = 1.8
    max_so_multiplier = max_so_multiplier_orig
    cust_proposed_initial_stakes = {}
    partial_fill_compensation_scale = 1
    
    # DCA calculation
    if max_so_multiplier_orig > 0:
        if safety_order_volume_scale > 1:
            firstLine = safety_order_volume_scale * (math.pow(safety_order_volume_scale, (max_so_multiplier_orig - 1)) - 1)
            divisor = safety_order_volume_scale - 1
            max_so_multiplier = 2 + firstLine / divisor
        elif safety_order_volume_scale < 1:
            firstLine = safety_order_volume_scale * (1 - math.pow(safety_order_volume_scale, (max_so_multiplier_orig - 1)))
            divisor = 1 - safety_order_volume_scale
            max_so_multiplier = 2 + firstLine / divisor
    
    #stoploss = -1
    
    def get_ml_adjusted_params(self, dataframe: DataFrame, pair: str) -> dict:
        """Get ML-adjusted parameters based on market conditions per pair - ENHANCED ERROR HANDLING"""
        try:
            EnhancedLogger.log_section(f"ML PARAMETER ADJUSTMENT - {pair}", "🤖")
            
            # Skip ML if disabled or insufficient data
            if not self.enable_ml_optimization or len(dataframe) < 50:
                EnhancedLogger.log_warning(f"ML optimization skipped for {pair} (disabled or insufficient data)", "⚠️")
                return {}
            
            # Ensure required columns exist before ML operations
            required_columns = ['close', 'high', 'low']
            if not all(col in dataframe.columns for col in required_columns):
                EnhancedLogger.log_error(f"Missing required columns for {pair}", "❌")
                return {}
            
            # Create pair-specific optimizer if doesn't exist
            if pair not in self.ml_optimizers:
                self.ml_optimizers[pair] = MLOptimizer(f"GKD_FisherV5_{pair.replace('/', '_').replace(':', '_')}")
                EnhancedLogger.log_success(f"Created ML optimizer for {pair}", "🆕")
            
            ml_optimizer = self.ml_optimizers[pair]
            
            # Check if it's time to update ML model for this pair
            current_time = datetime.datetime.now()
            should_update = (self.last_ml_update is None or 
                            (current_time - self.last_ml_update).total_seconds() > self.ml_update_frequency * 3600)
            
            if should_update and len(dataframe) > 100:
                try:
                    EnhancedLogger.log_subsection(f"Updating ML model for {pair}", "🔄")
                    
                    # Update ML model with recent performance for this pair
                    recent_performance = self.calculate_recent_performance(pair)
                    EnhancedLogger.log_performance("Recent Performance", recent_performance, "📊")
                    
                    ml_optimizer.update_model(dataframe, recent_performance)
                    self.last_ml_update = current_time
                    
                    EnhancedLogger.log_success(f"ML model updated at {current_time.strftime('%H:%M:%S')}", "✅")
                    
                    # Optimize parameters with Optuna for this specific pair
                    optimized_params = ml_optimizer.optimize_parameters(dataframe, recent_performance)
                    
                    if optimized_params:
                        # Ensure all exit parameters are present in optimized results
                        self._ensure_all_parameters(optimized_params, pair)
                        
                        # Add optimization score
                        optimized_params['score'] = ml_optimizer.study.best_value if ml_optimizer.study else 0.0
                        
                        EnhancedLogger.log_success(f"Optuna returned {len(optimized_params)} parameters", "🎯")
                        
                        # Log the optimized parameters with enhanced formatting
                        self.log_formatted_parameters(pair, optimized_params)
                        
                        return optimized_params
                    else:
                        EnhancedLogger.log_warning(f"Optuna optimization returned empty results for {pair}", "⚠️")
                
                except Exception as e:
                    EnhancedLogger.log_error(f"ML optimization error for {pair}: {str(e)}", "❌")
            
            # Get ML predictions for optimal parameters for this pair (fallback)
            try:
                EnhancedLogger.log_subsection(f"Getting ML predictions for {pair}", "🔮")
                ml_params = ml_optimizer.predict_optimal_params(dataframe)
                
                # ALWAYS ensure exit parameters are included (critical fix)
                self._ensure_all_parameters(ml_params, pair)
                
                if ml_params:
                    EnhancedLogger.log_success(f"Using ML predicted parameters: {len(ml_params)} total", "✨")
                
                return ml_params
                
            except Exception as e:
                EnhancedLogger.log_error(f"ML prediction error for {pair}: {str(e)}", "❌")
                return self._get_default_parameters()
                
        except Exception as e:
            EnhancedLogger.log_error(f"ML adjustment error for {pair}: {str(e)}", "💥")
            return self._get_default_parameters()

# PART 3 - Continuing from Part 2

    def _get_default_parameters(self) -> dict:
        """Return default parameters as fallback with enhanced logging"""
        EnhancedLogger.log_warning("Using default parameters as fallback", "🔄")
        return {
            'fisher_long_exit': self.fisher_long_exit.value,
            'fisher_short_exit': self.fisher_short_exit.value,
            'fisher_sell_threshold': self.fisher_sell_threshold.value,
            'atr_sl_long_multip': self.ATR_SL_long_Multip.value,
            'atr_sl_short_multip': self.ATR_SL_short_Multip.value,
            'rr_long': self.rr_long.value,
            'rr_short': self.rr_short.value
        }
    
    def _ensure_all_parameters(self, params: dict, pair: str) -> None:
        """Ensure all required parameters are present - COMPREHENSIVE VERSION"""
        required_params = {
            # Fisher Transform Parameters
            'fisher_period': 14,
            'fisher_smooth_long': 7,
            'fisher_smooth_short': 6,
            'fisher_buy_threshold': 1.5,
            'fisher_sell_threshold': 2.8,
            'fisher_long_exit': -0.5,
            'fisher_short_exit': 0.5,
            
            # Baseline & ATR Parameters  
            'baseline_period': 14,
            'atr_period': 14,
            'goldie_locks': 2.0,
            
            # Risk Management Parameters
            'atr_sl_long_multip': 2.5,
            'atr_sl_short_multip': 2.5,
            'rr_long': 3.0,
            'rr_short': 3.0
        }
        
        missing_count = 0
        added_params = []
        
        for param, default_value in required_params.items():
            if param not in params:
                params[param] = default_value
                missing_count += 1
                added_params.append(param)
        
        if missing_count > 0:
            EnhancedLogger.log_warning(f"Added {missing_count} missing parameters for {pair}", "🔧")
            # Log which parameters were added (for debugging)
            EnhancedLogger.log_parameter("Added Parameters", ", ".join(added_params[:3]) + "..." if len(added_params) > 3 else ", ".join(added_params), "📋")
        else:
            EnhancedLogger.log_success(f"All {len(required_params)} parameters verified for {pair}", "✅")
    
    def calculate_recent_performance(self, pair: str = None) -> float:
        """Calculate recent strategy performance for specific pair or overall with enhanced logging"""
        try:
            if pair:
                pair_trades = [perf for p, perf in self.trade_performance_cache.items() if p == pair]
                if not pair_trades:
                    EnhancedLogger.log_warning(f"No trade history for {pair}", "📊")
                    return 0.0
                performance = sum(pair_trades[-5:]) / len(pair_trades[-5:])
                EnhancedLogger.log_performance(f"Recent Performance ({pair})", performance, "🎯")
                return performance
            else:
                if not self.trade_performance_cache:
                    EnhancedLogger.log_warning("No trade history available", "📊")
                    return 0.0
                recent_trades = list(self.trade_performance_cache.values())[-10:]
                performance = sum(recent_trades) / len(recent_trades)
                EnhancedLogger.log_performance("Overall Recent Performance", performance, "🌟")
                return performance
        except Exception as e:
            EnhancedLogger.log_error(f"Performance calculation error: {e}", "💥")
            return 0.0
    
    def log_formatted_parameters(self, pair: str, params: Dict[str, Any]):
        """Log parameters in a beautifully formatted way with emojis and enhanced visuals"""
        EnhancedLogger.log_banner(f"OPTIMIZED PARAMETERS FOR {pair}", "🎯")
        
        # Fisher Transform Parameters Section
        EnhancedLogger.log_section("FISHER TRANSFORM SETTINGS", "🎣")
        fisher_params = {
            "fisher_period": ("🔄", "Period"),
            "fisher_smooth_long": ("📈", "Long Smooth"),
            "fisher_smooth_short": ("📉", "Short Smooth"), 
            "fisher_buy_threshold": ("🚀", "Buy Threshold")
        }
        
        for param, (emoji, name) in fisher_params.items():
            if param in params:
                EnhancedLogger.log_parameter(name, params[param], emoji)
        
        # Fisher Exit Parameters Section  
        EnhancedLogger.log_section("FISHER EXIT SETTINGS", "🚪")
        exit_params = {
            "fisher_long_exit": ("📤", "Long Exit"),
            "fisher_short_exit": ("📥", "Short Exit"),
            "fisher_sell_threshold": ("🛑", "Sell Threshold")
        }
        
        for param, (emoji, name) in exit_params.items():
            if param in params:
                value = params[param]
                # Color coding for exit levels
                if isinstance(value, (int, float)):
                    if value > 0:
                        color_status = "🟢 POSITIVE"
                    elif value < 0:
                        color_status = "🔴 NEGATIVE" 
                    else:
                        color_status = "🟡 NEUTRAL"
                    EnhancedLogger.log_parameter(f"{name} {color_status}", f"{value:.3f}", emoji)
                else:
                    EnhancedLogger.log_parameter(name, value, emoji)
        
        # Baseline & Volatility Section
        EnhancedLogger.log_section("BASELINE & VOLATILITY", "📊")
        baseline_params = {
            "baseline_period": ("📏", "Baseline Period"),
            "atr_period": ("🌊", "ATR Period"),
            "goldie_locks": ("🔒", "Goldie Locks Zone")
        }
        
        for param, (emoji, name) in baseline_params.items():
            if param in params:
                EnhancedLogger.log_parameter(name, params[param], emoji)
        
        # Risk Management Section
        EnhancedLogger.log_section("RISK MANAGEMENT", "⚖️")
        risk_params = {
            "atr_sl_long_multip": ("🛡️", "Long SL Multiplier"),
            "atr_sl_short_multip": ("🛡️", "Short SL Multiplier"),
            "rr_long": ("💰", "Long Risk/Reward"),
            "rr_short": ("💰", "Short Risk/Reward")
        }
        
        for param, (emoji, name) in risk_params.items():
            if param in params:
                value = params[param]
                if isinstance(value, (int, float)):
                    # Risk level indication
                    if 'sl_' in param:  # Stop loss multipliers
                        risk_level = "🟢 CONSERVATIVE" if value <= 2.0 else "🟡 MODERATE" if value <= 4.0 else "🔴 AGGRESSIVE"
                        EnhancedLogger.log_parameter(f"{name} ({risk_level})", f"{value:.2f}x", emoji)
                    else:  # Risk/Reward ratios
                        rr_quality = "🟢 EXCELLENT" if value >= 3.0 else "🟡 GOOD" if value >= 2.0 else "🔴 RISKY"
                        EnhancedLogger.log_parameter(f"{name} ({rr_quality})", f"{value:.1f}:1", emoji)
                else:
                    EnhancedLogger.log_parameter(name, value, emoji)
        
        # Optimization Quality Assessment
        if 'score' in params:
            score = params['score']
            if score > 0.1:
                quality = "🟢 EXCELLENT"
            elif score > 0.05:
                quality = "🟡 GOOD"
            elif score > 0:
                quality = "🟠 FAIR"
            else:
                quality = "🔴 POOR"
            
            EnhancedLogger.log_section("OPTIMIZATION QUALITY", "📈")
            EnhancedLogger.log_performance(f"Score {quality}", score, "🏆")
        
        # Summary
        param_count = len([p for p in params.keys() if p != 'score'])
        EnhancedLogger.log_section("PARAMETER SUMMARY", "📋")
        EnhancedLogger.log_parameter("Total Parameters", param_count, "🔢")
        EnhancedLogger.log_parameter("Optimization Time", datetime.datetime.now().strftime("%H:%M:%S"), "⏰")
        
        # Visual separator
        logger.info("🔹" * 60)
    
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """Populate indicators with enhanced ML integration and logging"""
        """Enhanced with STARTUP TRAINING"""
        pair = metadata.get('pair', 'Unknown')
        
        # 🚀 STARTUP TRAINING - Run on first data load
        if (self.startup_training_enabled and 
            pair not in self.initial_training_completed and 
            len(dataframe) >= self.startup_training_periods):
            
            logger.info(f"🎯 [STARTUP] Beginning initial ML training for {pair}")
            self.perform_startup_training(dataframe, pair)
            self.initial_training_completed[pair] = True
        EnhancedLogger.log_banner(f"POPULATING INDICATORS - {pair}", "📊")
        
        # Use default values initially
        fisher_period = self.fisher_period.value
        fisher_smooth_long = self.fisher_smooth_long.value
        fisher_smooth_short = self.fisher_smooth_short.value
        baseline_period = self.baseline_period.value
        atr_period = self.atr_period.value
        
        EnhancedLogger.log_section("INITIAL PARAMETERS", "⚙️")
        EnhancedLogger.log_parameter("Fisher Period", fisher_period, "🎣")
        EnhancedLogger.log_parameter("Baseline Period", baseline_period, "📏")
        EnhancedLogger.log_parameter("ATR Period", atr_period, "🌊")
        
        # Calculate basic indicators first (required for ML features)
        EnhancedLogger.log_subsection("Calculating base indicators", "🔧")
        
        try:
            dataframe["atr"] = talib.ATR(dataframe["high"], dataframe["low"], dataframe["close"], timeperiod=atr_period)
            EnhancedLogger.log_success("ATR calculated", "✅")
            
            dataframe["fisher"] = self.calculate_fisher(dataframe, fisher_period)
            EnhancedLogger.log_success("Fisher Transform calculated", "✅")
            
            dataframe["baseline"] = ema(dataframe["close"], length=baseline_period)
            dataframe["baseline_diff"] = dataframe["baseline"].diff()
            EnhancedLogger.log_success("Baseline indicators calculated", "✅")
            
        except Exception as e:
            EnhancedLogger.log_error(f"Base indicator calculation failed: {e}", "💥")
            raise
        
        # NOW get ML-adjusted parameters after basic indicators exist
        EnhancedLogger.log_subsection("Applying ML adjustments", "🤖")
        ml_params = self.get_ml_adjusted_params(dataframe, pair)
        
        # Apply ML adjustments to parameters if available
        if ml_params:
            EnhancedLogger.log_subsection("ML parameters detected, recalculating...", "🔄")
            
            original_params = {
                'fisher_period': fisher_period,
                'fisher_smooth_long': fisher_smooth_long, 
                'fisher_smooth_short': fisher_smooth_short,
                'baseline_period': baseline_period,
                'atr_period': atr_period
            }
            
            # Update parameters with ML suggestions
            fisher_period = ml_params.get('fisher_period', fisher_period)
            fisher_smooth_long = ml_params.get('fisher_smooth_long', fisher_smooth_long)
            fisher_smooth_short = ml_params.get('fisher_smooth_short', fisher_smooth_short)
            baseline_period = ml_params.get('baseline_period', baseline_period)
            atr_period = ml_params.get('atr_period', atr_period)
            
            # Ensure parameters are within valid ranges
            fisher_period = max(10, min(15, int(fisher_period)))
            fisher_smooth_long = max(3, min(10, int(fisher_smooth_long)))
            fisher_smooth_short = max(3, min(10, int(fisher_smooth_short)))
            baseline_period = max(5, min(21, int(baseline_period)))
            atr_period = max(7, min(21, int(atr_period)))
            
            # Log parameter changes
            changes_made = 0
            for param_name in original_params:
                old_val = original_params[param_name]
                new_val = locals()[param_name]
                if old_val != new_val:
                    changes_made += 1
                    EnhancedLogger.log_parameter(f"{param_name} changed", f"{old_val} → {new_val}", "🔄")
            
            if changes_made > 0:
                EnhancedLogger.log_success(f"Applied {changes_made} ML parameter adjustments", "🎯")
                
                # Recalculate indicators with ML-adjusted parameters
                if fisher_period != self.fisher_period.value:
                    dataframe["fisher"] = self.calculate_fisher(dataframe, fisher_period)
                    EnhancedLogger.log_success("Fisher recalculated with ML params", "🔄")
                
                if baseline_period != self.baseline_period.value:
                    dataframe["baseline"] = ema(dataframe["close"], length=baseline_period)
                    dataframe["baseline_diff"] = dataframe["baseline"].diff()
                    EnhancedLogger.log_success("Baseline recalculated with ML params", "🔄")
                
                if atr_period != self.atr_period.value:
                    dataframe["atr"] = talib.ATR(dataframe["high"], dataframe["low"], dataframe["close"], timeperiod=atr_period)
                    EnhancedLogger.log_success("ATR recalculated with ML params", "🔄")
            else:
                EnhancedLogger.log_success("ML parameters match defaults", "✨")
        else:
            EnhancedLogger.log_warning("No ML parameters available, using defaults", "⚠️")
        
        # Continue with remaining indicators
        EnhancedLogger.log_subsection("Calculating derived indicators", "🔧")
        
        try:
            # Smooth Fisher with EMA
            dataframe["fisher_smooth_long"] = ema(dataframe["fisher"], length=fisher_smooth_long)
            dataframe["fisher_smooth_short"] = ema(dataframe["fisher"], length=fisher_smooth_short)
            # FIXED: trend_long now uses fisher_smooth_long instead of fisher_smooth_short
            dataframe["fisher_trend_long"] = ema(dataframe["fisher_smooth_long"], length=21)
            # FIXED: trend_short uses shorter length (9) for faster response
            dataframe["fisher_trend_short"] = ema(dataframe["fisher_smooth_short"], length=21)
            EnhancedLogger.log_success("Fisher smoothing complete (FIXED trends)", "✅")
            
            # Baseline indicators
            dataframe["baseline_up"] = dataframe["baseline_diff"] > 0
            dataframe["baseline_down"] = dataframe["baseline_diff"] < 0
            trend_up_pct = (dataframe["baseline_up"].tail(50).sum() / 50) * 100
            EnhancedLogger.log_parameter("Baseline Uptrend %", f"{trend_up_pct:.1f}%", "📈")
            
            # Volatility (ATR for Goldie Locks Zone)
            dataframe["goldie_min"] = dataframe["baseline"] - (dataframe["atr"] * self.goldie_locks.value)
            dataframe["goldie_max"] = dataframe["baseline"] + (dataframe["atr"] * self.goldie_locks.value)
            EnhancedLogger.log_success("Goldie Locks zones calculated", "✅")
            
            # ML confidence indicators
            dataframe["ml_confidence"] = self.calculate_ml_confidence(dataframe)
            dataframe["market_regime"] = self.identify_market_regime(dataframe)
            
            # Enhanced signals with ML
            dataframe["ml_signal_strength"] = self.calculate_signal_strength(dataframe)
            
            # Log ML indicator statistics
            avg_confidence = dataframe["ml_confidence"].tail(50).mean()
            avg_signal_strength = dataframe["ml_signal_strength"].tail(50).mean()
            current_regime = dataframe["market_regime"].iloc[-1]
            
            regime_text = "🐂 BULL" if current_regime > 0 else "🐻 BEAR" if current_regime < 0 else "⚖️ NEUTRAL"
            
            EnhancedLogger.log_section("ML INDICATOR SUMMARY", "🤖")
            EnhancedLogger.log_parameter("Avg ML Confidence", f"{avg_confidence:.1%}", "🎯")
            EnhancedLogger.log_parameter("Avg Signal Strength", f"{avg_signal_strength:.3f}", "⚡")
            EnhancedLogger.log_parameter("Market Regime", regime_text, "🏛️")
            
            EnhancedLogger.log_success("All ML indicators calculated", "✅")
            
        except Exception as e:
            EnhancedLogger.log_error(f"Derived indicator calculation failed: {e}", "💥")
            raise
        
        # Final summary
        if ml_params:
            EnhancedLogger.log_success(f"ML parameters active: {len(ml_params)} adjustments", "🎯")
            EnhancedLogger.log_parameter("Active optimizers", len(self.ml_optimizers), "🤖")
        
        EnhancedLogger.log_banner(f"INDICATORS COMPLETE - {pair}", "🎉")
        
        return dataframe

    def perform_startup_training(self, dataframe: DataFrame, pair: str):
        """NEW: Perform ML training on startup using historical data"""
        try:
            logger.info(f"🧠 [STARTUP] Training ML model for {pair} with {len(dataframe)} historical candles")
            
            # Create pair-specific optimizer if doesn't exist
            if pair not in self.ml_optimizers:
                self.ml_optimizers[pair] = MLOptimizer(f"GKD_FisherV5_{pair.replace('/', '_').replace(':', '_')}")
            
            ml_optimizer = self.ml_optimizers[pair]
            
            # 📊 Generate synthetic training data from historical patterns
            training_data = self.generate_historical_training_data(dataframe, pair)
            
            if len(training_data) > 0:
                logger.info(f"📈 [STARTUP] Generated {len(training_data)} training samples for {pair}")
                
                # Update ML optimizer with historical performance patterns
                for sample in training_data:
                    ml_optimizer.performance_history.append(sample)
                
                # Train the model immediately
                if len(ml_optimizer.performance_history) >= 20:
                    ml_optimizer.update_model(dataframe, 0.0)  # Use neutral performance for initial training
                    logger.info(f"✅ [STARTUP] ML model trained successfully for {pair}")
                    
                    # Run initial Optuna optimization
                    logger.info(f"🎯 [STARTUP] Running initial parameter optimization for {pair}")
                    optimized_params = ml_optimizer.optimize_parameters(dataframe, 0.0)
                    
                    if optimized_params:
                        logger.info(f"🎉 [STARTUP] Initial optimization complete for {pair}")
                        self.log_formatted_parameters(pair, optimized_params)
                    else:
                        logger.warning(f"⚠️ [STARTUP] Initial optimization failed for {pair}")
                else:
                    logger.warning(f"⚠️ [STARTUP] Insufficient training data generated for {pair}")
            else:
                logger.error(f"❌ [STARTUP] Failed to generate training data for {pair}")
                
        except Exception as e:
            logger.error(f"❌ [STARTUP] Training failed for {pair}: {str(e)}")

    def generate_historical_training_data(self, dataframe: DataFrame, pair: str) -> List[Dict]:
        """NEW: Generate training data from historical price patterns"""
        try:
            training_samples = []
            lookback_period = min(500, len(dataframe) - 100)  # Use up to 500 candles for training
            
            logger.info(f"🔍 [STARTUP] Analyzing {lookback_period} historical periods for {pair}")
            
            # Calculate basic indicators needed for analysis
            dataframe_copy = dataframe.copy()
            dataframe_copy["atr"] = talib.ATR(dataframe_copy["high"], dataframe_copy["low"], 
                                             dataframe_copy["close"], timeperiod=14)
            dataframe_copy["fisher"] = self.calculate_fisher(dataframe_copy, 14)
            dataframe_copy["baseline"] = ema(dataframe_copy["close"], length=14)
            dataframe_copy["baseline_diff"] = dataframe_copy["baseline"].diff()
            
            # Generate training samples by analyzing historical patterns
            for i in range(100, lookback_period):  # Skip first 100 for indicator stability
                try:
                    # Extract features at this historical point
                    features = self.extract_features_at_index(dataframe_copy, i)
                    
                    # Calculate performance of next 10-20 candles as "target"
                    future_performance = self.calculate_future_performance(dataframe_copy, i, periods=15)
                    
                    if not np.isnan(future_performance) and abs(future_performance) < 0.5:  # Filter extreme values
                        training_sample = {
                            'features': features,
                            'performance': future_performance,
                            'timestamp': datetime.datetime.now() - datetime.timedelta(hours=lookback_period-i)
                        }
                        training_samples.append(training_sample)
                        
                except Exception as e:
                    continue  # Skip problematic samples
            
            logger.info(f"📊 [STARTUP] Generated {len(training_samples)} valid training samples for {pair}")
            return training_samples
            
        except Exception as e:
            logger.error(f"❌ [STARTUP] Error generating training data for {pair}: {str(e)}")
            return []
    def extract_features_at_index(self, dataframe: DataFrame, index: int) -> np.ndarray:
        """Extract ML features at a specific historical index"""
        try:
            features = []
            
            # Market volatility features
            atr_mean = dataframe['atr'].iloc[max(0, index-14):index].mean()
            features.append(atr_mean if not pd.isna(atr_mean) else 0.01)
            
            atr_std = dataframe['atr'].iloc[max(0, index-7):index].std()
            features.append(atr_std if not pd.isna(atr_std) else 0.001)
            
            # Price momentum features
            for period in [5, 10, 20]:
                pct_change = dataframe['close'].iloc[index] / dataframe['close'].iloc[max(0, index-period)] - 1
                features.append(pct_change if not pd.isna(pct_change) else 0.0)
            
            # Volume features (with defaults)
            if 'volume' in dataframe.columns:
                vol_mean = dataframe['volume'].iloc[max(0, index-14):index].mean()
                vol_pct = (dataframe['volume'].iloc[index] / dataframe['volume'].iloc[max(0, index-1)] - 1 
                          if index > 0 else 0.0)
            else:
                vol_mean, vol_pct = 1000.0, 0.0
            
            features.extend([vol_mean if not pd.isna(vol_mean) else 1000.0, 
                            vol_pct if not pd.isna(vol_pct) else 0.0])
            
            # Fisher transform features
            fisher_current = dataframe['fisher'].iloc[index]
            fisher_mean = dataframe['fisher'].iloc[max(0, index-5):index].mean()
            fisher_std = dataframe['fisher'].iloc[max(0, index-5):index].std()
            
            features.extend([
                fisher_current if not pd.isna(fisher_current) else 0.0,
                fisher_mean if not pd.isna(fisher_mean) else 0.0,
                fisher_std if not pd.isna(fisher_std) else 1.0
            ])
            
            # Baseline trend features
            baseline_diff_mean = dataframe['baseline_diff'].iloc[max(0, index-5):index].mean()
            baseline_diff_sum = dataframe['baseline_diff'].iloc[max(0, index-10):index].sum()
            
            features.extend([
                baseline_diff_mean if not pd.isna(baseline_diff_mean) else 0.0,
                baseline_diff_sum if not pd.isna(baseline_diff_sum) else 0.0
            ])
            
            # Market regime
            sma_50 = dataframe['close'].iloc[max(0, index-50):index].mean()
            sma_200 = dataframe['close'].iloc[max(0, index-200):index].mean()
            
            if not pd.isna(sma_50) and not pd.isna(sma_200) and sma_200 != 0:
                features.append(1.0 if sma_50 > sma_200 else 0.0)
            else:
                features.append(0.5)
            
            # Ensure exactly 12 features
            while len(features) < 12:
                features.append(0.0)
            features = features[:12]
            
            return np.array(features)
            
        except Exception as e:
            return np.zeros(12)

    def calculate_future_performance(self, dataframe: DataFrame, index: int, periods: int = 15) -> float:
        """Calculate future performance for training target"""
        try:
            if index + periods >= len(dataframe):
                return 0.0
            
            # Simple return calculation
            current_price = dataframe['close'].iloc[index]
            future_price = dataframe['close'].iloc[index + periods]
            
            if current_price > 0:
                return (future_price - current_price) / current_price
            else:
                return 0.0
                
        except:
            return 0.0
    def calculate_ml_confidence(self, dataframe: DataFrame) -> pd.Series:
        """Calculate ML model confidence for signals with enhanced logging"""
        try:
            EnhancedLogger.log_subsection("Calculating ML confidence", "🎯")
            
            # Simple confidence calculation based on market volatility and trend consistency
            atr_norm = dataframe["atr"] / dataframe["close"]
            trend_consistency = abs(dataframe["baseline_diff"].rolling(10).mean())
            fisher_volatility = dataframe["fisher"].rolling(10).std()
            
            # Higher confidence in stable, trending markets
            confidence = 1.0 - (atr_norm * 2 + fisher_volatility * 0.5)
            confidence = confidence.fillna(0.5).clip(0.1, 1.0)
            
            # Log confidence statistics
            avg_confidence = confidence.tail(20).mean()
            min_confidence = confidence.tail(20).min()
            max_confidence = confidence.tail(20).max()
            
            EnhancedLogger.log_parameter("Avg Confidence", f"{avg_confidence:.1%}", "🎯")
            EnhancedLogger.log_parameter("Min Confidence", f"{min_confidence:.1%}", "🔽")
            EnhancedLogger.log_parameter("Max Confidence", f"{max_confidence:.1%}", "🔼")
            
            return confidence
            
        except Exception as e:
            EnhancedLogger.log_error(f"ML confidence calculation error: {e}", "💥")
            return pd.Series(0.5, index=dataframe.index)
    
    def identify_market_regime(self, dataframe: DataFrame) -> pd.Series:
        """Identify market regime using ML features with enhanced logging"""
        try:
            EnhancedLogger.log_subsection("Identifying market regime", "🏛️")
            
            sma_50 = dataframe["close"].rolling(50).mean()
            sma_200 = dataframe["close"].rolling(200).mean()
            
            # Market regimes: 1=Bull, 0=Neutral, -1=Bear
            regime = pd.Series(0, index=dataframe.index)
            regime.loc[sma_50 > sma_200 * 1.02] = 1  # Bull market
            regime.loc[sma_50 < sma_200 * 0.98] = -1  # Bear market
            
            # Calculate regime statistics
            recent_regime = regime.tail(50)
            bull_periods = (recent_regime == 1).sum()
            bear_periods = (recent_regime == -1).sum()
            neutral_periods = (recent_regime == 0).sum()
            
            EnhancedLogger.log_parameter("Bull Periods", f"{bull_periods}/50 ({bull_periods*2:.0f}%)", "🐂")
            EnhancedLogger.log_parameter("Bear Periods", f"{bear_periods}/50 ({bear_periods*2:.0f}%)", "🐻") 
            EnhancedLogger.log_parameter("Neutral Periods", f"{neutral_periods}/50 ({neutral_periods*2:.0f}%)", "⚖️")
            
            current_regime = regime.iloc[-1]
            if current_regime > 0:
                EnhancedLogger.log_success("Current: BULL MARKET", "🐂")
            elif current_regime < 0:
                EnhancedLogger.log_warning("Current: BEAR MARKET", "🐻")
            else:
                EnhancedLogger.log_subsection("Current: NEUTRAL MARKET", "⚖️")
            
            return regime
            
        except Exception as e:
            EnhancedLogger.log_error(f"Market regime identification error: {e}", "💥")
            return pd.Series(0, index=dataframe.index)
    
    def calculate_signal_strength(self, dataframe: DataFrame) -> pd.Series:
        """Calculate signal strength using multiple indicators with enhanced logging"""
        try:
            EnhancedLogger.log_subsection("Calculating signal strength", "⚡")
            
            # Combine multiple signal components
            fisher_strength = abs(dataframe["fisher"]) / 3.0  # Normalize
            trend_strength = abs(dataframe["baseline_diff"]) / dataframe["atr"]
            volume_strength = 1.0  # Default if no volume data
            
            if 'volume' in dataframe.columns:
                volume_ma = dataframe['volume'].rolling(20).mean()
                volume_strength = (dataframe['volume'] / volume_ma).clip(0.5, 2.0) / 2.0
                EnhancedLogger.log_success("Volume strength included", "📊")
            else:
                EnhancedLogger.log_warning("No volume data, using default", "📊")
            
            # Combined signal strength
            signal_strength = (fisher_strength * 0.4 + trend_strength * 0.4 + volume_strength * 0.2)
            signal_strength = signal_strength.fillna(0.5).clip(0.1, 1.0)
            
            # Log signal strength statistics
            avg_strength = signal_strength.tail(20).mean()
            current_strength = signal_strength.iloc[-1]
            strong_signals = (signal_strength.tail(50) > 0.7).sum()
            
            EnhancedLogger.log_parameter("Avg Signal Strength", f"{avg_strength:.3f}", "⚡")
            EnhancedLogger.log_parameter("Current Strength", f"{current_strength:.3f}", "📊")
            EnhancedLogger.log_parameter("Strong Signals (>0.7)", f"{strong_signals}/50", "💪")
            
            if current_strength > 0.8:
                EnhancedLogger.log_success("VERY STRONG signal detected", "🚀")
            elif current_strength > 0.6:
                EnhancedLogger.log_success("STRONG signal detected", "💪")
            elif current_strength > 0.4:
                EnhancedLogger.log_warning("MODERATE signal detected", "⚡")
            else:
                EnhancedLogger.log_warning("WEAK signal detected", "🔋")
            
            return signal_strength
            
        except Exception as e:
            EnhancedLogger.log_error(f"Signal strength calculation error: {e}", "💥")
            return pd.Series(0.5, index=dataframe.index)
    
    def calculate_fisher(self, dataframe: DataFrame, period: int) -> pd.Series:
        """Fisher Transform calculation with ML enhancements and logging"""
        try:
            EnhancedLogger.log_subsection(f"Calculating Fisher Transform (period={period})", "🎣")
            
            median_price = (dataframe["high"] + dataframe["low"]) / 2
            fisher = pd.Series(0.0, index=dataframe.index)
            
            for i in range(period, len(dataframe)):
                price_window = median_price.iloc[i-period:i]
                price_min = price_window.min()
                price_max = price_window.max()
                if price_max != price_min:
                    norm = (median_price.iloc[i] - price_min) / (price_max - price_min)
                    norm = 2 * norm - 1
                    norm = max(min(norm, 0.999), -0.999)
                    fisher.iloc[i] = 0.5 * np.log((1 + norm) / (1 - norm))
                else:
                    fisher.iloc[i] = 0.0
            
            # Log Fisher Transform statistics
            current_fisher = fisher.iloc[-1]
            avg_fisher = fisher.tail(50).mean()
            std_fisher = fisher.tail(50).std()
            
            EnhancedLogger.log_parameter("Current Fisher", f"{current_fisher:.3f}", "🎣")
            EnhancedLogger.log_parameter("Avg Fisher (50)", f"{avg_fisher:.3f}", "📊")
            EnhancedLogger.log_parameter("Fisher Volatility", f"{std_fisher:.3f}", "🌊")
            
            if abs(current_fisher) > 2.0:
                EnhancedLogger.log_warning("Fisher in extreme territory", "⚠️")
            elif abs(current_fisher) > 1.0:
                EnhancedLogger.log_success("Fisher showing strong signal", "💪")
            
            return fisher
            
        except Exception as e:
            EnhancedLogger.log_error(f"Fisher Transform calculation error: {e}", "💥")
            return pd.Series(0.0, index=dataframe.index)
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """Populate entry trend with enhanced ML integration and logging"""
        pair = metadata.get('pair', 'Unknown')
        
        EnhancedLogger.log_banner(f"ENTRY SIGNAL ANALYSIS - {pair}", "🎯")
        
        # Get ML-adjusted parameters for this pair
        ml_params = self.get_ml_adjusted_params(dataframe, pair)
        
        # Use ML-adjusted thresholds
        fisher_buy_threshold = ml_params.get('fisher_buy_threshold', self.fisher_buy_threshold.value)
        fisher_sell_threshold = ml_params.get('fisher_sell_threshold', self.fisher_sell_threshold.value)
        
        EnhancedLogger.log_section("ENTRY THRESHOLDS", "🎚️")
        EnhancedLogger.log_parameter("Buy Threshold", fisher_buy_threshold, "🟢")
        EnhancedLogger.log_parameter("Sell Threshold", fisher_sell_threshold, "🔴")
        
        # ML-enhanced entry logic
        #ml_confidence_condition = dataframe["ml_confidence"] > self.ml_confidence_threshold.value
        #signal_strength_condition = dataframe["ml_signal_strength"] > 0.6
        ml_confidence_condition = dataframe["ml_confidence"] > self.ml_confidence_threshold.value
        signal_strength_condition = dataframe["ml_signal_strength"] > self.ml_signal_threshold.value
        
        # Count conditions for logging
        ml_conf_count = ml_confidence_condition.sum()
        signal_str_count = signal_strength_condition.sum()
        
        EnhancedLogger.log_section("ML CONDITIONS", "🤖")
        EnhancedLogger.log_parameter("High Confidence Periods", f"{ml_conf_count}/{len(dataframe)}", "🎯")
        EnhancedLogger.log_parameter("Strong Signal Periods", f"{signal_str_count}/{len(dataframe)}", "⚡")
        
        # V1.2 BALANCED: Long entry with symmetric conditions
        long_conditions = (
            (dataframe["fisher"] < fisher_buy_threshold) &
            (dataframe["fisher"] > dataframe['fisher_smooth_long']) &
            (dataframe["fisher_trend_long"] > dataframe["fisher_trend_long"].shift(1)) &
            (dataframe["baseline_up"] | (dataframe["market_regime"] > 0)) &
            (ml_confidence_condition | signal_strength_condition)
        )
        
        dataframe.loc[long_conditions, ["enter_long", "enter_tag"]] = [1, "fisher_long_v12"]
        
        long_signals = long_conditions.sum()
        EnhancedLogger.log_parameter("Long Entry Signals", long_signals, "🟢")
        
        # V1.2 BALANCED: Short entry with symmetric conditions
        if self.can_short:
            short_conditions = (
                (dataframe["fisher_smooth_short"] < fisher_sell_threshold) &
                (dataframe["baseline_down"]) &
                (dataframe["close"] >= dataframe["goldie_min"]) &
                (dataframe["close"] <= dataframe["goldie_max"]) &
                ml_confidence_condition &
                signal_strength_condition &
                (dataframe["market_regime"] <= 0)  # Neutral or bear market
            )
            dataframe.loc[short_conditions, ["enter_short", "enter_tag"]] = [1, "fisher_short_v12"]
            
            short_signals = short_conditions.sum()
            EnhancedLogger.log_parameter("Short Entry Signals", short_signals, "🔴")
        else:
            EnhancedLogger.log_warning("Short trading disabled", "⚠️")
        
        # Log recent entry signals
        recent_long = dataframe["enter_long"].tail(20).sum()
        if self.can_short:
            recent_short = dataframe["enter_short"].tail(20).sum()
            EnhancedLogger.log_section("RECENT SIGNALS (20 periods)", "📊")
            EnhancedLogger.log_parameter("Long Entries", recent_long, "🟢")
            EnhancedLogger.log_parameter("Short Entries", recent_short, "🔴")
        else:
            EnhancedLogger.log_section("RECENT SIGNALS (20 periods)", "📊")
            EnhancedLogger.log_parameter("Long Entries", recent_long, "🟢")
        
        # Current market analysis
        current_fisher = dataframe["fisher"].iloc[-1]
        current_confidence = dataframe["ml_confidence"].iloc[-1]
        current_strength = dataframe["ml_signal_strength"].iloc[-1]
        current_regime = dataframe["market_regime"].iloc[-1]
        
        EnhancedLogger.log_section("CURRENT MARKET STATE", "📈")
        EnhancedLogger.log_parameter("Fisher Value", f"{current_fisher:.3f}", "🎣")
        EnhancedLogger.log_ml_status("ML Analysis", current_confidence, "🤖")
        EnhancedLogger.log_parameter("Signal Strength", f"{current_strength:.3f}", "⚡")
        
        regime_emoji = "🐂" if current_regime > 0 else "🐻" if current_regime < 0 else "⚖️"
        regime_text = "BULL" if current_regime > 0 else "BEAR" if current_regime < 0 else "NEUTRAL"
        EnhancedLogger.log_parameter("Market Regime", f"{regime_text} {regime_emoji}", "🏛️")
        
        EnhancedLogger.log_banner(f"ENTRY ANALYSIS COMPLETE - {pair}", "✅")
        
        return dataframe
    
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """Populate exit trend with enhanced ML integration and logging"""
        pair = metadata.get('pair', 'Unknown')
        
        EnhancedLogger.log_banner(f"EXIT SIGNAL ANALYSIS - {pair}", "🚪")
        
        # Get ML-adjusted parameters for this pair
        ml_params = self.get_ml_adjusted_params(dataframe, pair)
        
        # Use ML-adjusted exit thresholds
        fisher_long_exit = ml_params.get('fisher_long_exit', self.fisher_long_exit.value)
        fisher_short_exit = ml_params.get('fisher_short_exit', self.fisher_short_exit.value)
        
        EnhancedLogger.log_section("EXIT THRESHOLDS", "🎚️")
        exit_color_long = "🟢" if fisher_long_exit > 0 else "🔴" if fisher_long_exit < 0 else "🟡"
        exit_color_short = "🟢" if fisher_short_exit > 0 else "🔴" if fisher_short_exit < 0 else "🟡"
        
        EnhancedLogger.log_parameter(f"Long Exit {exit_color_long}", f"{fisher_long_exit:.3f}", "📤")
        if self.can_short:
            EnhancedLogger.log_parameter(f"Short Exit {exit_color_short}", f"{fisher_short_exit:.3f}", "📥")
        
        # ML-enhanced exit logic with confidence-based adjustments
        ml_confidence = dataframe["ml_confidence"]
        
        # Long exit with ML-optimized threshold
        long_exit_conditions = (
            (dataframe["fisher_smooth_long"].shift() > fisher_long_exit) & 
            (dataframe["fisher_smooth_long"] < fisher_long_exit) & 
            (dataframe["fisher_smooth_long"] > dataframe['fisher']) &
            (ml_confidence > 0.2)  # Only exit with reasonable confidence
        )
        
        dataframe.loc[long_exit_conditions, ["exit_long", "exit_tag"]] = [1, "exit_long_ml"]
        
        long_exits = long_exit_conditions.sum()
        EnhancedLogger.log_parameter("Long Exit Signals", long_exits, "📤")
        
        # Short exit with ML-optimized threshold (if enabled)
        if self.can_short:
            short_exit_conditions = (
                (dataframe["fisher_smooth_short"] > fisher_short_exit) &
                (ml_confidence > 0.2)
            )
            
            dataframe.loc[short_exit_conditions, ["exit_short", "exit_tag"]] = [1, "exit_short_ml"]
            
            short_exits = short_exit_conditions.sum()
            EnhancedLogger.log_parameter("Short Exit Signals", short_exits, "📥")
        
        # Log recent exit signals
        recent_long_exit = dataframe["exit_long"].tail(20).sum() if "exit_long" in dataframe.columns else 0
        
        EnhancedLogger.log_section("RECENT EXITS (20 periods)", "📊")
        EnhancedLogger.log_parameter("Long Exits", recent_long_exit, "📤")
        
        if self.can_short:
            recent_short_exit = dataframe["exit_short"].tail(20).sum() if "exit_short" in dataframe.columns else 0
            EnhancedLogger.log_parameter("Short Exits", recent_short_exit, "📥")
        
        # Current exit readiness analysis
        current_fisher_long = dataframe["fisher_smooth_long"].iloc[-1]
        current_confidence = dataframe["ml_confidence"].iloc[-1]
        
        EnhancedLogger.log_section("CURRENT EXIT ANALYSIS", "🔍")
        
        long_distance_to_exit = current_fisher_long - fisher_long_exit
        EnhancedLogger.log_parameter("Long Distance to Exit", f"{long_distance_to_exit:.3f}", "📏")
        
        if abs(long_distance_to_exit) < 0.1:
            EnhancedLogger.log_warning("Long position near exit threshold", "⚠️")
        elif long_distance_to_exit < 0:
            EnhancedLogger.log_success("Long exit conditions met", "✅")
        
        if self.can_short:
            current_fisher_short = dataframe["fisher_smooth_short"].iloc[-1]
            short_distance_to_exit = current_fisher_short - fisher_short_exit
            EnhancedLogger.log_parameter("Short Distance to Exit", f"{short_distance_to_exit:.3f}", "📏")
            
            if abs(short_distance_to_exit) < 0.1:
                EnhancedLogger.log_warning("Short position near exit threshold", "⚠️")
            elif short_distance_to_exit > 0:
                EnhancedLogger.log_success("Short exit conditions met", "✅")
        
        EnhancedLogger.log_ml_status("Exit Confidence", current_confidence, "🎯")
        
        EnhancedLogger.log_banner(f"EXIT ANALYSIS COMPLETE - {pair}", "✅")
        
        return dataframe

# PART 5 (FINAL) - Continuing from Part 4

    def custom_exit(self, pair: str, trade: "Trade", current_time: "datetime", current_rate: float, current_profit: float, **kwargs):
        """Enhanced custom exit with ML integration and detailed logging"""
        tag = super().custom_sell(pair, trade, current_time, current_rate, current_profit, **kwargs)
        if tag:
            return tag
        
        EnhancedLogger.log_section(f"CUSTOM EXIT ANALYSIS - {pair}", "🚪")
        
        enter_tag = "empty"
        if hasattr(trade, "enter_tag") and trade.enter_tag is not None:
            enter_tag = trade.enter_tag
        
        EnhancedLogger.log_parameter("Entry Tag", enter_tag, "🏷️")
        EnhancedLogger.log_parameter("Current Profit", f"{current_profit:.2%}", "💰")
        EnhancedLogger.log_parameter("Trade Duration", str(current_time - trade.open_date_utc), "⏱️")
        
        # ML-enhanced stop loss with dynamic adjustment
        ml_adjusted_stop = -0.08
        current_ml_confidence = 0.5
        market_regime = 0
        
        try:
            # Get current dataframe for ML analysis
            dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            if not dataframe.empty:
                current_ml_confidence = dataframe["ml_confidence"].iloc[-1]
                market_regime = dataframe["market_regime"].iloc[-1]
                
                EnhancedLogger.log_ml_status("Current ML State", current_ml_confidence, "🤖")
                
                regime_text = "BULL 🐂" if market_regime > 0 else "BEAR 🐻" if market_regime < 0 else "NEUTRAL ⚖️"
                EnhancedLogger.log_parameter("Market Regime", regime_text, "🏛️")
                
                # Adjust stop loss based on ML confidence and market regime
                if current_ml_confidence < 0.5:
                    ml_adjusted_stop = -0.06  # Tighter stop in low confidence
                    EnhancedLogger.log_warning("Tighter stop due to low confidence", "⚠️")
                elif market_regime < 0 and not trade.is_short:
                    ml_adjusted_stop = -0.08   # Tighter stop for longs in bear market
                    EnhancedLogger.log_warning("Tighter stop for long in bear market", "🐻")
                elif market_regime > 0 and trade.is_short:
                    ml_adjusted_stop = -0.08 # Tighter stop for shorts in bull market
                    EnhancedLogger.log_warning("Tighter stop for short in bull market", "🐂")
                else:
                    # High confidence, with trend - can use wider stop
                    ml_adjusted_stop = -0.10  # -15% when confident
                EnhancedLogger.log_parameter("ML Adjusted Stop", f"{ml_adjusted_stop:.1%}", "🛡️")
                
        except Exception as e:
            EnhancedLogger.log_error(f"ML analysis failed: {e}", "❌")
        
        if current_profit <= ml_adjusted_stop:
            # Store trade performance for ML learning
            self.trade_performance_cache[trade.pair] = current_profit
            EnhancedLogger.log_warning(f"ML enhanced stop loss triggered", "🛑")
            EnhancedLogger.log_performance("Final Profit", current_profit, "💸")
            return f"ml_stop_loss ({entry_tag})"
        
        EnhancedLogger.log_success("No exit conditions met", "✅")
        return None
    
    def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, 
                          time_in_force: str, exit_reason: str, current_time: datetime, **kwargs) -> bool:
        """Enhanced trade exit confirmation with ML learning and detailed logging - FIXED"""
        
        EnhancedLogger.log_banner(f"TRADE EXIT CONFIRMATION - {pair}", "🔍")
        
        filled_buys = trade.select_filled_orders(trade.entry_side)
        count_of_buys = len(filled_buys)
        
        # Calculate profit for ML learning
        current_profit = trade.calc_profit_ratio(rate)
        
        EnhancedLogger.log_section("EXIT DETAILS", "📊")
        EnhancedLogger.log_parameter("Exit Reason", exit_reason, "📝")
        EnhancedLogger.log_parameter("Order Type", order_type, "📋")
        EnhancedLogger.log_parameter("Exit Amount", f"{amount:.8f}", "💹")
        EnhancedLogger.log_parameter("Exit Rate", f"{rate:.8f}", "💱")
        EnhancedLogger.log_parameter("Buy Orders", count_of_buys, "🔢")
        EnhancedLogger.log_performance("Exit Profit", current_profit, "💰")
        
        # ML learning: store trade performance
        if any(word in exit_reason for word in ["roi", "stop_loss", "stoploss", "ml_stop_loss"]):
            self.trade_performance_cache[pair] = current_profit
            
            # Enhanced logging for ML learning
            performance_quality = "🟢 GOOD" if current_profit > 0.01 else "🟡 BREAK-EVEN" if current_profit > -0.01 else "🔴 LOSS"
            EnhancedLogger.log_parameter(f"Performance {performance_quality}", f"{current_profit:.2%}", "📈")
            EnhancedLogger.log_success("Performance stored for ML learning", "🧠")
        
        # FIXED: Always allow stoploss exits (don't block negative profits!)
        if exit_reason in ["stop_loss", "ml_stop_loss"]:
            EnhancedLogger.log_success(f"Stoploss exit approved: {exit_reason}", "✅")
            # Clean up stake tracking
            if trade.amount == amount and pair in self.cust_proposed_initial_stakes:
                del self.cust_proposed_initial_stakes[pair]
                EnhancedLogger.log_success("Stake tracking cleaned up", "🧹")
            return True
        
        # FIXED: Only block small POSITIVE profits, not negative ones
        if current_profit > 0 and current_profit < 0.005:
            EnhancedLogger.log_warning("Profit too low (<0.5%), rejecting exit", "⚠️")
            return False
        
        # FIXED: Don't block single buy ROI exits (removed this condition)
        # if (count_of_buys == 1) & any(word in exit_reason for word in ["roi"]):
        #     EnhancedLogger.log_warning("Single buy + ROI exit, rejecting", "⚠️")
        #     return False
        
        # Clean up stake tracking
        if trade.amount == amount and pair in self.cust_proposed_initial_stakes:
            del self.cust_proposed_initial_stakes[pair]
            EnhancedLogger.log_success("Stake tracking cleaned up", "🧹")
        
        EnhancedLogger.log_success("Trade exit confirmed", "✅")
        return True


    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, 
                           proposed_stake: float, min_stake: float, max_stake: float, **kwargs) -> float:
        """Standard stake amount - no ML adjustments"""
        return round(proposed_stake,3)

    def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, 
                             current_profit: float, min_stake: float, max_stake: float, **kwargs) -> Optional[float]:
        """PYRAMIDING DCA - Only add to winning trades when signal confirms"""
        
        # 1. Only DCA when winning (pyramiding, not averaging down)
        if current_profit <= 0:
            return None
        
        # 2. Check if signal still confirms trade direction
        dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
        if len(dataframe) == 0:
            return None
        
        current_signal = dataframe.iloc[-1]
        
        # For LONG trades: only DCA if still getting long signals
        if not trade.is_short and current_signal.get('enter_short', 0) == 1:
            return None  # Don't pyramid long when short signal appears
        
        # For SHORT trades: only DCA if still getting short signals  
        if trade.is_short and current_signal.get('enter_long', 0) == 1:
            return None  # Don't pyramid short when long signal appears
        
        # 3. Check profit threshold for pyramiding
        filled_buys = trade.select_filled_orders(trade.entry_side)
        count_of_buys = len(filled_buys)
        
        # Minimum profit before first pyramid (e.g., 2%)
        min_pyramid_profit = 0.02
        
        # Progressive profit targets for additional pyramids
        profit_thresholds = [0.02, 0.05, 0.10, 0.15, 0.20]  # 2%, 5%, 10%, 15%, 20%
        
        if count_of_buys >= len(profit_thresholds) + 1:
            return None  # Max pyramids reached
        
        required_profit = profit_thresholds[count_of_buys - 1] if count_of_buys - 1 < len(profit_thresholds) else 0.30
        
        if current_profit < required_profit:
            return None  # Profit not high enough yet
        
        EnhancedLogger.log_section(f"PYRAMID DCA - {trade.pair}", "📈")
        EnhancedLogger.log_parameter("Current Profit", f"{current_profit:.2%}", "💰")
        EnhancedLogger.log_parameter("Pyramid Level", count_of_buys, "🔺")
        EnhancedLogger.log_parameter("Required Profit", f"{required_profit:.2%}", "🎯")
        
        try:
            actual_initial_stake = filled_buys[0].cost
            
            # Calculate pyramid stake (smaller than initial, scaling down)
            # Each pyramid is 50% of previous
            pyramid_stake = actual_initial_stake * (0.5 ** (count_of_buys - 1))
            
            # Cap at max allowed
            max_allowed_stake = actual_initial_stake * self.max_dca_multiplier
            stake_amount = min(pyramid_stake, max_allowed_stake, max_stake)
            
            EnhancedLogger.log_parameter("Pyramid Stake", f"{stake_amount:.4f}", "💵")
            EnhancedLogger.log_success(f"Pyramid order #{count_of_buys + 1} approved", "✅")
            
            return stake_amount
            
        except Exception as e:
            EnhancedLogger.log_error(f"Pyramid calculation failed: {e}", "💥")
            return None
        
        return None

    def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, 
                       current_profit: float, **kwargs) -> float:
        """Disabled - using fixed stoploss only"""
        return None  # DISABLED: Let strategy use fixed stoploss = -0.06

    
    def leverage(self, pair: str, current_time: datetime, current_rate: float, proposed_leverage: float, 
                max_leverage: float, side: str, **kwargs) -> float:
        """ML-enhanced leverage management with detailed logging"""
        
        EnhancedLogger.log_section(f"LEVERAGE CALCULATION - {pair}", "⚖️")
        
        try:
            dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            base_leverage = self.set_leverage if self.can_short else 1.0
            
            EnhancedLogger.log_parameter("Proposed Leverage", f"{proposed_leverage:.1f}x", "📊")
            EnhancedLogger.log_parameter("Max Leverage", f"{max_leverage:.1f}x", "🔺")
            EnhancedLogger.log_parameter("Base Leverage", f"{base_leverage:.1f}x", "⚙️")
            EnhancedLogger.log_parameter("Trade Side", side.upper(), "↔️")
            
            if not dataframe.empty:
                ml_confidence = dataframe["ml_confidence"].iloc[-1]
                market_volatility = dataframe["atr"].iloc[-1] / dataframe["close"].iloc[-1]
                
                EnhancedLogger.log_ml_status("ML Confidence", ml_confidence, "🤖")
                EnhancedLogger.log_parameter("Market Volatility", f"{market_volatility:.1%}", "🌊")
                
                # Reduce leverage in high volatility or low confidence conditions
                if ml_confidence < 0.6 or market_volatility > 0.05:
                    adjusted_leverage = base_leverage * 0.8
                    reason = "low confidence" if ml_confidence < 0.6 else "high volatility"
                    EnhancedLogger.log_warning(f"Reduced leverage due to {reason}", "⚠️")
                elif ml_confidence > 0.8 and market_volatility < 0.02:
                    adjusted_leverage = min(base_leverage * 1.1, max_leverage)
                    EnhancedLogger.log_success("Increased leverage due to favorable conditions", "📈")
                else:
                    adjusted_leverage = base_leverage
                    EnhancedLogger.log_success("Standard leverage applied", "✅")
                
                final_leverage = max(1, min(adjusted_leverage, max_leverage))
                
                leverage_change = final_leverage - base_leverage
                change_emoji = "📈" if leverage_change > 0 else "📉" if leverage_change < 0 else "➡️"
                
                EnhancedLogger.log_parameter("Final Leverage", f"{final_leverage:.1f}x", "🎯")
                EnhancedLogger.log_parameter(f"Leverage Change {change_emoji}", f"{leverage_change:+.1f}x", "📊")
                
                return round(final_leverage,1)
                
        except Exception as e:
            EnhancedLogger.log_error(f"Leverage calculation failed: {e}", "💥")
        
        default_leverage = self.set_leverage if self.can_short else 1.0
        EnhancedLogger.log_parameter("Default Leverage Applied", f"{default_leverage:.1f}x", "🔄")
        return default_leverage

