# source: https://raw.githubusercontent.com/Henrijc/Trading-bot/1dcd395931eb0b1f3bc8525dbbb0908c07001776/freqtrade/user_data/strategies/LunoFreqAIStrategy.py
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these libs ---

import logging
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
from typing import Optional, Union

# Mock freqtrade imports for deployment compatibility
try:
    from freqtrade.strategy import (
        BooleanParameter,
        CategoricalParameter,
        DecimalParameter,
        IntParameter,
        IStrategy,
        merge_informative_pair,
    )
    import freqtrade.vendor.qtpylib.indicators as qtpylib
except ImportError:
    # Mock implementations for deployment
    class IStrategy:
        INTERFACE_VERSION = 3
        minimal_roi = {}
        stoploss = -0.05
        trailing_stop = True
        trailing_stop_positive = 0.02
        trailing_stop_positive_offset = 0.03
        trailing_only_offset_is_reached = True
        timeframe = '5m'
        can_short = False
        startup_candle_count = 40
        process_only_new_candles = False
        
        def bot_loop_start(self, current_time, **kwargs):
            pass
            
        def populate_indicators(self, dataframe, metadata):
            return dataframe
            
        def populate_entry_trend(self, dataframe, metadata):
            return dataframe
            
        def populate_exit_trend(self, dataframe, metadata):
            return dataframe
            
        def confirm_trade_entry(self, *args, **kwargs):
            return True
            
        def custom_exit(self, *args, **kwargs):
            return None
    
    class DecimalParameter:
        def __init__(self, min_val, max_val, default=None, space=None, optimize=False):
            self.value = default
    
    class IntParameter:
        def __init__(self, min_val, max_val, default=None, space=None, optimize=False):
            self.value = default
    
    BooleanParameter = DecimalParameter
    CategoricalParameter = DecimalParameter
    
    def merge_informative_pair(*args, **kwargs):
        return None
    
    # Mock qtpylib
    class MockQtpylib:
        @staticmethod
        def rsi(dataframe, timeperiod=14):
            return dataframe['close'] * 0 + 50  # Mock RSI at 50
        
        @staticmethod
        def macd(dataframe):
            return {
                'macd': dataframe['close'] * 0,
                'macdsignal': dataframe['close'] * 0,
                'macdhist': dataframe['close'] * 0
            }
        
        @staticmethod
        def bollinger_bands(dataframe, window=20, stds=2):
            return {
                'upper': dataframe['close'] * 1.02,
                'mid': dataframe['close'],
                'lower': dataframe['close'] * 0.98
            }
        
        @staticmethod
        def ema(dataframe, timeperiod=12):
            return dataframe['close']
        
        @staticmethod
        def sma(dataframe, timeperiod=20):
            return dataframe['close']
        
        @staticmethod
        def momentum(dataframe, timeperiod=14):
            return dataframe['close'] * 0 + 1
        
        @staticmethod
        def atr(dataframe, timeperiod=14):
            return dataframe['close'] * 0.01
    
    qtpylib = MockQtpylib()

logger = logging.getLogger(__name__)


class Github_Henrijc_Trading_bot__LunoFreqAIStrategy__20250804_135809(IStrategy):
    """
    Real FreqAI Strategy for Luno-style trading
    This strategy uses the actual FreqAI framework for machine learning predictions
    """

    INTERFACE_VERSION = 3

    # Minimal ROI designed for the strategy.
    minimal_roi = {
        "0": 0.10,      # 10% profit
        "40": 0.06,     # 6% profit after 40 minutes
        "100": 0.04,    # 4% profit after 100 minutes
        "180": 0.02,    # 2% profit after 180 minutes
        "360": 0.01     # 1% profit after 360 minutes
    }

    # Optimal stoploss designed for the strategy.
    stoploss = -0.05  # 5% stop loss (4% risk management + buffer)

    # Trailing stoploss
    trailing_stop = True
    trailing_stop_positive = 0.02
    trailing_stop_positive_offset = 0.03
    trailing_only_offset_is_reached = True

    # Optimal timeframe for the strategy.
    timeframe = '5m'

    # FreqAI required parameters
    can_short = False  # We only do long trades
    startup_candle_count: int = 40
    process_only_new_candles = False

    # Strategy parameters
    buy_params = {
        "ai_signal_threshold": 0.65,  # Confidence threshold for AI buy signals
        "rsi_buy": 30,
        "volume_factor": 1.2,
    }

    sell_params = {
        "ai_signal_threshold": -0.3,  # Confidence threshold for AI sell signals
        "rsi_sell": 70,
    }

    # FreqAI specific parameters
    ai_signal_threshold = DecimalParameter(0.5, 0.9, default=0.65, space="buy", optimize=True)
    rsi_buy = IntParameter(20, 40, default=30, space="buy", optimize=True)
    rsi_sell = IntParameter(60, 80, default=70, space="sell", optimize=True)
    volume_factor = DecimalParameter(1.0, 2.0, default=1.2, space="buy", optimize=True)

    def bot_loop_start(self, current_time: datetime, **kwargs) -> None:
        """
        Called at the start of the bot iteration (one loop).
        """
        if self.config.get("freqai", {}).get("enabled", False):
            logger.info("FreqAI enabled - using real ML predictions for trading decisions")

    def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Adds several different TA indicators to the given DataFrame
        """

        # Basic indicators for feature engineering and fallback signals
        dataframe['rsi'] = qtpylib.rsi(dataframe, timeperiod=14)
        
        # MACD
        macd = qtpylib.macd(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macd_signal'] = macd['macdsignal']
        dataframe['macd_hist'] = macd['macdhist']
        
        # Bollinger Bands
        bollinger = qtpylib.bollinger_bands(dataframe, window=20, stds=2)
        dataframe['bb_lowerband'] = bollinger['lower']
        dataframe['bb_middleband'] = bollinger['mid']
        dataframe['bb_upperband'] = bollinger['upper']
        dataframe['bb_percent'] = (
            (dataframe['close'] - dataframe['bb_lowerband']) /
            (dataframe['bb_upperband'] - dataframe['bb_lowerband'])
        )
        dataframe['bb_width'] = (
            (dataframe['bb_upperband'] - dataframe['bb_lowerband']) / 
            dataframe['bb_middleband']
        )

        # EMA
        dataframe['ema12'] = qtpylib.ema(dataframe, timeperiod=12)
        dataframe['ema26'] = qtpylib.ema(dataframe, timeperiod=26)
        dataframe['ema50'] = qtpylib.ema(dataframe, timeperiod=50)

        # Volume indicators
        dataframe['volume_sma'] = qtpylib.sma(dataframe['volume'], timeperiod=20)
        dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_sma']

        # Price action indicators
        dataframe['price_change'] = dataframe['close'].pct_change()
        dataframe['momentum'] = qtpylib.momentum(dataframe, timeperiod=14)

        return dataframe

    def populate_any_indicators(
        self, pair, df, tf, informative=None, set_generalized_indicators=False
    ):
        """
        Function which takes a dataframe, pair and timeframe and populates
        indicators for FreqAI feature engineering.
        """

        coin = pair.split('/')[0]

        if informative is None:
            informative = self.dp.get_pair_dataframe(pair, tf)

        # FreqAI feature engineering - the "%" prefix indicates features for ML model
        informative[f"%-{coin}rsi-period-10"] = qtpylib.rsi(informative, timeperiod=10)
        informative[f"%-{coin}rsi-period-14"] = qtpylib.rsi(informative, timeperiod=14)
        informative[f"%-{coin}rsi-period-20"] = qtpylib.rsi(informative, timeperiod=20)

        # MACD features
        macd = qtpylib.macd(informative)
        informative[f"%-{coin}macd"] = macd['macd']
        informative[f"%-{coin}macd_signal"] = macd['macdsignal']
        informative[f"%-{coin}macd_hist"] = macd['macdhist']

        # Bollinger Band features
        bollinger = qtpylib.bollinger_bands(informative, window=20, stds=2)
        informative[f"%-{coin}bb_lowerband"] = bollinger['lower']
        informative[f"%-{coin}bb_middleband"] = bollinger['mid']
        informative[f"%-{coin}bb_upperband"] = bollinger['upper']
        informative[f"%-{coin}bb_percent"] = (
            (informative['close'] - bollinger['lower']) /
            (bollinger['upper'] - bollinger['lower'])
        )
        informative[f"%-{coin}bb_width"] = (
            (bollinger['upper'] - bollinger['lower']) / bollinger['mid']
        )

        # EMA features
        for period in [10, 21, 50]:
            informative[f"%-{coin}ema-period-{period}"] = qtpylib.ema(informative, timeperiod=period)

        # SMA features
        for period in [10, 20, 50]:
            informative[f"%-{coin}sma-period-{period}"] = qtpylib.sma(informative, timeperiod=period)

        # Volume features
        informative[f"%-{coin}volume_sma"] = qtpylib.sma(informative['volume'], timeperiod=20)
        informative[f"%-{coin}volume_ratio"] = informative['volume'] / informative[f"%-{coin}volume_sma"]

        # Price action features
        informative[f"%-{coin}price_change"] = informative['close'].pct_change()
        informative[f"%-{coin}high_low_ratio"] = informative['high'] / informative['low']

        # Momentum features
        for period in [10, 14, 20]:
            informative[f"%-{coin}momentum-period-{period}"] = qtpylib.momentum(informative, timeperiod=period)

        # Volatility features (ATR)
        for period in [14, 20]:
            informative[f"%-{coin}atr-period-{period}"] = qtpylib.atr(informative, timeperiod=period)

        # Add targets for FreqAI to predict - the "&" prefix indicates targets
        # These are what FreqAI will try to predict
        informative[f"&-{coin}close_price_2"] = informative['close'].shift(-2)
        informative[f"&-{coin}close_price_5"] = informative['close'].shift(-5)
        informative[f"&-{coin}close_price_10"] = informative['close'].shift(-10)

        # Classification targets (up/down/sideways)
        future_return_2 = (informative['close'].shift(-2) / informative['close'] - 1)
        future_return_5 = (informative['close'].shift(-5) / informative['close'] - 1)

        # Binary classification: 1 for up, 0 for down/sideways
        informative[f"&-{coin}up_or_down_2"] = (future_return_2 > 0.01).astype(int)
        informative[f"&-{coin}up_or_down_5"] = (future_return_5 > 0.01).astype(int)

        # Multi-class classification: 2=strong_up, 1=up, 0=down/sideways
        def classify_movement(returns):
            if returns > 0.02:
                return 2  # Strong up
            elif returns > 0.005:
                return 1  # Up
            else:
                return 0  # Down/sideways

        informative[f"&-{coin}price_class_2"] = future_return_2.apply(classify_movement)
        informative[f"&-{coin}price_class_5"] = future_return_5.apply(classify_movement)

        return informative

    def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Based on TA indicators and FreqAI predictions, populates the entry signal
        """

        # Check if FreqAI predictions are available
        freqai_predictions = [col for col in dataframe.columns if col.startswith('do_predict')]
        
        if freqai_predictions:
            # Use FreqAI predictions as primary signal
            logger.debug("Using FreqAI predictions for entry signals")
            
            # Look for prediction columns that indicate buy signals
            # FreqAI typically creates columns like 'do_predict_close_price_5'
            prediction_cols = [col for col in dataframe.columns if 'do_predict' in col and 'close_price' in col]
            
            if prediction_cols:
                # Use the first available prediction (e.g., 5-period ahead)
                pred_col = prediction_cols[0]
                
                # Calculate expected return from prediction
                current_price = dataframe['close']
                predicted_price = dataframe[pred_col]
                expected_return = (predicted_price / current_price - 1)
                
                # Entry condition: AI predicts significant upward movement
                dataframe.loc[
                    (
                        (expected_return > self.ai_signal_threshold.value / 100) &  # AI signal
                        (dataframe['rsi'] < 70) &  # Not overbought
                        (dataframe['volume_ratio'] > 1.0) &  # Volume confirmation
                        (dataframe['volume'] > 0)  # Basic sanity check
                    ),
                    'enter_long'] = 1
            else:
                # Fallback to classification predictions
                class_cols = [col for col in dataframe.columns if 'do_predict' in col and ('up_or_down' in col or 'price_class' in col)]
                
                if class_cols:
                    class_col = class_cols[0]
                    
                    # Entry condition: AI predicts upward movement
                    dataframe.loc[
                        (
                            (dataframe[class_col] >= 1) &  # AI predicts up or strong up
                            (dataframe['rsi'] < 70) &  # Not overbought
                            (dataframe['volume_ratio'] > 1.0) &  # Volume confirmation
                            (dataframe['volume'] > 0)  # Basic sanity check
                        ),
                        'enter_long'] = 1

        else:
            # Fallback to traditional technical analysis
            logger.debug("No FreqAI predictions available, using technical analysis fallback")
            
            dataframe.loc[
                (
                    (dataframe['rsi'] < self.rsi_buy.value) &
                    (dataframe['volume_ratio'] > self.volume_factor.value) &
                    (dataframe['close'] > dataframe['ema12']) &
                    (dataframe['ema12'] > dataframe['ema26']) &
                    (dataframe['macd'] > dataframe['macd_signal']) &
                    (dataframe['bb_percent'] < 0.2) &  # Near lower Bollinger Band
                    (dataframe['volume'] > 0)
                ),
                'enter_long'] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Based on TA indicators and FreqAI predictions, populates the exit signal
        """

        # Check if FreqAI predictions are available
        freqai_predictions = [col for col in dataframe.columns if col.startswith('do_predict')]
        
        if freqai_predictions:
            # Use FreqAI predictions for exit signals
            prediction_cols = [col for col in dataframe.columns if 'do_predict' in col and 'close_price' in col]
            
            if prediction_cols:
                pred_col = prediction_cols[0]
                
                # Calculate expected return from prediction
                current_price = dataframe['close']
                predicted_price = dataframe[pred_col]
                expected_return = (predicted_price / current_price - 1)
                
                # Exit condition: AI predicts downward movement or low confidence
                dataframe.loc[
                    (
                        (expected_return < -0.01) |  # AI predicts decline
                        (dataframe['rsi'] > self.rsi_sell.value) |  # Overbought
                        (dataframe['bb_percent'] > 0.8)  # Near upper Bollinger Band
                    ),
                    'exit_long'] = 1
            else:
                # Use classification predictions
                class_cols = [col for col in dataframe.columns if 'do_predict' in col and ('up_or_down' in col or 'price_class' in col)]
                
                if class_cols:
                    class_col = class_cols[0]
                    
                    # Exit condition: AI predicts down/sideways movement
                    dataframe.loc[
                        (
                            (dataframe[class_col] == 0) |  # AI predicts down/sideways
                            (dataframe['rsi'] > self.rsi_sell.value) |  # Overbought
                            (dataframe['bb_percent'] > 0.8)  # Near upper Bollinger Band
                        ),
                        'exit_long'] = 1

        else:
            # Fallback to traditional technical analysis
            dataframe.loc[
                (
                    (dataframe['rsi'] > self.rsi_sell.value) |
                    (dataframe['bb_percent'] > 0.8) |
                    (dataframe['macd'] < dataframe['macd_signal'])
                ),
                'exit_long'] = 1

        return dataframe

    def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
                           time_in_force: str, current_time: datetime,
                           entry_tag: Optional[str], side: str, **kwargs) -> bool:
        """
        Called right before placing an entry order.
        Implement 4% risk management as per user requirements
        """
        try:
            # Get current portfolio value
            portfolio_value = 154273.71  # User's capital (in production this would be dynamic)
            
            # Calculate 4% risk
            max_risk_amount = portfolio_value * 0.04  # R6170.95
            
            # Calculate position size based on 4% risk
            trade_value = amount * rate
            
            if trade_value > max_risk_amount:
                logger.warning(f"Trade value {trade_value} exceeds 4% risk limit {max_risk_amount}")
                return False
            
            # Special handling for XRP (user wants to keep 1000 XRP long-term)
            if 'XRP' in pair:
                # Conservative limit for XRP trades to protect reserves
                if amount > 100:
                    logger.info(f"XRP trade rejected - protecting 1000 XRP reserve")
                    return False
            
            logger.info(f"FreqAI Trade confirmed: {pair} {side} {amount} at {rate}")
            return True
            
        except Exception as e:
            logger.error(f"Error in confirm_trade_entry: {e}")
            return False

    def custom_exit(self, pair: str, trade, current_time: datetime, current_rate: float,
                   current_profit: float, **kwargs):
        """
        Custom exit logic for advanced profit taking with FreqAI insights
        """
        try:
            # Progressive profit taking based on user's R8000/month target
            # Target: R8000/month means ~R267/day profit target
            
            if current_profit > 0.15:  # 15% profit
                return "freqai_take_profit_15"
            elif current_profit > 0.10:  # 10% profit
                return "freqai_take_profit_10"
            elif current_profit > 0.05:  # 5% profit
                return "freqai_take_profit_5"
            elif current_profit < -0.04:  # 4% loss (strict risk management)
                return "freqai_stop_loss_4"
                
            return None
            
        except Exception as e:
            logger.error(f"Error in custom_exit: {e}")
            return None