# GodStra Strategy
# Author: @Mablue (Masoud Azizi)
# github: https://github.com/mablue/
# IMPORTANT:Add to your pairlists inside config.json (Under StaticPairList):
#   {
#       "method": "AgeFilter",
#       "min_days_listed": 30
#   },
# IMPORTANT: INSTALL TA BEFOUR RUN(pip install ta)
# IMPORTANT: Use Smallest "max_open_trades" for getting best results inside config.json

# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
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)
from pandas import DataFrame
# --------------------------------
from datetime import timezone, timedelta

# Add your lib to import here
# import talib.abstract as ta
from ta import add_all_ta_features
from ta.utils import dropna
import freqtrade.vendor.qtpylib.indicators as qtpylib
from functools import reduce
import numpy as np


class GodStra(IStrategy):
    position_adjustment_enable = True

    columns_logged = False
    pairs = {
        pair: {
            "first_buy": 0,
            "last_buy": 0.0,
            "last_min": 999999999999999.5,
            "last_max": 0,
            "trade_info": {},
            "max_touch": 0.0,
            "last_sell": 0.0,
            'count_of_buys': 0,
            'current_profit': 0,
            'expected_profit': 0,
            'previous_profit': 0,
            "last_candle": {},
            "last_count_of_buys": 0,
            'base_stake_amount': 0,
            'stop_buy': False,
            'last_date': 0,
            'stop': False,
            'max_profit': 0,
            'first_amount': 0,
            'total_amount': 0,
            'has_gain': 0,
            'force_sell': False,
            'force_buy': False
        }
        for pair in ["BTC/USDC", "ETH/USDC", "DOGE/USDC", "XRP/USDC", "SOL/USDC",
                     "BTC/USDT", "ETH/USDT", "DOGE/USDT", "XRP/USDT", "SOL/USDT"]
    }
    # 5/66:      9 trades. 8/0/1 Wins/Draws/Losses. Avg profit  21.83%. Median profit  35.52%. Total profit  1060.11476586 USDT ( 196.50Σ%). Avg duration 3440.0 min. Objective: -7.06960
    # +--------+---------+----------+------------------+--------------+-------------------------------+----------------+-------------+
    # |   Best |   Epoch |   Trades |    Win Draw Loss |   Avg profit |                        Profit |   Avg duration |   Objective |
    # |--------+---------+----------+------------------+--------------+-------------------------------+----------------+-------------|
    # | * Best |   1/500 |       11 |      2    1    8 |        5.22% |  280.74230393 USDT   (57.40%) |      2,421.8 m |    -2.85206 |
    # | * Best |   2/500 |       10 |      7    0    3 |       18.76% |  983.46414442 USDT  (187.58%) |        360.0 m |    -4.32665 |
    # | * Best |   5/500 |        9 |      8    0    1 |       21.83% | 1,060.11476586 USDT  (196.50%) |      3,440.0 m |     -7.0696 |

    # Buy hyperspace params:
    buy_params = {
        'buy-cross-0': 'volatility_kcc',
        'buy-indicator-0': 'trend_ichimoku_base',
        'buy-int-0': 42,
        'buy-oper-0': '<R',
        'buy-real-0': 0.06295
    }

    # Sell hyperspace params:
    sell_params = {
        'sell-cross-0': 'volume_mfi',
        'sell-indicator-0': 'trend_kst_diff',
        'sell-int-0': 98,
        'sell-oper-0': '=R',
        'sell-real-0': 0.8779
    }

    # ROI table:
    minimal_roi = {
        "0": 0.3556,
        "4818": 0.21275,
        "6395": 0.09024,
        "22372": 0
    }

    # Stoploss:
    stoploss = -0.34549

    # Trailing stop:
    trailing_stop = True
    trailing_stop_positive = 0.22673
    trailing_stop_positive_offset = 0.2684
    trailing_only_offset_is_reached = True
    # Buy hypers
    timeframe = '1m'
    print('Add {\n\t"method": "AgeFilter",\n\t"min_days_listed": 30\n},\n to your pairlists in config (Under StaticPairList)')

    def dna_size(self, dct: dict):
        def int_from_str(st: str):
            str_int = ''.join([d for d in st if d.isdigit()])
            if str_int:
                return int(str_int)
            return -1  # in case if the parameter somehow doesn't have index
        return len({int_from_str(digit) for digit in dct.keys()})

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Add all ta features
        dataframe = dropna(dataframe)
        dataframe = add_all_ta_features(
            dataframe, open="open", high="high", low="low", close="close", volume="volume",
            fillna=True)
        # dataframe.to_csv("df.csv", index=True)
        return dataframe

    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = list()
        # /5: Cuz We have 5 Group of variables inside buy_param
        for i in range(self.dna_size(self.buy_params)):

            OPR = self.buy_params[f'buy-oper-{i}']
            IND = self.buy_params[f'buy-indicator-{i}']
            CRS = self.buy_params[f'buy-cross-{i}']
            INT = self.buy_params[f'buy-int-{i}']
            REAL = self.buy_params[f'buy-real-{i}']
            DFIND = dataframe[IND]
            DFCRS = dataframe[CRS]

            if OPR == ">":
                conditions.append(DFIND > DFCRS)
            elif OPR == "=":
                conditions.append(np.isclose(DFIND, DFCRS))
            elif OPR == "<":
                conditions.append(DFIND < DFCRS)
            elif OPR == "CA":
                conditions.append(qtpylib.crossed_above(DFIND, DFCRS))
            elif OPR == "CB":
                conditions.append(qtpylib.crossed_below(DFIND, DFCRS))
            elif OPR == ">I":
                conditions.append(DFIND > INT)
            elif OPR == "=I":
                conditions.append(DFIND == INT)
            elif OPR == "<I":
                conditions.append(DFIND < INT)
            elif OPR == ">R":
                conditions.append(DFIND > REAL)
            elif OPR == "=R":
                conditions.append(np.isclose(DFIND, REAL))
            elif OPR == "<R":
                conditions.append(DFIND < REAL)

        #print(conditions)
        dataframe.loc[
            reduce(lambda x, y: x & y, conditions),
            'buy'] = 1

        return dataframe

    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = list()
        for i in range(self.dna_size(self.sell_params)):
            OPR = self.sell_params[f'sell-oper-{i}']
            IND = self.sell_params[f'sell-indicator-{i}']
            CRS = self.sell_params[f'sell-cross-{i}']
            INT = self.sell_params[f'sell-int-{i}']
            REAL = self.sell_params[f'sell-real-{i}']
            DFIND = dataframe[IND]
            DFCRS = dataframe[CRS]

            if OPR == ">":
                conditions.append(DFIND > DFCRS)
            elif OPR == "=":
                conditions.append(np.isclose(DFIND, DFCRS))
            elif OPR == "<":
                conditions.append(DFIND < DFCRS)
            elif OPR == "CA":
                conditions.append(qtpylib.crossed_above(DFIND, DFCRS))
            elif OPR == "CB":
                conditions.append(qtpylib.crossed_below(DFIND, DFCRS))
            elif OPR == ">I":
                conditions.append(DFIND > INT)
            elif OPR == "=I":
                conditions.append(DFIND == INT)
            elif OPR == "<I":
                conditions.append(DFIND < INT)
            elif OPR == ">R":
                conditions.append(DFIND > REAL)
            elif OPR == "=R":
                conditions.append(np.isclose(DFIND, REAL))
            elif OPR == "<R":
                conditions.append(DFIND < REAL)

        dataframe.loc[
            reduce(lambda x, y: x & y, conditions),
            'sell'] = 1

        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):
        # ne rien faire si ordre deja en cours
        print('ici')
        if trade.has_open_orders:
            # self.printLog("skip open orders")
            return None
        if (self.wallets.get_available_stake_amount() < 10):  # or trade.stake_amount >= max_stake:
            return 0

        dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        before_last_candle = dataframe.iloc[-2].squeeze()
        # prépare les données
        current_time = current_time.astimezone(timezone.utc)
        open_date = trade.open_date.astimezone(timezone.utc)
        dispo = round(self.wallets.get_available_stake_amount())
        hours_since_first_buy = (current_time - trade.open_date_utc).seconds / 3600.0
        days_since_first_buy = (current_time - trade.open_date_utc).days
        hours = (current_time - trade.date_last_filled_utc).total_seconds() / 3600.0
        count_of_buys = trade.nr_of_successful_entries
        current_time_utc = current_time.astimezone(timezone.utc)
        open_date = trade.open_date.astimezone(timezone.utc)
        days_since_open = (current_time_utc - open_date).days
        pair = trade.pair
        profit = trade.calc_profit(current_rate) #round(current_profit * trade.stake_amount, 1)
        last_lost = self.getLastLost(last_candle, pair)
        pct_first = 0

        total_counts = sum(
            pair_data['count_of_buys'] for pair_data in self.pairs.values() if not self.getShortName(pair) == 'BTC')

        if self.pairs[pair]['first_buy']:
            pct_first = self.getPctFirstBuy(pair, last_candle)

        lim = 0.3
        if (len(dataframe) < 1):
            # self.printLog("skip dataframe")
            return None

        # Dernier prix d'achat réel (pas le prix moyen)
        last_fill_price = self.pairs[trade.pair]['last_buy'] #trade.open_rate  # remplacé juste après ↓

        # if len(trade.orders) > 0:
        #     # On cherche le dernier BUY exécuté
        #     buy_orders = [o for o in trade.orders if o.is_buy and o.status == "closed"]
        #     if buy_orders:
        #         last_fill_price = buy_orders[-1].price

        # baisse relative
        dca_threshold = 0.0025 * count_of_buys
        decline = (last_fill_price - current_rate) / last_fill_price
        increase = - decline

        # if decline >= self.dca_threshold:
        #     # Exemple : on achète 50% du montant du dernier trade
        #     last_amount = buy_orders[-1].amount if buy_orders else 0
        #     stake_amount = last_amount * current_rate * 0.5
        #     return stake_amount

        condition = last_candle['percent'] > 0 #and last_candle['sma24_deriv1'] > 0
        limit_buy = 40
        # or (last_candle['close'] <= last_candle['min180'] and hours > 3)
        if (decline >= dca_threshold) and condition:
            print('decline')
            try:
                if self.pairs[pair]['has_gain'] and profit > 0:
                    self.pairs[pair]['force_sell'] = True
                    self.pairs[pair]['previous_profit'] = profit
                    return None

                max_amount = self.config.get('stake_amount') * 2.5
                stake_amount = min(min(max_amount, self.wallets.get_available_stake_amount()),
                                   self.adjust_stake_amount(pair, last_candle))
                # print(f"profit={profit} previous={self.pairs[pair]['previous_profit']} count_of_buys={trade.nr_of_successful_entries}")
                if stake_amount > 0:
                    self.pairs[pair]['previous_profit'] = profit
                    trade_type = "Loss " + (last_candle['enter_tag'] if last_candle['enter_long'] == 1 else '')
                    self.pairs[trade.pair]['count_of_buys'] += 1
                    self.pairs[pair]['total_amount'] += stake_amount
                    # self.log_trade(
                    #     last_candle=last_candle,
                    #     date=current_time,
                    #     action="🟧 Loss -",
                    #     dispo=dispo,
                    #     pair=trade.pair,
                    #     rate=current_rate,
                    #     trade_type=trade_type,
                    #     profit=round(profit, 1),
                    #     buys=trade.nr_of_successful_entries + 1,
                    #     stake=round(stake_amount, 2)
                    # )

                    self.pairs[trade.pair]['last_buy'] = current_rate
                    self.pairs[trade.pair]['max_touch'] = last_candle['close']
                    self.pairs[trade.pair]['last_candle'] = last_candle

                    # df = pd.DataFrame.from_dict(self.pairs, orient='index')
                    # colonnes_a_exclure = ['last_candle', 'stop',
                    #                       'trade_info', 'last_date', 'expected_profit', 'last_count_of_buys', 'base_stake_amount', 'stop_buy']
                    # df_filtered = df[df['count_of_buys'] > 0].drop(columns=colonnes_a_exclure)
                    # # df_filtered = df_filtered["first_buy", "last_max", "max_touch", "last_sell","last_buy", 'count_of_buys', 'current_profit']
                    #
                    # self.printLog(df_filtered)

                    return stake_amount
                return None
            except Exception as exception:
                self.printLog(exception)
                return None

        if current_profit > dca_threshold and (increase >= dca_threshold and self.wallets.get_available_stake_amount() > 0):
            print('increase')

            try:
                self.pairs[pair]['previous_profit'] = profit
                stake_amount = max(20, min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle)))
                if stake_amount > 0:
                    self.pairs[pair]['has_gain'] += 1

                    trade_type = 'Gain +' + (last_candle['enter_tag'] if last_candle['enter_long'] == 1 else '')
                    self.pairs[trade.pair]['count_of_buys'] += 1
                    self.pairs[pair]['total_amount'] += stake_amount
                    # self.log_trade(
                    #     last_candle=last_candle,
                    #     date=current_time,
                    #     action="🟡 Gain +",
                    #     dispo=dispo,
                    #     pair=trade.pair,
                    #     rate=current_rate,
                    #     trade_type='Gain',
                    #     profit=round(profit, 1),
                    #     buys=trade.nr_of_successful_entries + 1,
                    #     stake=round(stake_amount, 2)
                    # )
                    self.pairs[trade.pair]['last_buy'] = current_rate
                    self.pairs[trade.pair]['max_touch'] = last_candle['close']
                    self.pairs[trade.pair]['last_candle'] = last_candle
                    return stake_amount
                return None
            except Exception as exception:
                self.printLog(exception)
                return None

        return None