from datetime import timedelta, datetime
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, stoploss_from_open,
                                IntParameter, IStrategy, merge_informative_pair, informative, stoploss_from_absolute)
from pandas import DataFrame
from freqtrade.persistence import Trade
from sklearn.tree import DecisionTreeClassifier
from sklearn.preprocessing import StandardScaler
import numpy as np
import talib.abstract as ta
import pandas_ta as pdta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from typing import Optional, Union, Tuple


class HammerReversalStrategy(IStrategy):
    plot_config = {
        "main_plot": {
            "enter_tag": {
                "color": "#197260"
            },
            'sma5_1d': {
                'color': 'green'
            },
            'bb_upperband_1d': {
                'color': 'blue'
            },
            'bb_lowerband_1d': {
                'color': 'red'
            }
        },
        "subplots": {
            "Hammer": {
                "hammer": {
                    "color": "blue"
                },
                "loose_hammer": {
                    "color": "#c1b255"
                },
                "hammer_1h": {
                    "color": "blue"
                },
                "loose_hammer_1h": {
                    "color": "#c1b255"
                },
                "hammer_1d": {
                    "color": "blue"
                },
                "loose_hammer_1d": {
                    "color": "#c1b255"
                }
            },
            'Percent': {
                'percent3_1d': {
                    "color": 'pink'
                },
                'percent3': {
                    "color": 'red'
                },
                'percent5': {
                    "color": 'green'
                },
                'percent12': {
                    "color": 'blue'
                },
                'percent48': {
                    "color": 'yellow'
                }
            }

        }
    }
    minimal_roi = {
        "0": 5
    }
    # Regrouper toutes les informations dans un seul dictionnaire
    pairs = {
        pair: {
            "last_max": 0,
            "trade_info": {},
            "max_touch": 0.0,
            "last_sell": 0.0,
            "last_buy": 0.0
        }
        for pair in ["BTC/USDT", "ETH/USDT", "DOGE/USDT", "DASH/USDT", "XRP/USDT", "SOL/USDT"]
    }

    stoploss = -1
    timeframe = '1m'
    position_adjustment_enable = True
    columns_logged = False
    max_entry_position_adjustment = 20
    startup_candle_count = 288

    # def new_adjust_trade_position(self, trade, current_time, current_rate, current_profit, min_stake, max_stake,
    #                           **kwargs) -> float:
    #     dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
    #     last_candle = dataframe.iloc[-1].squeeze()
    #
    #     count_of_buys, hours, days, first_price, last_price = self.getTradeInfos(current_time, trade)
    #
    #     # Initialisation des user_data (backtest compatible)
    #     if 'dynamic_stoploss' not in trade.user_data:
    #         trade.user_data['dynamic_stoploss'] = first_price * 0.98  # Stoploss initial à -2%
    #
    #     if hours < 1 or trade.stake_amount >= max_stake:
    #         return 0
    #
    #     # Ajustement en cas de perte : renfort à la baisse
    #     if (last_candle['close'] < first_price) and (last_candle['touch_support']) and (current_profit < -0.015 * count_of_buys):
    #         additional_stake = self.config['stake_amount']
    #         print(f"Adjust Loss - {current_time} rate={current_rate:.3f} stake={additional_stake} count={count_of_buys}")
    #         return max(additional_stake, 0)
    #
    #     # Ajustement en cas de gain : renfort à la hausse
    #     if (last_candle['close'] > first_price) and (current_profit > 0.01 * count_of_buys):
    #         additional_stake = self.config['stake_amount']
    #
    #         # Mise à jour du stoploss dynamique (on lock un profit partiel par exemple)
    #         new_stoploss = current_rate * 0.99  # Stoploss dynamique à -1% sous le prix actuel
    #         trade.user_data['dynamic_stoploss'] = max(trade.user_data['dynamic_stoploss'], new_stoploss)
    #
    #         print(f"Adjust Gain + {current_time} rate={current_rate:.3f} stake={additional_stake} count={count_of_buys}")
    #         return max(additional_stake, 0)
    #
    #     return 0

    def adjust_trade_position(self, trade, current_time, current_rate, current_profit, min_stake, max_stake,
                              **kwargs) -> float:
        if trade.has_open_orders:
            return None

        dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        last_candle_288 = dataframe.iloc[-288].squeeze()
        """
        Ajuste la position suite à un signal de sortie partielle.
        """
        count_of_buys, hours, days, first_price, last_price = self.getTradeInfos(current_time, trade)
        # (hours < 1) or
        if (self.wallets.get_available_stake_amount() < 50): # or trade.stake_amount >= max_stake:
            return 0

        dispo = round(self.wallets.get_available_stake_amount())

        factor = 1
        if (count_of_buys > 4):
            factor = count_of_buys / 4

        if (count_of_buys > 1) \
                and (current_profit > 0.01) \
                and (last_candle['haclose'] < self.pairs[trade.pair]['max_touch'] * 0.99) \
                and (last_candle['percent5'] < 0) and (last_candle['percent12'] < 0):
            # print(f"Adjust Sell all {current_time} rate={current_rate:.3f} stake={trade.stake_amount} count={count_of_buys} profit={profit:.1f}")

            self.log_trade(
                last_candle=last_candle,
                date=current_time,
                action="Sell All",
                dispo=dispo,
                pair=trade.pair,
                rate=current_rate,
                trade_type='Sell',
                profit=round(current_profit, 4),  # round(current_profit * trade.stake_amount, 2),
                buys=trade.nr_of_successful_entries,
                stake=round(- trade.stake_amount, 2)
            )
            self.pairs[trade.pair]['last_max'] = max(last_candle['haclose'], self.pairs[trade.pair]['last_max'])
            self.pairs[trade.pair]['max_touch'] = last_candle['haclose']
            self.pairs[trade.pair]['last_buy'] = last_candle['haclose']
            self.pairs[trade.pair]['last_sell'] = 0
            return - trade.stake_amount

        if (last_candle['close'] < first_price) \
                and (last_candle['touch_support']) \
                and ((count_of_buys <= 4 and last_candle_288['sma5_1h'] <= last_candle['sma5_1h'])
                     or (count_of_buys > 4 and last_candle_288['sma5_1d'] <= last_candle['sma5_1d'])) \
                and (current_profit < -0.015 * count_of_buys * factor):
            additional_stake = self.calculate_stake(trade.pair, last_candle, factor)

            # print(f"Adjust Loss - {current_time} rate={current_rate:.3f} stake={additional_stake} count={count_of_buys} profit={profit:.1f}")
            self.log_trade(
                last_candle=last_candle,
                date=current_time,
                action="Loss -",
                dispo=dispo,
                pair=trade.pair,
                rate=current_rate,
                trade_type='Decrease',
                profit=round(current_profit, 4),  # round(current_profit * trade.stake_amount, 2),
                buys=trade.nr_of_successful_entries,
                stake=round(additional_stake, 2)
            )
            self.pairs[trade.pair]['last_max'] = last_candle['haclose']
            self.pairs[trade.pair]['max_touch'] = last_candle['haclose']
            self.pairs[trade.pair]['last_buy'] = 0
            self.pairs[trade.pair]['last_sell'] = last_candle['haclose']
            return max(additional_stake, 0)
        
        if (last_candle['close'] > first_price) and (current_profit > 0.01 * count_of_buys):
            additional_stake = self.calculate_stake(trade.pair, last_candle, 1)
            self.log_trade(
                last_candle=last_candle,
                date=current_time,
                dispo=dispo,
                action="Gain +",
                rate=current_rate,
                pair=trade.pair,
                trade_type='Increase',
                profit=round(current_profit, 2),
                buys=count_of_buys,
                stake=round(additional_stake, 2)
            )
            self.pairs[trade.pair]['last_max'] = last_candle['haclose']
            self.pairs[trade.pair]['max_touch'] = last_candle['haclose']
            self.pairs[trade.pair]['last_buy'] = last_candle['haclose']
            self.pairs[trade.pair]['last_sell'] = 0
            # print(f"Adjust Gain + {current_time} rate={current_rate:.3f} stake={additional_stake} count={count_of_buys} profit={profit:.1f}")
            return max(additional_stake, 0)

        return 0

    use_custom_stoploss = True

    # def new_custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
    #                    current_profit: float, **kwargs) -> float:
    #
    #     if 'dynamic_stoploss' in trade.user_data:
    #         stoploss_price = trade.user_data['dynamic_stoploss']
    #         if current_rate < stoploss_price:
    #             print(f"Stoploss touché ! Vente forcée {pair} à {current_rate}")
    #             return 0.001  # on force une sortie immédiate (stop très proche)
    #
    #     # Sinon on reste sur le stoploss standard de la stratégie
    #     return -1  # Exemple: 5% de perte max

    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                            proposed_stake: float, min_stake: float, max_stake: float,
                            **kwargs) -> float:
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)

        # Obtenir les données actuelles pour cette paire
        last_candle = dataframe.iloc[-1].squeeze()
        return self.calculate_stake(pair, last_candle, 1)

    # def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
    #                     current_rate: float, current_profit: float, **kwargs) -> float:
    #     dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
    #
    #     # Obtenir les données actuelles pour cette paire
    #     last_candle = dataframe.iloc[-1].squeeze()
    #     # self.getTradeInfos(current_time, trade)
    #     # print(f"current_profit={current_profit} mises=" + str(round(self.pairs[pair]['trade_info']['mises'], 4)))
    #     limit_sell = (last_candle['close'] - self.pairs[trade.pair]['max_touch']) / self.pairs[trade.pair]['max_touch']
    #
    #     if (current_profit > 0.01) & (limit_sell < -0.01) & (last_candle['percent12'] < 0): # & (limit_sell < -0.01) & (last_candle['DI+_1h'] < 10):
    #         sl_profit = 0.85 * current_profit  # n% du profit en cours
    #         print(f"Stoploss {current_time} {current_rate} set to {sl_profit} / {limit_sell} / {self.pairs[trade.pair]['max_touch']}")
    #
    #     else:
    #         sl_profit = -1  # Hard stop-loss
    #     stoploss = stoploss_from_open(sl_profit, current_profit)
    #     return stoploss

    def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
                    current_profit: float, **kwargs) -> 'Optional[Union[str, bool]]':
        """
        Custom exit function for dynamic trade exits.
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        # Calcul de la "distance de sécurité" avant stockage dans max_touch
        limit_sell = (last_candle['haclose'] - self.pairs[pair]['max_touch']) / self.pairs[pair]['max_touch']

        self.pairs[pair]['max_touch'] = max(last_candle['haclose'], self.pairs[pair]['max_touch'])

        # On ne déclenche le trailing stop que si un profit mini a déjà été atteint
        # and (limit_sell < -0.01)
        if (current_profit > 0.01) and (last_candle['percent12'] < 0) and (last_candle['percent5'] < 0):
            print(f"Custom Exit Triggered - {current_time} - Price: {current_rate:.2f} - Profit: {current_profit:.2%}")
            print(f"Max touch: {self.pairs[pair]['max_touch']:.2f}, Limit sell: {limit_sell:.2%}")

            return 'trailing_stop_exit'

        return None

    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], **kwargs) -> bool:
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        dispo = round(self.wallets.get_available_stake_amount())
        stake_amount = self.calculate_stake(pair, last_candle, 1)

        # if (self.pairs[pair]['last_sell'] > 0) and \
        #         (self.pairs[pair]['last_sell'] - last_candle['close']) / self.pairs[pair]['last_sell'] < 0.012:
        #     return False
        self.pairs[pair]['last_max'] = max(last_candle['haclose'], self.pairs[pair]['last_max'])
        self.pairs[pair]['max_touch'] = last_candle['haclose']
        self.pairs[pair]['last_buy'] = last_candle['haclose']
        self.pairs[pair]['last_sell'] = 0
        #print(f"Buy {current_time} {entry_tag} rate={rate:.3f} amount={amount}")
        self.log_trade(
            last_candle=last_candle,
            date=current_time,
            action="START BUY",
            pair=pair,
            rate=rate,
            dispo=dispo,
            profit=0,
            stake=round(stake_amount, 2)
        )
        return True

    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, **kwargs) -> bool:

        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        dispo = round(self.wallets.get_available_stake_amount())

        allow_to_sell = (last_candle['percent5'] < -0.00)
        ok = (allow_to_sell) | (exit_reason == 'force_exit')
        if ok:
            # self.pairs[pair]['last_max'] = 0
            # self.pairs[pair]['max_touch'] = 0
            self.pairs[pair]['last_buy'] = 0
            self.pairs[pair]['last_sell'] = rate
            self.log_trade(
                last_candle=last_candle,
                date=current_time,
                action="Sell",
                pair=pair,
                trade_type=exit_reason,
                rate=last_candle['close'],
                dispo=dispo,
                profit=round(trade.calc_profit(rate, amount), 2)
            )
            #print(f"Sell {current_time} {exit_reason} rate={rate:.3f} amount={amount} profit={amount * rate:.3f}")

        return ok

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        heikinashi = qtpylib.heikinashi(dataframe)

        dataframe['haopen'] = heikinashi['open']
        dataframe['haclose'] = heikinashi['close']
        dataframe['hapercent'] = dataframe['haclose'].pct_change()
        
        dataframe['hammer'] = ta.CDLHAMMER(dataframe['open'], dataframe['high'], dataframe['low'], dataframe['close'])
        dataframe['inv_hammer'] = ta.CDLINVERTEDHAMMER(dataframe['open'], dataframe['high'], dataframe['low'],
                                                       dataframe['close'])
        # Volume
        dataframe['volume_mean'] = ta.SMA(dataframe['volume'], timeperiod=20)
        dataframe['volume_above_avg'] = dataframe['volume'] > 1.2 * dataframe['volume_mean']

        # RSI
        dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
        dataframe['rsi_low'] = dataframe['rsi'] < 30
        dataframe['rsi_high'] = dataframe['rsi'] > 70

        # Support / Résistance
        dataframe['lowest_20'] = dataframe['low'].rolling(window=20).min()
        dataframe['highest_20'] = dataframe['high'].rolling(window=20).max()
        dataframe['touch_support'] = dataframe['low'] <= dataframe['lowest_20']
        dataframe['touch_resistance'] = dataframe['high'] >= dataframe['highest_20']

        # MACD
        macd = pdta.macd(dataframe['close'])
        # dataframe['macd'] = macd['macd']
        # dataframe['macdsignal'] = macd['macdsignal']
        # dataframe['macdhist'] = macd['macdhist']
        dataframe['macd'] = macd['MACD_12_26_9']
        dataframe['macdsignal'] = macd['MACDs_12_26_9']
        dataframe['macdhist'] = macd['MACDh_12_26_9']

        # Bollinger Bands
        bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
        dataframe['bb_lowerband'] = bollinger['lower']
        dataframe['bb_middleband'] = bollinger['mid']
        dataframe['bb_upperband'] = bollinger['upper']

        dataframe['touch_bb_lower'] = dataframe['low'] <= dataframe['bb_lowerband']

        # ADX (Trend Force)
        dataframe['adx'] = ta.ADX(dataframe['high'], dataframe['low'], dataframe['close'], timeperiod=14)
        # ATR
        dataframe['atr'] = ta.ATR(dataframe['high'], dataframe['low'], dataframe['close'], timeperiod=14)

        # Ratio mèche/corps (manche)
        dataframe['candle_length'] = dataframe['high'] - dataframe['low']
        dataframe['candle_body'] = abs(dataframe['close'] - dataframe['open'])
        dataframe['wick_ratio'] = dataframe['candle_length'] / dataframe['candle_body']

        dataframe["percent"] = dataframe['close'].pct_change()
        dataframe["percent3"] = dataframe['close'].pct_change(3)
        dataframe["percent5"] = dataframe['close'].pct_change(5)
        dataframe["percent12"] = dataframe['close'].pct_change(12)
        dataframe["percent48"] = dataframe['close'].pct_change(48)

        dataframe = self.pattern_hammer(dataframe)
        dataframe = self.detect_hammer_with_context(dataframe)
        dataframe = self.detect_loose_hammer(dataframe)
        #dataframe = self.detect_squeeze_pump(dataframe)

        # ======================================================================================
        ################### INFORMATIVE 1h
        informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1h")
        # informative['hammer'] = ta.CDLHAMMER(informative['open'], informative['high'], informative['low'], informative['close'])
        informative = self.detect_loose_hammer(informative)
        informative = self.detect_hammer_with_context(informative)
        bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(informative), window=20, stds=2)
        informative['bb_lowerband'] = bollinger['lower']
        informative['bb_middleband'] = bollinger['mid']
        informative['bb_upperband'] = bollinger['upper']
        informative['sma5'] = ta.SMA(informative, timeperiod=5)
        informative["percent3"] = informative['close'].pct_change(3)
        dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1h", ffill=True)

        # ======================================================================================
        ################### INFORMATIVE 1d
        informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1d")
        # informative['hammer'] = ta.CDLHAMMER(informative['open'], informative['high'], informative['low'], informative['close'])
        informative = self.detect_loose_hammer(informative)
        informative = self.detect_hammer_with_context(informative)
        informative['sma5'] = ta.SMA(informative, timeperiod=5)
        informative["percent3"] = informative['sma5'].pct_change(3)

        bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(informative), window=20, stds=2)
        informative['bb_lowerband'] = bollinger['lower']
        informative['bb_middleband'] = bollinger['mid']
        informative['bb_upperband'] = bollinger['upper']

        dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1d", ffill=True)

        dataframe['hammer_marker'] = np.where(dataframe['hammer_signal'], dataframe['low'] * 0.99, np.nan)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (dataframe['hammer'] > 0) & False
            # & (dataframe['close'] < dataframe['bb_middleband'])
            # & (dataframe['volume_above_avg'])
            # & (dataframe['rsi_low'])
            # & (dataframe['touch_support'] | dataframe['touch_bb_lower']) # Support ou BB inférieure
            # & (dataframe['wick_ratio'] > 2)  # Manche >= 2x corps
            # (dataframe['adx'] < 30) &  # Éviter les tendances trop fortes
            # (dataframe['macd'] > dataframe['macdsignal'])
            ,  # Divergence possible
            ['enter_long', 'enter_tag']] = [1, 'buy_hammer']

        # d ataframe.loc[
        #     (dataframe['hammer2'] > 0)
        #     # & (dataframe['close'] < dataframe['bb_middleband'])
        #     # (dataframe['volume_above_avg']) &
        #     # (dataframe['rsi_low']) &
        #     # & (dataframe['touch_support'] | dataframe['touch_bb_lower']) # Support ou BB inférieure
        #     # (dataframe['wick_ratio'] > 2) &  # Manche >= 2x corps
        #     # (dataframe['adx'] < 30) &  # Éviter les tendances trop fortes
        #     # (dataframe['macd'] > dataframe['macdsignal'])
        #     ,  # Divergence possible
        #     ['enter_long', 'enter_tag']] = [1, 'buy_hammer2']
        dataframe.loc[
            (dataframe['percent3'] < - 0.005)
            &  (dataframe['percent48'] < 0.02)
            ,  # Divergence possible
            ['enter_long', 'enter_tag']] = [1, 'buy_loose_hammer']

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # dataframe.loc[
        #     (dataframe['inv_hammer'] > 0)
        #     # (dataframe['volume_above_avg']) &
        #     # (dataframe['rsi_high']) &
        #     # (dataframe['touch_resistance'] | (dataframe['high'] >= dataframe['bb_upperband'])) &
        #     # (dataframe['wick_ratio'] > 2) &
        #     # (dataframe['adx'] < 30) &
        #     # (dataframe['macd'] < dataframe['macdsignal'])
        #     ,
        #     ['exit_long', 'exit_tag']] = [1, 'sell_hammer']
        return dataframe

    def getTradeInfos(self, current_time, trade):
        filled_buys = trade.select_filled_orders('buy')
        count_of_buys = len(filled_buys)
        first_price = filled_buys[0].price
        days = 0
        minutes = 0
        hours = 0
        last_price = first_price
        mises=0
        for buy in filled_buys:
            minutes = (current_time - buy.order_date_utc).seconds / 60
            hours = round(minutes / 60, 0)
            days = (current_time - buy.order_date_utc).days
            last_price = buy.price
            mises += buy.amount * buy.price
        # self.pairs[trade.pair]['trade_info'] = {
        #     "count_of_buys": count_of_buys,
        #     "hours": hours,
        #     "days": days,
        #     "minutes": minutes,
        #     "first_price": first_price,
        #     "last_price": last_price,
        #     "mises": mises
        # }
        return count_of_buys, hours, days, first_price, last_price

    def informative_pairs(self):
        # get access to all pairs available in whitelist.
        pairs = self.dp.current_whitelist()
        informative_pairs = [(pair, '1d') for pair in pairs]
        informative_pairs += [(pair, '1h') for pair in pairs]

        return informative_pairs

    # def pattern_hammer(self, df: DataFrame) -> DataFrame:
    #     """
    #     Expected df contains Open, High, Low, Close,
    #     """
    #     # Compute percentile
    #     for level in [50, 90]:
    #         df[f'{level}_percentile'] = df[['high', 'low']].apply(lambda x: np.percentile(x, q=level),
    #                                                               axis=1)
    #
    #     condition = ((df['open'].values >= df[
    #         '50_percentile'].values)  # open larger then 50 percentile, i.e. at the upper half
    #                  & (df['close'].values >= df['90_percentile'].values)  # close larger then 90 percentile, i.e. at the top of candlestick
    #                  & (df['close'].values >= df['open'].values)  # bullish candlestick
    #                  )
    #
    #     df['hammer2'] = np.where(condition, 1, 0)
    #     return df

    def pattern_hammer(self, df: DataFrame) -> DataFrame:
        lower_shadow = np.minimum(df['open'], df['close']) - df['low']
        upper_shadow = df['high'] - np.maximum(df['open'], df['close'])
        body = abs(df['close'] - df['open'])

        df['hammer2'] = (
                (lower_shadow > 2 * body) &  # Longue mèche basse
                (upper_shadow < body) &  # Faible mèche haute
                (df['close'] > df['open']) &  # Bougie verte
                ((df['close'] - df['low']) / (df['high'] - df['low']) > 0.75)  # Clôture dans le quart supérieur
        ).astype(int)

        return df

    def detect_hammer_with_context(self, df: DataFrame) -> DataFrame:
        """
        Détection d'un marteau validé par :
        - Structure de la bougie (marteau classique)
        - Volume anormalement haut (signale l'intérêt du marché)
        - Divergence RSI (momentum qui se retourne)

        """

        # === Détection du marteau ===
        lower_shadow = np.minimum(df['open'], df['close']) - df['low']
        upper_shadow = df['high'] - np.maximum(df['open'], df['close'])
        body = abs(df['close'] - df['open'])

        df['hammer'] = (
            (lower_shadow > 2 * body) &          # Longue mèche basse
            (upper_shadow < body) &               # Faible mèche haute
            (df['close'] > df['open']) &          # Bougie verte
            ((df['close'] - df['low']) / (df['high'] - df['low']) > 0.75)  # Clôture en haut de la bougie
        ).astype(int)

        # === Filtre sur le volume ===
        df['volume_mean'] = df['volume'].rolling(window=20).mean()
        df['high_volume'] = df['volume'] > 1.5 * df['volume_mean']

        # === RSI pour la divergence ===
        df['rsi'] = ta.RSI(df['close'], timeperiod=14)

        df['rsi_lowest'] = df['rsi'].rolling(window=5).min()  # Cherche un creux récent de RSI
        df['price_lowest'] = df['close'].rolling(window=5).min()

        # Divergence haussière = prix fait un nouveau plus bas, mais RSI remonte
        df['bullish_divergence'] = (
            (df['low'] < df['low'].shift(1)) &
            (df['rsi'] > df['rsi'].shift(1)) &
            (df['rsi'] < 30)  # Survendu
        )

        # === Condition finale : marteau + contexte favorable ===
        df['hammer_signal'] = (
            df['hammer'] &
            df['high_volume'] &
            df['bullish_divergence']
        ).astype(int)

        return df
    
    def detect_loose_hammer(self, df: DataFrame) -> DataFrame:
        """
        Détection large de marteaux : accepte des corps plus gros, ne vérifie pas le volume,
        ne demande pas de divergence, juste un pattern visuel simple.
        """

        body = abs(df['close'] - df['open'])
        upper_shadow = abs(df['high'] - np.maximum(df['close'], df['open']))
        lower_shadow = abs(np.minimum(df['close'], df['open']) - df['low'])

        # Critères simplifiés :
        df['loose_hammer'] = (
            (lower_shadow > body * 2.5) & # mèche basse > 1.5x corps
            (upper_shadow < body) # petite mèche haute
            # (df['close'] > df['open']) # bougie verte (optionnel, on peut prendre aussi les rouges)
        ).astype(int)
        df['won_hammer'] = (
            (upper_shadow > body * 2.5) & # mèche basse > 1.5x corps
            (lower_shadow < body) # petite mèche haute
            # (df['close'] > df['open']) # bougie verte (optionnel, on peut prendre aussi les rouges)
        ).astype(int)

        return df

    def detect_squeeze_pump(self, dataframe: DataFrame) -> DataFrame:
        """
        Détecte un pump vertical violent, pour éviter d'acheter dans une phase de distribution ultra risquée.
        """
        # Ratio volume par rapport à la moyenne mobile
        dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume'].rolling(20).mean()

        dataframe['bb_upper_dist'] = (dataframe['close'] - dataframe['bb_upperband']) / dataframe['bb_upperband']

        # Bougie ultra verticale
        dataframe['candle_pct_change'] = (dataframe['close'] - dataframe['open']) / dataframe['open']

        # ATR pour détecter la volatilité excessive
        dataframe['atr_ratio'] = dataframe['atr'] / dataframe['close']

        # Condition de détection (à ajuster selon la pair et le marché)
        dataframe['squeeze_alert'] = (
            (dataframe['volume_ratio'] > 5) &  # volume X5 ou plus
            (dataframe['candle_pct_change'] > 0.05) &  # bougie verte de +5% ou plus
            (dataframe['bb_upper_dist'] > 0.03)  # ferme largement au-dessus de la BB supérieure
        )

        return dataframe

    def log_trade(self, action, pair, date, trade_type=None, rate=None, dispo=None, profit=None, buys=None, stake=None, last_candle=None):
        # Afficher les colonnes une seule fois
        if self.config.get('runmode') == 'hyperopt':
            return
        if not self.columns_logged:
            print(
                f"| {'Date':<16} | {'Action':<10} | {'Pair':<10} | {'Trade Type':<18} | {'Rate':>12} | {'Dispo':>6} | {'Profit':>8} | {'Pct':>5} | {'max7_1d':>11} | {'max_touch':>12} | {'last_max':>12} | {'Buys':>5} | {'Stake':>10} |"
            )
            print(
                f"|{'-' * 18}|{'-' * 12}|{'-' * 12}|{'-' * 20}|{'-' * 14}|{'-' * 8}|{'-' * 10}|{'-' * 7}|{'-' * 13}|{'-' * 14}|{'-' * 14}|{'-' * 7}|{'-' * 12}|"
            )
            self.columns_logged = True
        date = str(date)[:16] if date else "-"
        limit = None
        # if buys is not None:
        #     limit = round(last_rate * (1 - self.fibo[buys] / 100), 4)

        rsi = ''
        rsi_pct = ''
        # if last_candle is not None:
        #     if (not np.isnan(last_candle['rsi_1d'])) and (not np.isnan(last_candle['rsi_1h'])):
        #         rsi = str(int(last_candle['rsi_1d'])) + " " + str(int(last_candle['rsi_1h']))
        #     if (not np.isnan(last_candle['rsi_pct_1d'])) and (not np.isnan(last_candle['rsi_pct_1h'])):
        #         rsi_pct = str(int(10000 * last_candle['bb_mid_pct_1d'])) + " " + str(
        #             int(last_candle['rsi_pct_1d'])) + " " + str(int(last_candle['rsi_pct_1h']))

        # first_rate = self.percent_threshold.value
        # last_rate = self.threshold.value
        # action = self.color_line(action, action)
        sma5_1d = ''
        sma5_1h = ''
        # if last_candle['sma5_pct_1d'] is not None:
        #     sma5_1d = round(last_candle['sma5_pct_1d'] * 100, 2)
        # if last_candle['sma5_pct_1h'] is not None:
        #     sma5_1h = round(last_candle['sma5_pct_1h'] * 100, 2)
        sma5 = str(sma5_1d) + ' ' + str(sma5_1h)
        first_rate = self.pairs[pair]['last_max']

        # if action != 'Sell':
        #     profit = round((last_candle['close'] - self.pairs[pair]['last_max']) / self.pairs[pair]['last_max'], 2)

        limit_sell = rsi_pct  # round((last_candle['close'] - self.pairs[pair]['last_max']) / self.pairs[pair]['last_max'], 4)
        max7_1d = round(self.pairs[pair]['max_touch'], 1) #last_candle['max7_1d'] #round(100 * (last_candle['close'] - self.pairs[pair]['last_max']) / self.pairs[pair]['last_max'], 1)
        pct_max = round(100 * (last_candle['close'] - max7_1d) / max7_1d, 1)
        print(
            f"| {date:<16} | {action:<10} | {pair:<10} | {trade_type or '-':<18} | {rate or '-':>12} | {dispo or '-':>6} | {profit or '-':>8} | {pct_max or '-':>5} | {max7_1d or '-':>11} | {round(self.pairs[pair]['max_touch'], 2) or '-':>12} | {round(self.pairs[pair]['last_max'],2) or '-':>12} | {buys or '-':>5} | {stake or '-':>10} |"
        )

    def calculate_stake(self, pair, last_candle, factor=1):
        amount = self.config['stake_amount'] * factor #1000 / self.first_stack_factor.value self.protection_stake_amount.value  #
        return amount

    @property
    def protections(self):
        return [
            {
                "method": "CooldownPeriod",
                "stop_duration_candles": 12
            }
            # {
            #     "method": "MaxDrawdown",
            #     "lookback_period_candles": self.lookback.value,
            #     "trade_limit": self.trade_limit.value,
            #     "stop_duration_candles": self.protection_stop.value,
            #     "max_allowed_drawdown": self.protection_max_allowed_dd.value,
            #     "only_per_pair": False
            # },
            # {
            #     "method": "StoplossGuard",
            #     "lookback_period_candles": 24,
            #     "trade_limit": 4,
            #     "stop_duration_candles": self.protection_stoploss_stop.value,
            #     "only_per_pair": False
            # },
            # {
            #     "method": "StoplossGuard",
            #     "lookback_period_candles": 24,
            #     "trade_limit": 4,
            #     "stop_duration_candles": 2,
            #     "only_per_pair": False
            # },
            # {
            #     "method": "LowProfitPairs",
            #     "lookback_period_candles": 6,
            #     "trade_limit": 2,
            #     "stop_duration_candles": 60,
            #     "required_profit": 0.02
            # },
            # {
            #     "method": "LowProfitPairs",
            #     "lookback_period_candles": 24,
            #     "trade_limit": 4,
            #     "stop_duration_candles": 2,
            #     "required_profit": 0.01
            # }
        ]