# source: https://raw.githubusercontent.com/giwrgosgiai/freq/d74ea2687e5559b0263c77db81f7e00a3888cb3d/user_data/strategies/AI_Trading_Strategy.py
import logging
import numpy as np
import pandas as pd
import pandas_ta as ta
from pandas import DataFrame
from datetime import datetime, timedelta
from typing import Optional, Dict, List
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
from freqtrade.strategy.parameters import CategoricalParameter
from freqtrade.persistence import Trade

logger = logging.getLogger(__name__)

class Github_giwrgosgiai_freq__AI_Trading_Strategy__20250813_174524(IStrategy):
    """
    AI-Powered Trading Strategy using Machine Learning models
    Combines LightGBM, XGBoost, and Random Forest for predictions
    """
    
    # Strategy parameters
    INTERFACE_VERSION = 3
    
    # HYPEROPT PARAMETERS - For optimization
    # Buy parameters - OPTIMIZED for better win rate
    buy_rsi = IntParameter(25, 45, default=35, space="buy", optimize=True)  # More relaxed buy RSI
    sell_rsi = IntParameter(55, 75, default=65, space="sell", optimize=True)  # More relaxed sell RSI
    
    # Simple prediction thresholds (no complex AI)
    buy_prediction = DecimalParameter(0.5, 0.8, default=0.6, space="buy", optimize=True)
    sell_prediction = DecimalParameter(0.2, 0.5, default=0.4, space="sell", optimize=True)
    
    # ROI table - SIMPLIFIED for swing trading
    minimal_roi = {
        "0": 0.02,      # 2% profit target
        "30": 0.015,    # 1.5% after 30 minutes
        "60": 0.01,     # 1% after 1 hour
        "120": 0.005,   # 0.5% after 2 hours
        "240": 0.0      # Close after 4 hours
    }
    
    # Stoploss parameters
    stoploss = -0.015  # 1.5% stop loss
    
    # Hyperopt stoploss parameter (will override fixed stoploss)
    use_custom_stoploss = True
    
    # Hyperopt stoploss value - OPTIMIZED for better risk management
    hyperopt_stoploss = DecimalParameter(-0.06, -0.03, default=-0.045, space="stoploss")  # Tighter stoploss
    
    # ROI table - SIMPLIFIED for swing trading
    minimal_roi = {
        "0": 0.02,      # 2% profit target
        "30": 0.015,    # 1.5% after 30 minutes
        "60": 0.01,     # 1% after 1 hour
        "120": 0.005,   # 0.5% after 2 hours
        "240": 0.0      # Close after 4 hours
    }
    
    # Trailing stoploss - OPTIMIZED for better profit protection
    trailing_stop = True
    trailing_stop_positive = 0.002  # 0.2% trailing stop for better profit protection
    trailing_stop_positive_offset = 0.008  # 0.8% offset for earlier protection
    trailing_only_offset_is_reached = True
    
    # Timeframe - Changed to 15m for swing trading
    timeframe = '15m'
    
    # Number of candles the strategy requires before producing valid signals
    # Much reduced for faster startup and more trading opportunities
    startup_candle_count = 20
    
    # Process only new candles
    process_only_new_candles = True
    
    # Use this method if you want to override the default result for the buy signal
    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', 
                       current_rate: float, current_profit: float, **kwargs) -> float:
        """
        Custom stoploss logic for swing trading, returning the new distance relative to current_rate
        """
        # Dynamic stoploss for swing trading with hyperopt parameter - OPTIMIZED
        if current_profit < -0.015:
            return -0.025  # 2.5% stop loss for losing trades (tighter)
        elif current_profit < -0.008:
            return -0.02  # 2% stop loss for moderate losses (tighter)
        elif current_profit > 0.02:
            return -0.015  # 1.5% stop loss for profitable trades (protect profits)
        
        # Use hyperopt parameter for base stoploss
        return self.hyperopt_stoploss.value  # This will be optimized by hyperopt
    
    def populate_any_indicators(self, pair: str, df: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Generate all technical indicators for the AI model
        """
        if len(df) < 50:
            return df
        
        # Trend indicators
        df['sma_20'] = ta.sma(df['close'], length=20)
        df['sma_50'] = ta.sma(df['close'], length=50)
        df['ema_12'] = ta.ema(df['close'], length=12)
        df['ema_26'] = ta.ema(df['close'], length=26)
        df['ema_50'] = ta.ema(df['close'], length=50)  # Added EMA 50 for trend confirmation
        df['ema_200'] = ta.ema(df['close'], length=200)  # Added EMA 200 for major trend confirmation
        
        # MACD
        macd = ta.macd(df['close'])
        df['macd'] = macd['MACD_12_26_9']
        df['macd_signal'] = macd['MACDs_12_26_9']
        df['macd_histogram'] = macd['MACDh_12_26_9']
        
        # RSI
        df['rsi'] = ta.rsi(df['close'], length=14)
        
        # Bollinger Bands
        bb = ta.bbands(df['close'], length=20, std=2)
        if bb is not None and not bb.empty:
            df['bb_upper'] = bb['BBU_20_2.0']
            df['bb_middle'] = bb['BBM_20_2.0']
            df['bb_lower'] = bb['BBL_20_2.0']
            df['bb_width'] = (df['bb_upper'] - df['bb_lower']) / df['bb_middle']
        else:
            # Fallback calculation
            df['bb_upper'] = df['close'].rolling(window=20).mean() + (df['close'].rolling(window=20).std() * 2)
            df['bb_middle'] = df['close'].rolling(window=20).mean()
            df['bb_lower'] = df['close'].rolling(window=20).mean() - (df['close'].rolling(window=20).std() * 2)
            df['bb_width'] = (df['bb_upper'] - df['bb_lower']) / df['bb_middle']
        
        # Stochastic
        stoch = ta.stoch(df['high'], df['low'], df['close'])
        df['stoch_k'] = stoch['STOCHk_14_3_3']
        df['stoch_d'] = stoch['STOCHd_14_3_3']
        
        # Volume indicators
        df['volume_sma'] = ta.sma(df['volume'], length=20)
        df['volume_ratio'] = df['volume'] / df['volume_sma']
        df['volume_above_sma'] = (df['volume'] > df['volume_sma']).astype(int)
        
        # Price action
        df['high_low_ratio'] = df['high'] / df['low']
        df['close_open_ratio'] = df['close'] / df['open']
        
        # Volatility
        df['atr'] = ta.atr(df['high'], df['low'], df['close'], length=14)
        df['atr_ratio'] = df['atr'] / df['close']
        
        # Momentum
        df['roc'] = ta.roc(df['close'], length=10)
        df['mom'] = ta.mom(df['close'], length=10)
        
        # Additional features
        df['price_change'] = df['close'].pct_change()
        df['volume_change'] = df['volume'].pct_change()
        
        # Lag features for time series
        for i in range(1, 6):
            df[f'close_lag_{i}'] = df['close'].shift(i)
            df[f'volume_lag_{i}'] = df['volume'].shift(i)
        
        # Rolling statistics
        df['close_rolling_mean'] = df['close'].rolling(window=10).mean()
        df['close_rolling_std'] = df['close'].rolling(window=10).std()
        df['volume_rolling_mean'] = df['volume'].rolling(window=10).mean()
        
        return df
    
    def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Technical indicators - SIMPLIFIED for reliability
        """
        # RSI
        dataframe['rsi'] = ta.rsi(dataframe['close'], length=14)
        
        # Moving averages
        dataframe['sma_20'] = ta.sma(dataframe['close'], length=20)
        dataframe['ema_50'] = ta.ema(dataframe['close'], length=50)
        
        # MACD
        macd = ta.macd(dataframe['close'])
        dataframe['macd'] = macd['MACD_12_26_9']
        dataframe['macd_signal'] = macd['MACDs_12_26_9']
        dataframe['macd_histogram'] = macd['MACDh_12_26_9']
        
        # Bollinger Bands
        bollinger = ta.bbands(dataframe['close'], length=20, std=2)
        dataframe['bb_upper'] = bollinger['BBU_20_2.0']
        dataframe['bb_middle'] = bollinger['BBM_20_2.0']
        dataframe['bb_lower'] = bollinger['BBL_20_2.0']
        
        # Volume indicators
        dataframe['volume_sma'] = ta.sma(dataframe['volume'], length=20)
        
        return dataframe
    
    def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Entry signal logic - SIMPLIFIED for reliability
        """
        dataframe.loc[:, 'enter_long'] = 0
        dataframe.loc[:, 'enter_tag'] = ''
        
        # Simple, reliable entry conditions - MORE RELAXED
        for index in range(len(dataframe)):
            if index < 20:  # Need at least 20 candles for indicators
                continue
                
            # Entry conditions (more relaxed for more trades)
            rsi_oversold = dataframe.iloc[index]['rsi'] < self.buy_rsi.value
            price_above_sma = dataframe.iloc[index]['close'] > dataframe.iloc[index]['sma_20']
            macd_bullish = dataframe.iloc[index]['macd'] > dataframe.iloc[index]['macd_signal']
            volume_good = dataframe.iloc[index]['volume'] > 0
            
            # More relaxed entry - only need 2 out of 4 conditions
            conditions_met = sum([rsi_oversold, price_above_sma, macd_bullish, volume_good])
            
            if conditions_met >= 2:  # Only need 2 conditions instead of all 4
                dataframe.loc[dataframe.index[index], 'enter_long'] = 1
                dataframe.loc[dataframe.index[index], 'enter_tag'] = 'Relaxed_Entry'
        
        return dataframe
    
    def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        Exit signal logic - SIMPLIFIED for reliability
        """
        dataframe.loc[:, 'exit_long'] = 0
        dataframe.loc[:, 'exit_tag'] = ''
        
        # Simple, reliable exit conditions - MORE RELAXED
        for index in range(len(dataframe)):
            if index < 20:  # Need at least 20 candles for indicators
                continue
                
            # Exit conditions (more relaxed for faster exits)
            rsi_overbought = dataframe.iloc[index]['rsi'] > self.sell_rsi.value
            price_below_sma = dataframe.iloc[index]['close'] < dataframe.iloc[index]['sma_20']
            macd_bearish = dataframe.iloc[index]['macd'] < dataframe.iloc[index]['macd_signal']
            
            # Exit if ANY condition is met (more aggressive)
            if (rsi_overbought or price_below_sma or macd_bearish):
                dataframe.loc[dataframe.index[index], 'exit_long'] = 1
                dataframe.loc[dataframe.index[index], 'exit_tag'] = 'Relaxed_Exit'
        
        return dataframe
    
    def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', 
                   current_rate: float, current_profit: float, **kwargs) -> Optional[str]:
        """
        Custom exit logic - SIMPLIFIED for reliability
        """
        # Simple profit taking and stop loss
        if current_profit >= 0.02:  # Take profit at 2%
            return "Profit_Take"
        
        if current_profit <= -0.015:  # Stop loss at -1.5%
            return "Stop_Loss"
        
        # Time-based exit (avoid holding too long)
        trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600  # hours
        if trade_duration > 48:  # Exit after 48 hours
            return "Time_Exit"
        
        return None
