from datetime import timedelta, datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, stoploss_from_open,
                                IntParameter, IStrategy, merge_informative_pair, informative, stoploss_from_absolute)
import pandas as pd
import numpy as np
from pandas import DataFrame
from typing import Optional, Union, Tuple
from scipy.signal import find_peaks

import logging
import configparser
from technical import pivots_points
# --------------------------------

# Add your lib to import here
import ta
import talib.abstract as talib
import freqtrade.vendor.qtpylib.indicators as qtpylib
import requests

import joblib
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.metrics import accuracy_score
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error

logger = logging.getLogger(__name__)
# Configuration du logger
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
)

from tabulate import tabulate


def pprint_df(dframe):
    print(tabulate(dframe, headers='keys', tablefmt='psql', showindex=False))


def normalize(df):
    df = (df - df.min()) / (df.max() - df.min())
    return df


def get_limit_from_config(section, pair):
    file_path = '/HOME/home/souti/freqtrade2/user_data/strategies/Thor_001.txt'
    # Créez un objet ConfigParser
    config = configparser.ConfigParser()

    try:
        # Lisez le fichier avec les valeurs
        config.read(file_path)

        # Vérifiez si la section existe
        if config.has_section(section):
            # Obtenez les valeurs à partir de la section et de la clé (pair)
            limit = config.get(section, pair)
            return limit
        # else:
        #    raise ValueError(f"La section '{section}' n'existe pas dans le fichier de configuration.")
    except Exception as e:
        print(f"Erreur lors de la lecture du fichier de configuration : {e}")
        return None


# Order(
#     id=123456,
#     pair='BTC/USDT',
#     order_type='limit',
#     side='buy',
#     price=45000.0,
#     amount=0.1,
#     filled=0.1,
#     status='closed',
#     order_date=datetime(2023, 5, 12, 14, 30, 0),  # Date de création de l'ordre
#     exchange_order_id='abcdef123456',
#     fee=0.0005,
#     fee_currency='BTC'
# )

class Thor_001(IStrategy):
    levels = [1, 2, 2, 4, 6, 8]

    # ROI table:
    minimal_roi = {
        "0": 10,
        # "567": 0.273,
        # "2814": 0.12,
        # "7675": 0.05
    }

    min_max_buys = {
        "BTC/USDT": {"min": 1000000, "max": 0, "profit": 0.01, 'last_rsi_1d': 0, 'last_sell_price': 1000000},
        "ETH/USDT": {"min": 1000000, "max": 0, "profit": 0.01, 'last_rsi_1d': 0, 'last_sell_price': 1000000},
        "DOGE/USDT": {"min": 1000000, "max": 0, "profit": 0.02, 'last_rsi_1d': 0, 'last_sell_price': 1000000},
        "XRP/USDT": {"min": 1000000, "max": 0, "profit": 0.02, 'last_rsi_1d': 0, 'last_sell_price': 1000000},
        "SOL/USDT": {"min": 1000000, "max": 0, "profit": 0.02, 'last_rsi_1d': 0, 'last_sell_price': 1000000},
        "DASH/USDT": {"min": 1000000, "max": 0, "profit": 0.02, 'last_rsi_1d': 0, 'last_sell_price': 1000000}
    }

    last_sell = {
        "BTC/USDT": 0,
        "ETH/USDT": 0,
        "DOGE/USDT": 0,
        "DASH/USDT": 0,
        "XRP/USDT": 0,
        "SOL/USDT": 0
    }
    # Hyperparameters
    sell_max_threshold = 5.0  # % sous le max où vendre
    sell_min_threshold = 1.0

    close = 'close'
    open = 'open'

    # Stoploss:
    stoploss = -0.10  # 0.256
    # Custom stoploss
    use_custom_stoploss = False

    # Buy hypers
    timeframe = '5m'

    max_open_trades = 5
    max_amount = 40

    startup_candle_count = 26 

    # DCA config
    position_adjustment_enable = True

    plot_config = {
        "main_plot": {
            "min200": {
                "color": "#86c932"
            },
            "max50": {
                "color": "white"
            },
            "max200": {
                "color": "yellow"
            },
            "max_previous_1h": {
                "color": "#da59a6"},
            "min_previous_1h": {
                "color": "#da59a6",
            },
            "sma5_1h": {
                "color": "red",
            },
            "close_1d": {
                "color": "yellow"
            }
        },
        "subplots": {
            "Rsi": {
                "rsi": {
                    "color": "pink"
                },
                "rsi_1h": {
                    "color": "yellow"
                },
                "rsi_sma_1h": {
                    "color": "yellow"
                }
            },
            "Pct": {
                "percent": {
                    "color": "blue"
                },
                "percent3": {
                    "color": "green"
                },
                "percent12": {
                    "color": "yellow"
                },
                "percent24": {
                    "color": "pink"
                },
                "sma5_pct_1h": {
                    "color": "red",
                }
            }
        }
    }

    # 20 20 40   60 100 160 260 420
    # 50 50 100 300 500
    # fibo = [1, 1, 2, 3, 5, 8, 13, 21]
    # my fibo
    # 50 50 50 100 100 150 200 250 350 450 600 1050
    fibo = [1, 1, 1, 2, 2, 3, 4, 5, 7, 9, 12, 16, 21]
    baisse = [1, 2, 3, 5, 7, 10, 14, 19, 26, 35, 47, 63, 84]
    # Ma suite	1	1	1	2	2	3	4	5	7	9	12	16	21
    # Mise	50	50	50	100	100	150	200	250	350	450	600	800	1050
    # Somme Mises	50	100	150	250	350	500	700	950	1300	1750	2350	3150	4200
    # baisse	1	2	3	5	7	10	14	19	26	35	47	63	84

    trades = list()
    max_profit_pairs = {}

    profit_b_no_change = BooleanParameter(default=True, space="sell")
    profit_b_quick_lost = BooleanParameter(default=True, space="sell")
    profit_b_sma5 = BooleanParameter(default=True, space="sell")
    profit_b_sma10 = BooleanParameter(default=True, space="sell")
    profit_b_sma20 = BooleanParameter(default=True, space="sell")
    profit_b_quick_gain = BooleanParameter(default=True, space="sell")
    profit_b_quick_gain_3 = BooleanParameter(default=True, space="sell")
    profit_b_old_sma10 = BooleanParameter(default=True, space="sell")
    profit_b_very_old_sma10 = BooleanParameter(default=True, space="sell")
    profit_b_over_rsi = BooleanParameter(default=True, space="sell")
    profit_b_short_loss = BooleanParameter(default=True, space="sell")

    sell_b_percent = DecimalParameter(0, 0.02, decimals=3, default=0.01, space='sell')
    sell_b_percent3 = DecimalParameter(0, 0.02, decimals=3, default=0.01, space='sell')
    sell_b_candels = IntParameter(0, 48, default=12, space='sell')

    sell_b_too_old_day = IntParameter(0, 10, default=300, space='sell')
    sell_b_too_old_percent = DecimalParameter(0, 0.02, decimals=3, default=0.01, space='sell')

    sell_b_profit_no_change = DecimalParameter(0, 0.02, decimals=3, default=0.005, space='sell')
    sell_b_profit_percent12 = DecimalParameter(0, 0.002, decimals=4, default=0.001, space='sell')

    sell_b_RSI = IntParameter(70, 98, default=88, space='sell')
    sell_b_RSI2 = IntParameter(70, 98, default=88, space='sell')
    sell_b_RSI3 = IntParameter(70, 98, default=80, space='sell')

    sell_b_RSI2_percent = DecimalParameter(0, 0.02, decimals=3, default=0.01, space='sell')
    # sell_b_expected_profit = DecimalParameter(0, 0.01, decimals=3,  default=0.01, space='sell')

    protection_percent_buy_lost = IntParameter(1, 10, default=5, space='protection')
    # protection_nb_buy_lost = IntParameter(1, 2, default=2, space='protection')

    protection_fibo = IntParameter(1, 10, default=2, space='protection')

    # trailing stoploss hyperopt parameters
    # hard stoploss profit
    pHSL = DecimalParameter(-0.200, -0.040, default=-0.08, decimals=3, space='sell', optimize=False, load=True)
    # profit threshold 1, trigger point, SL_1 is used
    pPF_1 = DecimalParameter(0.008, 0.020, default=0.016, decimals=3, space='sell', optimize=True, load=True)
    pSL_1 = DecimalParameter(0.008, 0.020, default=0.011, decimals=3, space='sell', optimize=True, load=True)

    # profit threshold 2, SL_2 is used
    pPF_2 = DecimalParameter(0.040, 0.100, default=0.080, decimals=3, space='sell', optimize=True, load=True)
    pSL_2 = DecimalParameter(0.020, 0.070, default=0.040, decimals=3, space='sell', optimize=True, load=True)

    columns_logged = False

    features = ['rsi_percent', 'rsi_percent3', 'rsi_percent5', 'bb_upperband', 'bb_lowerband', 'close',
                close, 'percent12', 'percent24', 'tag_min', 'tag_percent24', 'tag_stable', 'tag_buy']

    # Charger les modèles
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.model = None

    def train_model(self, dataframe: DataFrame):
        # Sélection des colonnes de caractéristiques (features)
        features = self.features
        target = 'future_direction'

        # Supprimer les lignes avec des valeurs NaN
        # dataframe = dataframe.dropna()
        # dataframe = dataframe.fillna("Unknown")  # Remplace NaN par "Unknown"

        # Création d'une nouvelle DataFrame contenant uniquement les colonnes spécifiées
        # dataframe = old_dataframe[self.features]
        # dataframe['future_direction'] = old_dataframe['future_direction']
        # dataframe['future_change'] = old_dataframe['future_change']
        # dataframe['rsi_pct_range'] = old_dataframe['rsi_pct_range']
        print(f"Shape of dataset: {dataframe.shape}")
        print(dataframe.head())

        dataframe = dataframe.dropna()
        #dataframe = dataframe.fillna(0)  # Remplace NaN par 0

        # Diviser les données en train (80%) et test (20%)
        X = dataframe[features]
        y = dataframe[target]
        print(f"Shape of dataset: {dataframe.shape}")
        print(dataframe.head())

        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

        # Entraîner le modèle de régression logistique
        model = LogisticRegression(max_iter=5000)
        model.fit(X_train, y_train)
        # Évaluer le modèle
        predictions = model.predict(X_test)
        accuracy = accuracy_score(y_test, predictions)
        print(f"Accuracy du modèle : {accuracy * 100:.2f}%")

        # # HistGradientBoostingClassifier est conçu pour les problèmes de classification, pas pour la régression.
        # # Si votre problème est une classification (par exemple, prédire une catégorie ou une classe),
        # # vous pouvez utiliser ce modèle.
        # # Remplacer LinearRegression par HistGradientBoostingClassifier
        # # Diviser les données
        # X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
        #
        # model = HistGradientBoostingClassifier()
        # model.fit(X_train, y_train)
        #
        # # Prédictions
        # y_pred = model.predict(X_test)
        # accuracy = accuracy_score(y_test, y_pred)
        # print(f"Accuracy: {accuracy:.2f}")

        return model

    def add_future_direction(self, dataframe: DataFrame) -> DataFrame:
        dataframe['future_change'] = dataframe['close'].shift(-12) - dataframe['close']
        dataframe['future_direction'] = dataframe['future_change'].apply(lambda x: 1 if x > 0 else 0)
        return dataframe

    def predict_with_model(self, dataframe: DataFrame, model):
        features = self.features

        # Supprimer les lignes avec des NaN

        # Prédire les probabilités
        #dataframe['prediction_prob'] = model.predict_proba(dataframe[features])[:, 1]
        #dataframe['predicted_direction'] = dataframe['prediction_prob'].apply(lambda x: 1 if x > 0.5 else 0)

        # Assurez-vous que dataframe est une copie complète
        dataframe = dataframe.copy()
        dataframe = dataframe.dropna()

        # Prédire les probabilités
        dataframe.loc[:, 'prediction_prob'] = model.predict_proba(dataframe[features])[:, 1]

        # Appliquer la direction prédite
        dataframe.loc[:, 'predicted_direction'] = (dataframe['prediction_prob'] > 0.5).astype(int)

        return dataframe

    def color_line(self, action, line):
        if "INC" in action:  # Exemple pour un prix croissant
            return f"\033[92m{line}\033[0m"  # Vert
        elif "DEC" in action:  # Exemple pour un prix décroissant
            return f"\033[91m{line}\033[0m"  # Rouge
        elif "Allow Sell" in action:  # Exemple pour une vente
            return f"\033[93m{line}\033[0m"  # Jaune
        elif "Allow To buy" in action:  # Exemple pour un achat
            return f"\033[94m{line}\033[0m"  # Bleu
        else:
            return line  # Sans modification

    def log_trade(self, action, pair, date, trade_type=None, rate=None, dispo=None, profit=None, rsi=None, minutes=None,
                  first_rate=None, last_rate=None, buys=None, stake=None):
        # Afficher les colonnes une seule fois
        if not self.columns_logged:
            print(
                f"| {'Date':<16} | {'Action':<20} | {'Pair':<10} | {'Trade Type':<18} | {'Rate':>12} | {'Dispo':>6} | {'Profit':>8} | {'RSI':>5} | {'Limit':>10} | {'First Rate':>12} | {'Last Rate':>12} | {'Buys':>5} | {'Stake':>10} |"
            )
            print(
                f"|{'-' * 18}|{'-' * 22}|{'-' * 12}|{'-' * 20}|{'-' * 14}|{'-' * 8}|{'-' * 10}|{'-' * 7}|{'-' * 12}|{'-' * 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 = (str(rsi) if rsi else '') + " " + str(rsi - self.min_max_buys[pair]['last_rsi_1d'])

        #action = self.color_line(action, action)
        print(
            f"| {date:<16} | {action:<20} | {pair:<10} | {trade_type or '-':<18} | {rate or '-':>12} | {dispo or '-':>6} | {profit or '-':>8} | {rsi or '-':>5} | {limit or '-':>10} | {first_rate or '-':>12} | {last_rate or '-':>12} | {buys or '-':>5} | {stake or '-':>10} |"
        )

    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:
        # count_buys = 0
        # trade = self.getTrade(pair)
        # if trade:
        #     filled_buys = trade.select_filled_orders('buy')
        #     count_buys = len(filled_buys)

        # print('entry_tag' + str(entry_tag))
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        # last_candle_288 = dataframe.iloc[-289].squeeze()
        # limit = get_limit_from_config('Achats', pair)

        # allow_to_buy = True #(not self.stop_all) #& (not self.all_down)

        #allow_to_buy = (last_candle['rsi_1d'] > 50) # | (last_candle['rsi_1d'] > last_candle_288['rsi_1d'])  # (rate <= float(limit)) | (entry_tag == 'force_entry')
        allow_to_buy = \
            (
                ((last_candle['ha_red_1d'] == 0) | (entry_tag == "buy_crash")) # & (last_candle['close'] < self.min_max_buys[pair]['last_sell_price'] * 0.99)
                #& (last_candle['rsi_1d'] >= 45)
            )
        self.trades = list()
        dispo = round(self.wallets.get_available_stake_amount())
        stake_amount = self.calculate_stake(pair, rate, last_candle)
        if allow_to_buy:
            self.log_trade(
                date=current_time,
                action="Allow To buy",
                pair=pair,
                trade_type=entry_tag,
                rate=rate,
                dispo=dispo,
                profit=0,
                stake=round(stake_amount, 2),
                rsi=round(last_candle['rsi_1d'])
            )
            self.min_max_buys[pair]['last_rsi_1d'] = round(last_candle['rsi_1d'])
            # logger.info(f"Allow_to_buy {allow_to_buy} {pair} {current_time} Buy={entry_tag} rate={rate} dispo={dispo} rsi_1d={rsi_1d}")

        return allow_to_buy

    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:
        # allow_to_sell = (minutes > 30)
        # limit = get_limit_from_config('Ventes', pair)
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()

        # & (last_candle['rsi_1d'] <= 50)# rate > float(limit)
        allow_to_sell = (
            last_candle['percent'] < -0.00) | (last_candle['percent3'] < - 0.00) | (40 >= last_candle['rsi'] >= 80
            ) #& (last_candle['rsi_1d'] < self.min_max_buys[pair]['last_rsi_1d'])
        string = ""
        # allow_to_sell = (last_candle['rsi_1d'] >= self.min_max_buys[pair]['last_rsi_1d'])

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

        if allow_to_sell:
            self.trades = list()
            self.log_trade(
                date=current_time,
                action="Allow Sell trade",
                pair=pair,
                trade_type=exit_reason,
                first_rate=trade.open_rate,
                rate=last_candle['close'],
                dispo=dispo,
                profit=round(trade.calc_profit(rate, amount), 2),
                rsi=round(last_candle['rsi_1d'])
            )
            self.min_max_buys[pair]['last_rsi_1d'] = round(last_candle['rsi_1d'])

        else:
            self.log_trade(
                date=current_time,
                action="Cancel Sell trade",
                pair=pair,
                trade_type=exit_reason,
                first_rate=trade.open_rate,
                rate=last_candle['close'],
                dispo=dispo,
                #profit=round(trade.calc_profit(rate, amount), 2),
                rsi=round(last_candle['rsi_1d'])
            )
        ok = (allow_to_sell) | (exit_reason == 'force_exit')
        if ok:
            self.min_max_buys[pair]['last_sell_price'] = last_candle['close']
            self.last_sell[pair] = rate
        return ok

    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=pair, timeframe=self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        # adjusted_stake_amount = self.adjust_stake_amount(pair, current_candle)

        # adjusted_stake_amount = self.calculate_stake(pair, current_rate)
        # print(f"Pour {pair}, avec une valeur actuelle de {current_rate}, la mise est de {adjusted_stake_amount:.2f}%")
        adjusted_stake_amount = self.calculate_stake(pair, current_rate, last_candle)  # self.config['stake_amount']

        # logger.info(f"{pair} adjusted_stake_amount{adjusted_stake_amount}")

        # Use default stake amount.
        return adjusted_stake_amount

    # def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float,
    #                     current_profit: float, **kwargs) -> float:
    #     # Récupérer les données pour le trade en cours
    #     dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
    #     last_candle = dataframe.iloc[-1]
    #
    #     # Heikin-Ashi values
    #     ha_close = last_candle['ha_close_1h']
    #     ha_open = last_candle['ha_open_1h']
    #     ha_low = last_candle['ha_low_1h']
    #     ha_high = last_candle['ha_high_1h']
    #
    #     # Calcul de la volatilité et du buffer dynamique
    #     ha_range = abs(ha_close - ha_open)
    #     ha_relative_diff = ha_range / ha_close if ha_close > 0 else 0
    #     stoploss_buffer = ha_relative_diff * 0.5  # Coefficient ajustable
    #
    #     # Calcul du stoploss dynamique basé sur Heikin-Ashi
    #     if ha_close > ha_open:  # Tendance haussière
    #         stoploss = ha_open * (1 - stoploss_buffer)
    #     else:  # Tendance baissière
    #         stoploss = ha_low * (1 - stoploss_buffer)
    #
    #     # Contrainte de la fourchette [95%, 98%]
    #     stoploss_min = current_rate * 0.97
    #     stoploss_max = current_rate * 0.985
    #     stoploss_final = max(stoploss_min, min(stoploss, stoploss_max))
    #
    #     return stoploss_final

    # 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)
    #     # last_candle = dataframe.iloc[-1].squeeze()
    #
    #     # # hard stoploss profit
    #     HSL = self.pHSL.value
    #     PF_1 = self.pPF_1.value
    #     SL_1 = self.pSL_1.value
    #     PF_2 = self.pPF_2.value
    #     SL_2 = self.pSL_2.value
    #
    #     # For profits between PF_1 and PF_2 the stoploss (sl_profit) used is linearly interpolated
    #     # between the values of SL_1 and SL_2. For all profits above PL_2 the sl_profit value
    #     # rises linearly with current profit, for profits below PF_1 the hard stoploss profit is used.
    #
    #     # if current_profit > PF_2:
    #     #     sl_profit = SL_2 + (current_profit - PF_2)
    #     # elif current_profit > PF_1:
    #     #     sl_profit = SL_1 + ((current_profit - PF_1) * (SL_2 - SL_1) / (PF_2 - PF_1))
    #     # else:
    #     #     sl_profit = HSL
    #
    #     filled_buys = trade.select_filled_orders('buy')
    #     count_of_buys = len(filled_buys)
    #     # first_price = filled_buys[0].price
    #
    #     days = (current_time - trade.open_date_utc).days
    #
    #     #print(f"entry_tag={trade.entry_tag} max={trade.max_rate} min={trade.min_rate} ")
    #     if current_profit > (count_of_buys / 0.6) / 100: #0.0125:
    #         sl_profit = 0.6 * current_profit  # 75% du profit en cours
    #     else:
    #         sl_profit = self.pHSL.value  # Hard stop-loss
    #
    #     stoploss = stoploss_from_open(sl_profit, current_profit)
    #
    #     # logger.info(f"{pair} {current_time} stoploss={stoploss:.4f} sl_profit={sl_profit:.4f} current_profit={current_profit:.4f} count_of_buys={count_of_buys}")
    #     return stoploss

    def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
                    current_profit: float, **kwargs) -> 'Optional[Union[str, bool]]':

        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        previous_last_candle = dataframe.iloc[-2].squeeze()
        fifth_candle = dataframe.iloc[-6].squeeze()
        if (last_candle['percent'] > 0) | (last_candle['percent3'] > 0.0): # | (last_candle['max200_diff'] > 0.02):
            return None

        # if not (pair in self.max_profit_pairs):
        #     self.max_profit_pairs[pair] = current_profit
        # self.max_profit_pairs[pair] = max(self.max_profit_pairs[pair], current_profit)
        #
        # if (self.max_profit_pairs[pair] > 0.01) & (current_profit > 0):
        #     allow_decrease = 0.2 * self.max_profit_pairs[pair]
        #     if self.max_profit_pairs[pair] - current_profit > allow_decrease:
        #         print("Max profit=" + str(self.max_profit_pairs[pair]) + ' ' + str(current_profit) + ' ' + str(
        #             allow_decrease) + ' ' + str(self.max_profit_pairs[pair] - current_profit))
        #         return 'max_lost_profit'
        # hours = (current_time - trade.open_date_utc).seconds / 3600
        #
        # if (hours >= 2) & (self.max_profit_pairs[pair] < 0.002) & (self.max_profit_pairs[pair] > - 0.002):
        #     return "no_change"

        # if (current_profit < - 0.03 ):
        #     print("Stop loss max profit=" + str(self.max_profit_pairs[pair]) + ' current=' + str(current_profit))
        #     return 'stop_loss'

        filled_buys = trade.select_filled_orders('buy')
        count_of_buys = len(filled_buys)

        expected_profit = max(self.min_max_buys[pair]['profit'], 0.01 * count_of_buys)
        # print(last_candle['buy_tag'])

        days = (current_time - trade.open_date_utc).days
        hours = (current_time - trade.open_date_utc).seconds / 3600

        # fibo_fact = fibo[hours]
        ######
        # if last_candle['percent5'] < -0.05:
        #     return "stop_loss_005"

        max_percent = 0.005  # self.min_max_buys[pair]['profit'] / 5  # last_candle['bb_width'] / 3.5  # 0.005
        max_profit = self.min_max_buys[pair]['profit']  # last_candle['bb_width'] * 3 / 4 # 0.015

        # if (current_profit > expected_profit) & \
        #         (last_candle['open'] > last_candle['max_previous_1h']) & \
        #         (last_candle['percent'] < 0) \
        #         & (last_candle[self.close] > last_candle['sma5_1h']):
        #     return 'b_max_previous'

        # if (current_profit < - 0.03) & \
        #         (last_candle['rsi_1d'] < 45) & \
        #         (last_candle['percent'] < 0):
        #     return 'b_rsi_1d'

        if (current_profit > expected_profit) & \
                (last_candle['percent12'] < -max_percent) \
                & ((current_time - trade.open_date_utc).seconds >= 3600) \
                & (last_candle[self.close] > last_candle['sma5_1h']) \
                & (80 > last_candle['rsi_1h'] < 50):
            return 'b_percent12'
        # if (current_profit > max_profit) & \
        #         ((last_candle['percent'] < - max_percent) | (last_candle['percent3'] < -max_percent) | (
        #                 last_candle['percent5'] < -max_percent)):
        #     return 'b_percent_quick'

        if self.profit_b_quick_lost.value and (current_profit >= max_profit) \
                & (last_candle['percent3'] < -max_percent) \
                & (last_candle[self.close] > last_candle['sma5_1h']) \
                & (80 > last_candle['rsi_1h'] < 50):
            return "b_quick_lost"

        # if self.profit_b_no_change.value and (current_profit > self.sell_b_profit_no_change.value) \
        #         & (last_candle['percent12'] < self.sell_b_profit_percent12.value) & (last_candle['percent5'] < 0) \
        #         & ((current_time - trade.open_date_utc).seconds >= 3600):
        #     return "b_no_change"

        if (current_profit > self.sell_b_percent.value) & (last_candle['percent3'] < - self.sell_b_percent3.value) \
                & ((current_time - trade.open_date_utc).seconds <= 300 * self.sell_b_candels.value):
            return "b_quick_gain_param"

        if self.profit_b_sma5.value:
            if (current_profit > expected_profit) \
                    & ((fifth_candle['sma5'] > last_candle['sma5']) \
                       | (last_candle['percent3'] < -expected_profit) | (
                               last_candle['percent5'] < -expected_profit)) \
                    & ((last_candle['percent'] < 0) & (last_candle['percent3'] < 0)):
                # print("over_bb_band_sma10_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
                return 'b_sma5'

        if self.profit_b_sma10.value:
            if (current_profit > expected_profit) \
                    & ((fifth_candle['sma10'] > last_candle['sma10']) \
                       | (last_candle['percent3'] < -expected_profit) | (
                               last_candle['percent5'] < -expected_profit)) \
                    & ((last_candle['percent'] < 0) & (last_candle['percent3'] < 0)):
                # print("over_bb_band_sma10_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
                return 'b_sma10'

        if self.profit_b_sma20.value:
            if (current_profit > expected_profit) \
                    & (previous_last_candle['sma10'] > last_candle['sma10']) \
                    & ((current_time - trade.open_date_utc).seconds >= 3600) \
                    & ((previous_last_candle['sma20'] > last_candle['sma20']) &
                       ((last_candle['percent5'] < 0) | (last_candle['percent12'] < 0) | (
                               last_candle['percent24'] < 0))):
                # print("over_bb_band_sma10_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
                return 'b_sma20'

        if self.profit_b_over_rsi.value:
            if (current_profit > 0) & (previous_last_candle['rsi'] > self.sell_b_RSI.value):
                # & (last_candle['percent'] < 0): #| (previous_last_candle['rsi'] > 75 & last_candle['rsi'] < 70)):
                # print("over_rsi", pair, trade, " profit=", current_profit, " rate=", current_rate)
                return 'b_over_rsi'

            if (current_profit > 0) & (previous_last_candle['rsi'] > self.sell_b_RSI2.value) & \
                    (last_candle[
                         'percent'] < - self.sell_b_RSI2_percent.value):  # | (previous_last_candle['rsi'] > 75 & last_candle['rsi'] < 70)):
                # print("over_rsi", pair, trade, " profit=", current_profit, " rate=", current_rate)
                return 'b_over_rsi_2'

            if (current_profit > 0) & (previous_last_candle['rsi'] > self.sell_b_RSI3.value) & \
                    (last_candle[self.close] >= last_candle['max200']) \
                    & (last_candle[
                           'percent'] < - self.sell_b_RSI2_percent.value):  # | (previous_last_candle['rsi2'] > 75 & last_candle['rsi'] < 70)):
                # print("over_rsi", pair, trade, " profit=", current_profit, " rate=", current_rate)
                return 'b_over_rsi_max'

    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 populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Add all ta features
        pair = metadata['pair']

        # dataframe['achats'] = get_limit_from_config('Achats', pair)
        # dataframe['ventes'] = get_limit_from_config('Ventes', pair)

        heikinashi = qtpylib.heikinashi(dataframe)
        dataframe['enter_long'] = ""
        dataframe['enter_tag'] = ""
        dataframe['haopen'] = heikinashi['open']
        dataframe['haclose'] = heikinashi['close']
        dataframe['hapercent'] = (dataframe['haclose'] - dataframe['haopen']) / dataframe['haclose']
        dataframe['mid'] = (dataframe['close'] + dataframe['open']) / 2

        dataframe['pct_change'] = dataframe[self.close].pct_change(5)
        dataframe['min'] = talib.MIN(dataframe[self.close], timeperiod=200)
        dataframe['min12'] = talib.MIN(dataframe[self.close], timeperiod=12)

        dataframe['min50'] = talib.MIN(dataframe[self.close], timeperiod=50)
        dataframe['min200'] = talib.MIN(dataframe[self.close], timeperiod=200)

        dataframe['max50'] = talib.MAX(dataframe[self.close], timeperiod=50)
        dataframe['max144'] = talib.MAX(dataframe[self.close], timeperiod=144)
        dataframe['min_max50'] = (dataframe['max50'] - dataframe['min50']) / dataframe['min50']

        dataframe['max200'] = talib.MAX(dataframe[self.close], timeperiod=200)
        dataframe['min_max200'] = (dataframe['max200'] - dataframe['min200']) / dataframe['min200']
        dataframe['max200_diff'] = (dataframe['max200'] - dataframe[self.close]) / dataframe[self.close]
        dataframe['max50_diff'] = (dataframe['max50'] - dataframe[self.close]) / dataframe[self.close]

        dataframe['sma5'] = talib.SMA(dataframe, timeperiod=5)
        dataframe['sma10'] = talib.SMA(dataframe, timeperiod=10)
        dataframe['sma20'] = talib.SMA(dataframe, timeperiod=20)
        dataframe["percent"] = (dataframe[self.close] - dataframe[self.open]) / dataframe[self.open]
        dataframe["percent3"] = (dataframe[self.close] - dataframe[self.open].shift(3)) / dataframe[self.open].shift(3)
        dataframe["percent5"] = (dataframe[self.close] - dataframe[self.open].shift(5)) / dataframe[self.open].shift(5)
        dataframe["percent12"] = (dataframe[self.close] - dataframe[self.open].shift(12)) / dataframe[self.open].shift(
            12)
        dataframe["percent24"] = (dataframe[self.close] - dataframe[self.open].shift(24)) / dataframe[self.open].shift(
            24)
        dataframe["percent48"] = (dataframe[self.close] - dataframe[self.open].shift(48)) / dataframe[self.open].shift(
            48)
        dataframe["percent_max_144"] = (dataframe[self.close] - dataframe["max144"]) / dataframe[self.close]
        # print(metadata['pair'])
        dataframe['rsi'] = talib.RSI(dataframe[self.close], timeperiod=12)
        dataframe['rsi24'] = talib.RSI(dataframe[self.close], timeperiod=24)
        dataframe['rsi48'] = talib.RSI(dataframe[self.close], timeperiod=48)

        dataframe['volume2'] = dataframe['volume']
        dataframe.loc[dataframe['percent'] < 0, 'volume2'] *= -1

        # ======================================================================================
        # MACD pour identifier les croisements
        macd = talib.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']

        # Moyennes mobiles pour les tendances
        dataframe['ema_50'] = talib.EMA(dataframe, timeperiod=50)
        dataframe['ema_200'] = talib.EMA(dataframe, timeperiod=200)

        # ADX pour la force de tendance
        dataframe['adx'] = talib.ADX(dataframe)

        # Volume moyen pour filtrer les opportunités à faible volume
        dataframe['volume_mean_sma'] = talib.SMA(dataframe['volume'], timeperiod=20)
        # ======================================================================================

        # ======================================================================================
        # 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["bb_percent"] = (
                (dataframe[self.close] - dataframe["bb_lowerband"]) /
                (dataframe["bb_upperband"] - dataframe["bb_lowerband"])
        )
        # ======================================================================================
        # Normalization

        dataframe['average_line'] = dataframe[self.close].mean()
        dataframe['average_line_50'] = talib.MIDPOINT(dataframe[self.close], timeperiod=50)

        dataframe['average_line_288'] = talib.MIDPOINT(dataframe[self.close], timeperiod=288)
        dataframe['average_line_288_098'] = dataframe['average_line_288'] * 0.98
        dataframe['average_line_288_099'] = dataframe['average_line_288'] * 0.99
        # Sort the close prices to find the 4 lowest values
        sorted_close_prices = dataframe[self.close].tail(576).sort_values()
        lowest_4 = sorted_close_prices.head(20)

        dataframe['lowest_4_average'] = dataframe['min200']  # lowest_4.mean()
        # Propagate this mean value across the entire dataframe
        # dataframe['lowest_4_average'] = dataframe['lowest_4_average'].iloc[0]

        # # Sort the close prices to find the 4 highest values
        sorted_close_prices = dataframe[self.close].tail(288).sort_values(ascending=False)
        highest_4 = sorted_close_prices.head(20)

        # # Calculate the mean of the 4 highest values
        dataframe['highest_4_average'] = dataframe['max200']  # highest_4.mean()

        # # Propagate this mean value across the entire dataframe
        # dataframe['highest_4_average'] = dataframe['highest_4_average'].iloc[0]

        dataframe['volatility'] = talib.STDDEV(dataframe[self.close], timeperiod=144) / dataframe[self.close]
        dataframe['atr'] = talib.ATR(dataframe['high'], dataframe['low'], dataframe[self.close], timeperiod=144) / \
                           dataframe[self.close]

        # ======================================================================================
        ################### INFORMATIVE 1h
        informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1h")
        informative['sma5'] = talib.SMA(informative, timeperiod=10)
        informative['sma5_pct'] = (informative["sma5"] - informative["sma5"].shift(6)) / informative["sma5"]
        informative["max_previous"] = talib.MAX(informative['close'], timeperiod=48)
        informative["min_previous"] = talib.MIN(informative['close'], timeperiod=48)

        informative['volatility'] = talib.STDDEV(informative['close'], timeperiod=14) / informative['close']
        informative['atr'] = (talib.ATR(informative['high'], informative['low'], informative['close'], timeperiod=14)) / \
                             informative['close']
        informative['rsi'] = talib.RSI(informative['close'], timeperiod=12)
        informative['rsi_sma'] = talib.SMA(informative['rsi'], timeperiod=10)
        heikinashi = qtpylib.heikinashi(informative)
        informative['ha_open'] = heikinashi['open']
        informative['ha_close'] = heikinashi['close']
        informative['ha_high'] = heikinashi['high']
        informative['ha_low'] = heikinashi['low']

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

        # Calcul des seuils dynamiques
        dataframe['sell_threshold_max'] = dataframe['max_previous_1h'] - (
                    dataframe['max_previous_1h'] * self.sell_max_threshold / 100)
        dataframe['sell_threshold_min'] = dataframe['min_previous_1h'] + (
                    dataframe['min_previous_1h'] * self.sell_min_threshold / 100)

        # ======================================================================================
        ################### INFORMATIVE 1d
        informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1d")
        sorted_close_prices = informative['close'].tail(365).sort_values()
        lowest_4 = sorted_close_prices.head(4)
        informative['lowest_4'] = lowest_4.mean()

        sorted_close_prices = informative['close'].tail(365).sort_values(ascending=False)
        highest_4 = sorted_close_prices.head(4)
        informative['highest_4'] = highest_4.mean()

        last_14_days = informative.tail(14)

        # Récupérer le minimum et le maximum de la colonne 'close' des 14 derniers jours
        min_14_days = last_14_days['close'].min()
        max_14_days = last_14_days['close'].max()
        informative['lowest'] = min_14_days
        informative['highest'] = max_14_days
        informative['pct_min_max'] = (max_14_days - min_14_days) / min_14_days
        informative['mid_min_max'] = min_14_days + (max_14_days - min_14_days) / 2
        informative['middle'] = informative['lowest_4'] + (informative['highest_4'] - informative['lowest_4']) / 2
        informative['mid_min_max_0.98'] = informative['mid_min_max'] * 0.98
        informative['rsi'] = talib.RSI(informative['close'], timeperiod=12)
        informative['rsi_sma'] = talib.SMA(informative['rsi'], timeperiod=12)
        informative["percent"] = (informative[self.close] - informative[self.open]) / informative[self.open]

        # Heikin Ashi (simplified)
        heikinashi = qtpylib.heikinashi(informative)
        informative['ha_open'] = heikinashi['open']
        informative['ha_close'] = heikinashi['close']
        informative['ha_high'] = heikinashi['high']
        informative['ha_low'] = heikinashi['low']
        # informative['ha_close'] = (informative['open'] + informative['high'] + informative['low'] + informative['close']) / 4
        # informative['ha_open'] = informative['ha_close'].shift(1).combine_first(informative['open'])
        informative['ha_red'] = informative['ha_close'] < informative['ha_open']

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

        dataframe['count_buys'] = 0

        dataframe['last_price'] = dataframe[self.close]
        dataframe['first_price'] = dataframe[self.close]
        dataframe['mid_price'] = (dataframe['last_price'] + dataframe['first_price']) / 2
        dataframe['close01'] = dataframe.iloc[-1][self.close] * 1.01
        dataframe['amount'] = 0
        dataframe['limit'] = dataframe[self.close]
        count_buys = 0
        if self.dp:
            if self.dp.runmode.value in ('live', 'dry_run'):
                self.getOpenTrades()

                for trade in self.trades:
                    if trade.pair != pair:
                        continue
                    filled_buys = trade.select_filled_orders('buy')
                    dataframe['count_buys'] = len(filled_buys)
                    count = 0
                    amount = 0
                    for buy in filled_buys:
                        if count == 0:
                            dataframe['first_price'] = buy.price
                            dataframe['close01'] = buy.price * 1.01

                        # Order(id=2396, trade=1019, order_id=29870026652, side=buy, filled=0.00078, price=63921.01,
                        # status=closed, date=2024-08-26 02:20:11)
                        dataframe['last_price'] = buy.price
                        print(buy)
                        count = count + 1
                        amount += buy.price * buy.filled
                    dataframe['mid_price'] = (dataframe['last_price'] + dataframe['first_price']) / 2
                    count_buys = count
                    dataframe['limit'] = dataframe['last_price'] * (1 - self.baisse[count] / 100)
                    dataframe['amount'] = amount
                    print(f"amount= {amount}")
                # trades = Trade.get_trades([Trade.is_open is False]).all()
                trades = Trade.get_trades_proxy(is_open=False, pair=metadata['pair'])
                if trades:
                    trade = trades[-1]
                    print('closed trade pair is : ')
                    dataframe['expected_profit'] = (1 + self.expectedProfit(pair, dataframe.iloc[-1])) * dataframe[
                        'last_price']

        dataframe['buy_level'] = dataframe['lowest_4_average'] * (1 - self.levels[count_buys] / 100)

        # ======================================================================================================

        dataframe['tag_min'] = (dataframe[self.close] <= dataframe['min200'] * 1.002)
        dataframe['tag_percent24'] = (dataframe['percent24'] <= -self.min_max_buys[pair]['profit'])
        dataframe['tag_stable'] = (dataframe['min50'].shift(6) == dataframe['min50'])
        dataframe['tag_buy'] = dataframe['tag_min'].shift(6) & dataframe['tag_percent24'].shift(6) & dataframe[
            'tag_stable']

        self.getBinanceOrderBook(pair, dataframe)

        # =========================================================================
        dataframe['rsi_range'] = pd.cut(dataframe['rsi'], bins=[0,10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
                                        labels=['00', '10', '20', '30', '40', '50', '60', '70', '80', '90'])
        dataframe['rsi_percent'] = 100 * (dataframe['rsi'] - dataframe['rsi'].shift(1)) / dataframe['rsi']
        dataframe['rsi_percent3'] = 100 * (dataframe['rsi'] - dataframe['rsi'].shift(3)) / dataframe['rsi']
        dataframe['rsi_percent5'] = 100 * (dataframe['rsi'] - dataframe['rsi'].shift(5)) / dataframe['rsi']
        dataframe['rsi_pct_range'] = pd.cut(dataframe['rsi_percent3'],
                                            bins=[-1000, -80, -60, -40, -20, 0, 20, 40, 60, 80, 1000],
                                            labels=['-100', '-80', '-60', '-40', '-20', '0', '20', '40', '60', '80'])

        # Calcul des changements futurs
        dataframe['future_change'] = dataframe['close'].shift(-12) - dataframe['close']
        dataframe['future_direction'] = dataframe['future_change'].apply(lambda x: 1 if x > 0 else 0)

        rsi_probabilities = dataframe.groupby('rsi_pct_range')['future_direction'].mean()
        print(f"Probabilités de hausse selon rsi_pct_range :\n", rsi_probabilities)

        return dataframe

    def getOpenTrades(self):
        # if len(self.trades) == 0:
        print('search open trades')
        if self.dp:
            if self.dp.runmode.value in ('live', 'dry_run'):
                self.trades = Trade.get_open_trades()
        return self.trades

    def getTrade(self, pair):
        trades = self.getOpenTrades()
        trade_for_pair = None
        for trade in trades:
            if trade.pair == pair:
                trade_for_pair = trade
                break
        return trade_for_pair

    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        pair = metadata['pair']

        self.getOpenTrades()
        # expected_profit = self.expectedProfit(pair, dataframe.iloc[-1])
        # self.getBinanceOrderBook(pair, dataframe)
        last_candle = dataframe.iloc[-1].squeeze()
        # limit = last_candle['first_price'] * (1 - self.baisse[last_candle['count_buys']] / 100)

        # self.updateLastValue(dataframe, 'expected_profit', expected_profit)
        # print("---------------" + pair + "----------------")
        # print('adjust stake amount ' + str(self.adjust_stake_amount(pair, dataframe.iloc[-1])))
        # print('adjust exit price ' + str(self.adjust_exit_price(dataframe.iloc[-1])))
        # print('calcul expected_profit ' + str(expected_profit))

        buy_level = dataframe[self.open]  # dataframe['buy_level']  # self.get_buy_level(pair, dataframe)

        dataframe.loc[
            (
                    (dataframe['max200_diff'] >= 0.018)
                    & (dataframe[self.open] < dataframe['average_line_288'])
                    & (dataframe[self.close] < dataframe['min12'] * 1.002)
                    & (dataframe['min12'].shift(2) == dataframe['min12'])
            ), ['enter_long', 'enter_tag']] = (1, 'buy_min_max200')

        dataframe.loc[
            (
                    (
                            (dataframe['percent12'].shift(3) < -0.015) |
                            (dataframe['percent24'].shift(3) < -0.022) |
                            (dataframe['percent48'].shift(3) < -0.030)
                    )
                    & (dataframe[self.close].shift(3) <= dataframe['min50'].shift(3) * 1.002)
                    & (dataframe[self.open].shift(3) < dataframe['average_line_50'].shift(3))
                    & (
                        (dataframe['min200'].shift(3) == dataframe['min200'])
                    )
            ), ['enter_long', 'enter_tag']] = (1, 'buy_0_percent12')
        dataframe.loc[
            (
                    (dataframe['count_buys'] == 0)
                    &
                    (
                        (dataframe[self.close] <= dataframe['sma5_1h'] * (1 - self.min_max_buys[pair]['profit'])) |
                        (dataframe['percent24'] <= -self.min_max_buys[pair]['profit'])
                    )
                    & (dataframe[self.close] <= dataframe['sma5_1h'])
                    & ((last_candle['close'] - last_candle['min_previous_1h']) / (last_candle['max_previous_1h'] - last_candle['close']) < 0.2)
            ), ['enter_long', 'enter_tag']] = (1, 'buy_percent12')

        # dataframe.loc[
        #     (
        #         (dataframe['percent_1d'] <= -0.1)
        #         & (dataframe['min12'].shift(12) == dataframe['min12'])
        #     ), ['enter_long', 'enter_tag']] = (1, 'buy_crash')

        nan_columns = dataframe.columns[dataframe.isna().any()].tolist()
        print("Colonnes contenant des NaN :", nan_columns)

        # Compter les colonnes avec uniquement des NaN
        num_only_nan_columns = dataframe.isna().all().sum()
        print(f"Nombre de colonnes contenant uniquement des NaN : {num_only_nan_columns}")

        # Compter les NaN par colonne
        nan_count_per_column = dataframe.isna().sum()
        print("Nombre de NaN par colonne :")
        print(nan_count_per_column)
        columns_with_nan = nan_count_per_column[nan_count_per_column > 1000]
        print("Colonnes avec au moins 1000 NaN :")
        print(columns_with_nan)

        #self.model = joblib.load('regression_model.pkl')
        # Former le modèle si ce n'est pas déjà fait
        if self.model is None:
            print("Calculate model")
            dataframe = self.add_future_direction(dataframe)
            self.model = self.train_model(dataframe)
        else:
            print("Load model")

        # Coefficients du modèle
        print("Coefficients :", self.model.coef_)

        # Ordonnée à l'origine (intercept)
        print("Ordonnée à l'origine :", self.model.intercept_)
        # Hyperparamètres du modèle
        print("Hyperparamètres du modèle :", self.model.get_params())


        # Faire des prédictions
        dataframe = self.predict_with_model(dataframe, self.model)
        # Sauvegarde du modèle
        # joblib.dump(self.model, 'regression_model.pkl')

        # Entrée sur un signal de hausse
        # dataframe.loc[
        #     (dataframe['predicted_direction'] == 1),
        #     ['enter_long', 'enter_tag']] = (1, 'buy_predict')

        return dataframe

    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # dataframe.loc[
        #     (
        #         (dataframe['sma5_pct_1h'].shift(24) < dataframe['sma5_pct_1h'].shift(12))
        #         & (dataframe['sma5_pct_1h'].shift(12) > dataframe['sma5_pct_1h'])
        #         & (dataframe['sma5_pct_1h'] < 0.035)
        #     ), ['sell', 'exit_long']] = (1, 'sell_sma5_pct_1h')
        return dataframe

    def adjust_trade_position(self, trade: Trade, current_time: datetime,
                              current_rate: float, current_profit: float, min_stake: float,
                              max_stake: float, **kwargs):
        pair = trade.pair
        # self.min_max_buys[pair][min] = min(self.min_max_buys[pair][min], current_rate)
        # self.min_max_buys[pair][max] = max(self.min_max_buys[pair][max], current_rate)

        dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
        # print(dataframe)
        last_candle = dataframe.iloc[-1].squeeze()
        last_candle_1 = dataframe.iloc[-2].squeeze()
        last_candle_3 = dataframe.iloc[-4].squeeze()
        last_candle_12 = dataframe.iloc[-13].squeeze()
        last_candle_24 = dataframe.iloc[-25].squeeze()
        return None

        # "& (last_candle['sma5_pct_1h'] < -0.035)
        if (len(dataframe) < 1) | (self.wallets.get_available_stake_amount() < 10):
            return None
        if pair not in ('BTC/USDT', 'DOGE/USDT', 'ETH/USDT'):
            return None
        if last_candle['rsi_1d'] is None:
            return None
        if (last_candle['ha_red_1d'] is None) | (last_candle['ha_red_1d'] == 1):
            return None
        max_buys = 10

        filled_buys = trade.select_filled_orders('buy')
        count_of_buys = len(filled_buys)
        if count_of_buys >= max_buys:
            return None
        first_price = filled_buys[0].price
        days = 0
        minutes = 0
        last_price = first_price
        for buy in filled_buys:
            minutes = (current_time - buy.order_date_utc).seconds / 60
            days = (current_time - buy.order_date_utc).days
            last_price = buy.price
        condition = (last_candle['sma5_pct_1h'] >= 0)
        dispo = round(self.wallets.get_available_stake_amount())
        profit = ((current_rate - last_price) / current_rate)
        rsi_1d = last_candle['rsi_1d']

        if (
                # (last_candle_3['volume'] * 5 < last_candle['volume'])
                (last_candle['rsi_1h'] > 50)
                #(last_candle['rsi_1d'] >= self.min_max_buys[pair]['last_rsi_1d'])
                & (minutes > 180)
                & (current_rate > max(first_price, last_price) * (1 + count_of_buys / 100))
        ):
            stake_amount = self.calculate_stake(pair, current_rate, last_candle)
            self.log_trade(
                date=current_time,
                action="INC price",
                pair=trade.pair,
                rate=current_rate,
                minutes=minutes,
                first_rate=first_price,
                last_rate=last_price,
                buys=count_of_buys,
                stake=round(stake_amount, 2),
                profit=round(profit * 100, 2),
                dispo=dispo,
                rsi=round(last_candle['rsi_1d'])

            )
            self.min_max_buys[pair]['last_rsi_1d'] = round(last_candle['rsi_1d'])

            # logger.info(
            #    f"INC price={trade.pair} {current_time} minutes={minutes} first={first_price:.4f} last={last_price:.4f} rate={current_rate:.4f} buys={count_of_buys}  stake={stake_amount:.4f}")
            return stake_amount

        # self.protection_nb_buy_lost.value
        # limit = last_candle[limits[count_of_buys]]
        limit = last_price * (1 - self.fibo[count_of_buys] / 100)
        # limit = last_price * (0.99)
        # stake_amount = min(200, self.adjust_stake_amount(pair, last_candle) * self.fibo[count_of_buys])
        # stake_amount = self.config.get('stake_amount', 50) * self.fibo[count_of_buys]
        stake_amount = self.calculate_stake(pair, current_rate, last_candle) * self.fibo[count_of_buys]
        percent_max = (last_candle['max200'] - last_candle['close']) / last_candle['close']
        if (
                False
                & (last_candle['rsi_1d'] >= self.min_max_buys[pair]['last_rsi_1d'])
                #(last_candle_1['rsi'] < 12)
                #(last_price <= first_price)
                & (current_rate <= limit)
                & (minutes > 180)):
            fact = - profit / 0.05
            stake_amount = 100 #min(self.wallets.get_available_stake_amount(), max(100, min(666, stake_amount * fact)))
            self.min_max_buys[pair]['last_rsi_1d'] = round(last_candle['rsi_1d'])

            self.log_trade(
                date=current_time,
                action="DEC price",
                pair=trade.pair,
                rate=current_rate,
                minutes=minutes,
                first_rate=first_price,
                last_rate=last_price,
                buys=count_of_buys,
                stake=round(stake_amount, 2),
                profit=round(profit * 100, 2),
                dispo=dispo,
                rsi=round(last_candle['rsi_1d'])
            )

            # logger.info(
            #     f"DEC price={trade.pair} {current_time} minutes={minutes} first={first_price:.4f} last={last_price:.4f} rate={current_rate:.4f} buys={count_of_buys} limit={limit:.4f} stake={stake_amount:.4f} rsi={rsi_1d:.4f}")

            return stake_amount

        # if days > 1:
        #     self.log_trade(
        #         date=current_time,
        #         action="NO BUY",
        #         pair=trade.pair,
        #         rate=current_rate,
        #         minutes=minutes,
        #         first_rate=first_price,
        #         last_rate=last_price,
        #         buys=count_of_buys,
        #         profit=round(profit * 100, 2),
        #         dispo=dispo,
        #         rsi=round(last_candle['rsi_1d'])
        #     )
        return None

        # print("Adjust " + trade.pair + " time=" + str(current_time) + ' rate=' + str(current_rate) + " buys=" + str(count_of_buys) + " limit=" + str(limit) + " stake=" + str(stake_amount))
        # logger.info(
        #    f"Adjust price={trade.pair} buy={condition} rate={current_rate:.4f} buys={count_of_buys} limit={limit:.4f} stake={stake_amount:.4f}")

        if (0 < count_of_buys <= max_buys) & (current_rate <= limit) & (condition) & (minutes > 180):
            try:

                # This then calculates current safety order size
                # stake_amount = stake_amount * pow(1.5, count_of_buys)
                # print("Effective Adjust " + trade.pair + " time=" + str(current_time) + ' rate=' + str(current_rate) + " buys=" + str(count_of_buys) + " limit=" + str(limit) + " stake=" + str(stake_amount))
                logger.info(
                    f"Adjust price={trade.pair} {current_time} first={first_price:.4f} rate={current_rate:.4f} buys={count_of_buys} limit={limit:.4f} stake={stake_amount:.4f}")

                return min(self.wallets.get_available_stake_amount(), stake_amount)
            except Exception as exception:
                print(exception)
                return None
        return None

    # def adjust_stake_amount(self, pair: str, dataframe: DataFrame):
    #     # Calculer le minimum des 14 derniers jours
    #     current_price = dataframe[self.close]
    #
    #     v_max = max(current_price, self.min_max_buys[pair]['max'])
    #     v_min = min(current_price, self.min_max_buys[pair]['min'])
    #
    #     adjusted_stake_amount = self.config['stake_amount'] * v_min / v_max
    #
    #     #print(
    #     #    f"Stack amount ajusté price={current_price} max_min={max_min_4:.4f} min_14={min_14_days_4:.4f} max_14={max_14_days_4:.4f} factor={factor_4:.4f} percent={percent_4:.4f} amount={adjusted_stake_amount:.4f}")
    #     # print(f"Stack amount ajusté price={current_price} max_min={max_min:.4f} min_14={min_14_days:.4f} max_14={max_14_days:.4f} factor={factor:.4f} percent={percent:.4f} amount={adjusted_stake_amount_2:.4f}")
    #
    #     return adjusted_stake_amount

    def adjust_exit_price(self, dataframe: DataFrame):
        # Calculer le max des 14 derniers jours
        min_14_days = dataframe['lowest_1d']
        max_14_days = dataframe['highest_1d']
        # entry_price = dataframe['fbp']
        current_price = dataframe[self.close]
        percent = 0.5 * (max_14_days - min_14_days) / min_14_days
        exit_price = (1 + percent) * entry_price

        print(f"Exit price ajusté price={current_price:.4f} max_14={max_14_days:.4f} exit_price={exit_price:.4f}")

        return exit_price

    def expectedProfit(self, pair: str, dataframe: DataFrame):

        current_price = dataframe['last_price']  # dataframe[self.close]

        # trade = self.getTrade(pair)
        # if trade:
        #     current_price = trade.open_rate

        # Calculer le max des 14 derniers jours
        min_14_days = dataframe['lowest_1d']
        max_14_days = dataframe['highest_1d']
        percent = (max_14_days - current_price) / (min_14_days)

        min_max = dataframe['pct_min_max_1d']  # (max_14_days - min_14_days) / min_14_days
        expected_profit = min(0.1, max(0.01, dataframe['min_max200'] * 0.5))

        print(
            f"Expected profit price={current_price:.4f} min_max={min_max:.4f} min_14={min_14_days:.4f} max_14={max_14_days:.4f} percent={percent:.4f} expected_profit={expected_profit:.4f}")

        # self.analyze_conditions(pair, dataframe)
        return expected_profit

    def getBinanceOrderBook(self, pair, dataframe: DataFrame):
        """Fetch the order book (depth) from Binance."""
        # print(dataframe)
        last_candle = dataframe.iloc[-1].squeeze()
        symbol = pair.replace('/', '')

        try:
            url = f"https://api.binance.com/api/v3/depth?symbol={symbol}&limit=5000"
            response = requests.get(url)
            data = response.json()

            # Extract bids and asks from the order book
            asks, bids = self.calculateSMA(20, data['asks'], data['bids'])  # Ventes List of [price, quantity]
            # bids = data['bids']
            # asks = data['asks']  # Achats List of [price, quantity]

            # Process the depth data as you need it
            # bid_volume = sum([float(bid[1]) for bid in bids])  # Sum of all bid volumes
            # $ * nb / $ => nb
            bid_volume = sum([float(bid[0]) * float(bid[1]) / float(last_candle[self.close]) for bid in bids[:10]])
            # ask_volume = sum([float(ask[1]) for ask in asks])  # Sum of all ask volumes
            ask_volume = sum([float(ask[0]) * float(ask[1]) / float(last_candle[self.close]) for ask in asks[:10]])

            # Example: add the difference in volumes as an indicator
            if bid_volume and ask_volume:
                self.updateLastValue(dataframe, 'depth_bid_ask_diff', round(bid_volume - ask_volume, 2))
            else:
                self.updateLastValue(dataframe, 'depth_bid_ask_diff', 0)

            # probabilité baisse hausse sur les n premiers élements
            for start in [0, 50, 100, 150]:
                self.updateLastValue(dataframe, 'prob_hausse_' + str(start + 50),
                                     self.calculateProbaNb(asks, bids, start, start + 50))
                # dataframe['prob_hausse_' + str(nb)] = self.calculateProbaNb(asks, bids, nb)
            # Analyse des prix moyens pondérés par les volumes (VWAP) :
            #
            # Le VWAP (Volume Weighted Average Price) peut être utilisé pour comprendre la pression directionnelle.
            # Si le VWAP basé sur les ordres d'achat est plus élevé que celui des ordres de vente,
            # cela peut indiquer une probabilité de hausse.
            nb = 50

            bid_vwap = sum([float(bid[0]) * float(bid[1]) for bid in bids[:nb]]) / sum(
                [float(bid[1]) for bid in bids[:nb]])
            ask_vwap = sum([float(ask[0]) * float(ask[1]) for ask in asks[:nb]]) / sum(
                [float(ask[1]) for ask in asks[:nb]])

            if bid_vwap > ask_vwap:
                self.updateLastValue(dataframe, 'vwap_hausse',
                                     round(100 * (bid_vwap - ask_vwap) / (bid_vwap + ask_vwap), 2))
            else:
                self.updateLastValue(dataframe, 'vwap_hausse',
                                     - round(100 * (ask_vwap - bid_vwap) / (bid_vwap + ask_vwap), 2))

            current_price = last_candle[self.close]  # le prix actuel du marché

            # Calcul du seuil de variation de 1%
            lower_threshold = current_price * 0.99
            upper_threshold = current_price * 1.01

            # Volumes d'achat (bids) sous 1% du prix actuel
            bid_volume_1percent = sum(
                [float(bid[1]) for bid in bids if current_price >= float(bid[0]) >= lower_threshold])

            # Volumes de vente (asks) au-dessus de 1% du prix actuel
            ask_volume_1percent = sum(
                [float(ask[1]) for ask in asks if current_price <= float(ask[0]) <= upper_threshold])

            # Estimation de la probabilité basée sur le déséquilibre des volumes
            total_volume = bid_volume_1percent + ask_volume_1percent
            if total_volume > 0:
                prob_hausse = bid_volume_1percent / total_volume
                prob_baisse = ask_volume_1percent / total_volume
            else:
                prob_hausse = prob_baisse = 0

            self.updateLastValue(dataframe, 'proba_hausse_1%', round(prob_hausse * 100, 2))
            self.updateLastValue(dataframe, 'proba_baisse_1%', round(prob_baisse * 100, 2))
            print(f"Probabilité de hausse de 1%: {prob_hausse * 100:.2f}%")
            print(f"Probabilité de baisse de 1%: {prob_baisse * 100:.2f}%")

            self.calculateResistance(pair, asks, dataframe)
            self.calculateSupport(pair, bids, dataframe)

            dataframe['r_s'] = 100 * (dataframe['r_min'] - dataframe['s_min']) / dataframe['s_min']

        except Exception as e:
            logger.error(f"Error fetching order book: {e}")
            return None, None

    def calculateProbaNb(self, asks, bids, start, nb):
        top_bids = sum([float(bid[1]) for bid in bids[start:nb]])
        top_asks = sum([float(ask[1]) for ask in asks[start:nb]])
        if top_bids > top_asks:
            proba = round(100 * (top_bids - top_asks) / (top_bids + top_asks), 2)
        else:
            proba = - round(100 * (top_asks - top_bids) / (top_bids + top_asks), 2)
        return proba

    def calculateResistance(self, pair, asks, dataframe: DataFrame):
        last_candle = dataframe.iloc[-1].squeeze()

        # Filtrage +-5%
        current_price = float(last_candle[self.close])
        lower_bound = current_price * 0.95
        upper_bound = current_price * 1.05
        ask_prices = [float(ask[0]) for ask in asks]
        ask_volumes = [float(ask[1]) for ask in asks]
        ask_df = pd.DataFrame({'price': ask_prices, 'volume': ask_volumes})
        filtered_ask_df = ask_df[(ask_df['price'] >= lower_bound) & (ask_df['price'] <= upper_bound)]
        # Trier le DataFrame sur la colonne 'volume' en ordre décroissant
        sorted_ask_df = filtered_ask_df.sort_values(by='volume', ascending=False)

        # Ne garder que les 3 premières lignes (les 3 plus gros volumes)
        top_3_asks = sorted_ask_df.head(3)
        print(top_3_asks)

        # Convertir les ordres de vente en numpy array pour faciliter le traitement
        asks_array = np.array(filtered_ask_df, dtype=float)

        # Détecter les résistances : on peut définir qu'une résistance est un niveau de prix où la quantité est élevée
        # Ex: seuil de résistance à partir des 10% des plus grosses quantités
        resistance_threshold = np.percentile(asks_array[:, 1], 90)
        resistances = asks_array[asks_array[:, 1] >= resistance_threshold]

        # Afficher les résistances trouvées
        # print(f"{pair} Niveaux de résistance détectés:")
        # for resistance in resistances:
        #     print(f"{pair} Prix: {resistance[0]}, Quantité: {resistance[1]}")

        # Exemple : somme des quantités sur des plages de prix
        # Intervalles de 10 USDT
        step = last_candle[self.close] / 100
        price_intervals = np.arange(asks_array[:, 0].min(), asks_array[:, 0].max(), step=step)

        for start_price in price_intervals:
            end_price = start_price + step
            mask = (asks_array[:, 0] >= start_price) & (asks_array[:, 0] < end_price)
            volume_in_range = asks_array[mask, 1].sum()
            amount = volume_in_range * end_price
            print(
                f"Prix entre {start_price:.6f} et {end_price:.6f}: Volume total = {volume_in_range:.2f} amount={amount:.2f}")

        # Trier les asks par quantité en ordre décroissant
        asks_sorted = asks_array[asks_array[:, 1].argsort()][::-1]

        # Sélectionner les trois plus gros resistances
        top_3_resistances = asks_sorted[:3]

        # Afficher les trois plus gros resistances
        print("Les trois plus grosses resistances détectées : ")
        self.updateLastValue(dataframe, 'r3', top_3_resistances[0][0])
        self.updateLastValue(dataframe, 'r2', top_3_resistances[1][0])
        self.updateLastValue(dataframe, 'r1', top_3_resistances[2][0])
        self.updateLastValue(dataframe, 'r_min',
                             min(top_3_resistances[0][0], top_3_resistances[1][0], top_3_resistances[2][0]))
        for resistance in top_3_resistances:
            print(f"{pair} Prix: {resistance[0]}, Quantité: {resistance[1]}")

    def calculateSupport(self, pair, bids, dataframe: DataFrame):
        last_candle = dataframe.iloc[-1].squeeze()

        # Convert to pandas DataFrame to apply moving average
        current_price = float(last_candle[self.close])
        lower_bound = current_price * 0.95
        upper_bound = current_price * 1.05
        bid_prices = [float(bid[0]) for bid in bids]
        bid_volumes = [float(bid[1]) for bid in bids]
        bid_df = pd.DataFrame({'price': bid_prices, 'volume': bid_volumes})
        filtered_bid_df = bid_df[(bid_df['price'] >= lower_bound) & (bid_df['price'] <= upper_bound)]
        # Trier le DataFrame sur la colonne 'volume' en ordre décroissant
        sorted_bid_df = filtered_bid_df.sort_values(by='volume', ascending=False)

        # Ne garder que les 3 premières lignes (les 3 plus gros volumes)
        top_3_bids = sorted_bid_df.head(3)
        print(top_3_bids)

        # Convertir les ordres d'achat en numpy array pour faciliter le traitement
        bids_array = np.array(filtered_bid_df, dtype=float)

        # Détecter les supports : on peut définir qu'un support est un niveau de prix où la quantité est élevée
        # Ex: seuil de support à partir des 10% des plus grosses quantités
        support_threshold = np.percentile(bids_array[:, 1], 90)
        supports = bids_array[bids_array[:, 1] >= support_threshold]

        # Afficher les supports trouvés
        # print(f"{pair} Niveaux de support détectés:")
        # for support in supports:
        #     print(f"{pair} Prix: {support[0]}, Quantité: {support[1]}")

        step = last_candle[self.close] / 100
        # Exemple : somme des quantités sur des plages de prix pour les bids
        price_intervals = np.arange(bids_array[:, 0].min(), bids_array[:, 0].max(), step=step)  # Intervalles de 10 USDT

        for start_price in price_intervals:
            end_price = start_price + step
            mask = (bids_array[:, 0] >= start_price) & (bids_array[:, 0] < end_price)
            volume_in_range = bids_array[mask, 1].sum()
            amount = volume_in_range * end_price
            print(
                f"Prix entre {start_price:.6f} et {end_price:.6f}: Volume total = {volume_in_range:.2f} amount={amount:.2f}")

        # Trier les bids par quantité en ordre décroissant
        bids_sorted = bids_array[bids_array[:, 1].argsort()][::-1]

        # Sélectionner les trois plus gros supports
        top_3_supports = bids_sorted[:3]

        # Afficher les trois plus gros supports
        print("Les trois plus gros supports détectés:")

        self.updateLastValue(dataframe, 's1', top_3_supports[0][0])
        self.updateLastValue(dataframe, 's2', top_3_supports[1][0])
        self.updateLastValue(dataframe, 's3', top_3_supports[2][0])
        self.updateLastValue(dataframe, 's_min', max(top_3_supports[0][0], top_3_supports[1][0], top_3_supports[2][0]))

        for support in top_3_supports:
            print(f"{pair} Prix: {support[0]}, Quantité: {support[1]}")

    def updateLastValue(self, df: DataFrame, col, value):
        if col in df.columns:
            print(f"update last col {col} {value}")
            df.iloc[-1, df.columns.get_loc(col)] = value
        else:
            print(f"update all col {col} {value}")
            df[col] = value

    # def update_last_record(self, dataframe: DataFrame, new_data):
    #     # Vérifiez si de nouvelles données ont été reçues
    #     if new_data is not None:
    #         # Ne mettez à jour que la dernière ligne du dataframe
    #         last_index = dataframe.index[-1]  # Sélectionne le dernier enregistrement
    #         dataframe.loc[last_index] = new_data  # Met à jour le dernier enregistrement avec les nouvelles données
    #     return dataframe

    def calculateSMA(self, nb, asks, bids):
        # Prepare data for plotting
        bid_prices = [float(bid[0]) for bid in bids]
        bid_volumes = [float(bid[1]) for bid in bids]

        ask_prices = [float(ask[0]) for ask in asks]
        ask_volumes = [float(ask[1]) for ask in asks]

        # Convert to pandas DataFrame to apply moving average
        bid_df = pd.DataFrame({'price': bid_prices, 'volume': bid_volumes})
        ask_df = pd.DataFrame({'price': ask_prices, 'volume': ask_volumes})

        # Apply a rolling window to calculate a 10-value simple moving average (SMA)
        bid_df['volume_sma'] = bid_df['volume'].rolling(window=nb).mean()
        ask_df['volume_sma'] = ask_df['volume'].rolling(window=nb).mean()

        # Pour bid_df
        bid_df = bid_df.dropna(subset=['volume_sma'])
        bids_with_sma = list(zip(bid_df['price'], bid_df['volume_sma']))

        # Pour ask_df
        ask_df = ask_df.dropna(subset=['volume_sma'])
        asks_with_sma = list(zip(ask_df['price'], ask_df['volume_sma']))

        # print(bids_with_sma)
        # print(asks_with_sma)

        return asks_with_sma, bids_with_sma

    def calculate_stake(self, pair, value, last_candle):
        """
        Calcule la mise en pourcentage (entre 20% et 100%) pour une paire donnée
        en fonction de la valeur actuelle et de la plage min/max de la paire.

        Parameters:
            pair (str): La paire (ex: "BTC/USDT").
            value (float): La valeur actuelle de la paire.

        Returns:
            float: Le pourcentage de mise, entre 20% et 100%.
        """
        # if pair not in self.min_max_buys:
        #    raise ValueError(f"La paire {pair} n'est pas définie dans min_max_buys")

        min_val = last_candle['min_previous_1h']  # self.min_max_buys[pair]["min"]
        max_val = last_candle['max_previous_1h']  # self.min_max_buys[pair]["max"]

        amount = self.config['stake_amount']
        return amount
        if (max_val == min_val) | (np.isnan(max_val)):
            return amount
        if value <= min_val:
            return amount  # Mise minimale de 20%
        elif value >= max_val:
            return amount / 5  # Mise maximale de 100%
        else:
            # Calcul de la position relative entre min et max
            position = (value - min_val) / (max_val - min_val)
            # Interpolation linéaire entre 20% et 100%
            stake = amount - (position * (amount * 4 / 5))
            return stake
