# Zeus Strategy: First Generation of GodStra Strategy with maximum
# AVG/MID profit in USDT
# Author: @Mablue (Masoud Azizi)
# github: https://github.com/mablue/
# IMPORTANT: INSTALL TA BEFOUR RUN(pip install ta)
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --spaces buy sell roi --strategy Zeus
# --- Do not remove these libs ---
from freqtrade.strategy.parameters import CategoricalParameter, DecimalParameter, IntParameter, BooleanParameter

from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
# --------------------------------

# Add your lib to import here
import ta
from functools import reduce
import numpy as np
import talib.abstract as talib
from freqtrade.strategy.strategy_helper import merge_informative_pair
import freqtrade.vendor.qtpylib.indicators as qtpylib


class Zeus_2_2(IStrategy):

    # *    1/43:     86 trades. 72/6/8 Wins/Draws/Losses. Avg profit  12.66%. Median profit  11.99%. Total profit  0.10894395 BTC ( 108.94Σ%). Avg duration 3 days, 0:31:00 min. Objective: -48.48793
    # "max_open_trades": 10,
    # "stake_currency": "BTC",
    # "stake_amount": 0.01,
    # "tradable_balance_ratio": 0.99,
    # "timeframe": "4h",
    # "dry_run_wallet": 0.1,

    # Buy hyperspace params:
    buy_params = {
        "buy_cat": "<R",
        "buy_real": 0.0128,
    }

    # Sell hyperspace params:
    sell_params = {
        "sell_cat": "=R",
        "sell_real": 0.9455,
    }

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

    # Stoploss:
    stoploss = -0.256

    buy_real = DecimalParameter(0.001, 0.999, decimals=4, default=0.11908, space='buy')
    buy_cat = CategoricalParameter([">R", "=R", "<R"], default='<R', space='buy')
    buy_pct = DecimalParameter(0.001, 0.02, decimals=3, default=0.005, space='buy')
    buy_pct_1 = DecimalParameter(-0.2, 0.2, decimals=2, default=0.005, space='buy')
    buy_pct_3 = DecimalParameter(-0.2, 0.2, decimals=2, default=0.005, space='buy')
    buy_pct_5 = DecimalParameter(-0.2, 0.2, decimals=2, default=0.005, space='buy')
    buy_bb_lowerband = DecimalParameter(1, 1.05, default=1, decimals=2, space='buy')
    buy_bb_width = DecimalParameter(0.01, 0.15, default=0.065, decimals=2, space='buy')

    buy_real_diff = DecimalParameter(0.001, 0.999, decimals=4, default=0.59608, space='buy')
    buy_cat_diff = CategoricalParameter([">R", "=R", "<R"], default='>R', space='buy')

    # Buy hypers
    timeframe = '4h'

    plot_config = {
        "main_plot": {
            "min200": {"color": "#86c932"},
            "max50": {"color": "white"},
            "max200": {"color": "yellow"},
            "sma3_1d": {"color": "pink"},
            "sma5_1d": {"color": "blue"},
            "sma10_1d": {"color": "orange"},
        },
        "subplots": {
            "Ind": {
                "trend_ichimoku_base": {
                    "color": "#dd1384",
                },
                "trend_kst_diff": {
                    "color": "#850678",
                },
            },
            "Percent": {
                "max_min": {"color": "#74effc"},
                "pct_change_1_1d": {"color": "green"},
                "pct_change_3_1d": {"color": "orange"},
                "pct_change_5_1d": {"color": "red"},

            }
        }
    }

    profit_no_change = BooleanParameter(default=True, space="sell")
    profit_quick_lost = BooleanParameter(default=True, space="sell")
    profit_sma5 = BooleanParameter(default=True, space="sell")
    profit_sma10 = BooleanParameter(default=True, space="sell")
    profit_sma20 = BooleanParameter(default=True, space="sell")
    profit_quick_gain = BooleanParameter(default=True, space="sell")
    profit_quick_gain_3 = BooleanParameter(default=True, space="sell")
    profit_old_sma10 = BooleanParameter(default=True, space="sell")
    profit_very_old_sma10 = BooleanParameter(default=True, space="sell")
    profit_over_rsi = BooleanParameter(default=True, space="sell")
    profit_short_loss = BooleanParameter(default=True, space="sell")

    sell_percent = DecimalParameter(0, 0.02, decimals=3,  default=0.01, space='sell')
    sell_percent3 = DecimalParameter(0, 0.02, decimals=3,  default=0.01, space='sell')
    sell_candels = IntParameter(0, 48, default=12, space='sell')

    sell_too_old_day = IntParameter(0, 10, default=5, space='sell')
    sell_too_old_percent = DecimalParameter(0, 0.02, decimals=3,  default=0.01, space='sell')

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

    sell_RSI = IntParameter(70, 98, default=88, space='sell')
    sell_RSI2 = IntParameter(70, 98, default=88, space='sell')
    sell_RSI3 = IntParameter(70, 98, default=80, space='sell')

    sell_RSI2_percent = DecimalParameter(0, 0.02, decimals=3,  default=0.01, space='sell')
    # sell_expected_profit = DecimalParameter(0, 0.01, decimals=3,  default=0.01, space='sell')

    def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
                    current_profit: float, **kwargs):
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        previous_last_candle = dataframe.iloc[-2].squeeze()
        previous_5_candle = dataframe.iloc[-5].squeeze()

        expected_profit = 0.01
        # if (last_candle['pct_change_1_1d'] < 0):
        #     expected_profit = self.sell_expected_profit.value
        #
        # if (current_profit > expected_profit) & (last_candle['pct_change_1_1d'] < 0):
        #     return "exp_profit_down"

        if (current_profit >= - self.sell_too_old_percent.value) & ((current_time - trade.open_date_utc).days >= self.sell_too_old_day.value)\
                & ((current_time - trade.open_date_utc).days < self.sell_too_old_day.value * 2)\
                & (previous_last_candle['sma10'] > last_candle['sma10']) & (last_candle['percent3'] < 0):
            return "too_old_0.01"
        if (current_profit >= - self.sell_too_old_percent.value * 2) & ((current_time - trade.open_date_utc).days >= self.sell_too_old_day.value * 2)\
            & ((current_time - trade.open_date_utc).days < self.sell_too_old_day.value * 3) \
                & (previous_last_candle['sma10'] > last_candle['sma10']) & (last_candle['percent3'] < 0):
            return "too_old_0.02"
        if (current_profit >= - self.sell_too_old_percent.value * 3) & ((current_time - trade.open_date_utc).days >= self.sell_too_old_day.value * 3) \
                & (previous_last_candle['sma10'] > last_candle['sma10']) & (last_candle['percent3'] < 0):
            return "too_old_0.03"

        if self.profit_quick_lost.value and (current_profit >= 0.015) & (last_candle['percent3'] < -0.005):
            return "quick_lost"

        if self.profit_no_change.value and (current_profit > self.sell_profit_no_change.value) \
                & (last_candle['percent10'] < self.sell_profit_percent10.value) & (last_candle['percent5'] < 0) \
                & ((current_time - trade.open_date_utc).seconds >= 3600):
            return "no_change"

        if (current_profit > self.sell_percent.value) & (last_candle['percent3'] < - self.sell_percent3.value) \
                & ((current_time - trade.open_date_utc).seconds <= 300 * self.sell_candels.value):
            return "quick_gain_param"

        if self.profit_sma5.value:
            if (current_profit > expected_profit) \
                    & ((previous_5_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 'sma5'

        if self.profit_sma10.value:
            if (current_profit > expected_profit) \
                    & ((previous_5_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 'sma10'

        if self.profit_sma20.value:
            if (current_profit > 0.005) \
                    & (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['percent10'] < 0) | (last_candle['percent20'] < 0))):
                # print("over_bb_band_sma10_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
                return 'sma20'

        if self.profit_over_rsi.value:
            if (current_profit > 0) & (previous_last_candle['rsi'] > self.sell_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 'over_rsi'

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

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

        if self.profit_short_loss.value:
            if (current_profit > -expected_profit) & (previous_last_candle['percent10'] > 0.04) & (last_candle['percent'] < 0)\
                    & ((current_time - trade.open_date_utc).days >= 1): #| (previous_last_candle['rsi'] > 75 & last_candle['rsi'] < 70)):
                # print("over_rsi", pair, trade, " profit=", current_profit, " rate=", current_rate)
                return 'short_lost'

    def informative_pairs(self):
        # get access to all pairs available in whitelist.
        pairs = self.dp.current_whitelist()
        # Assign tf to each pair so they can be downloaded and cached for strategy.
        # informative_pairs = [(pair, "5m") for pair in pairs]
        informative_pairs = [(pair, '1d') for pair in pairs]
        # informative_pairs += [(pair, '1w') for pair in pairs]

        # Optionally Add additional "static" pairs
        # informative_pairs = [("BTC/USDT", "1w"), ("BTC/USDT", "1d"), ("BTC/USDT", "5m")]

        return informative_pairs

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Add all ta features

        dataframe['trend_ichimoku_base'] = ta.trend.ichimoku_base_line(
            dataframe['high'],
            dataframe['low'],
            window1=9,
            window2=26,
            visual=False,
            fillna=False
        )
        KST = ta.trend.KSTIndicator(
            close=dataframe['close'],
            roc1=10,
            roc2=15,
            roc3=20,
            roc4=30,
            window1=10,
            window2=10,
            window3=10,
            window4=15,
            nsig=9,
            fillna=False
        )

        dataframe['trend_kst_diff'] = KST.kst_diff()
        dataframe['pct_change'] = dataframe['close'].pct_change(5);
        dataframe['min10'] = talib.MIN(dataframe['close'], timeperiod=10)
        dataframe['min20'] = talib.MIN(dataframe['close'], timeperiod=20)
        dataframe['min50'] = talib.MIN(dataframe['close'], timeperiod=50)
        dataframe['min200'] = talib.MIN(dataframe['close'], timeperiod=200)
        dataframe['max50'] = talib.MAX(dataframe['close'], timeperiod=50)
        dataframe['max200'] = talib.MAX(dataframe['close'], timeperiod=200)
        dataframe['rsi'] = talib.RSI(dataframe)
        dataframe['sma5'] = talib.SMA(dataframe, timeperiod=5)
        dataframe['sma10'] = talib.SMA(dataframe, timeperiod=10)
        dataframe['sma20'] = talib.SMA(dataframe, timeperiod=20)
        dataframe['sma50'] = talib.SMA(dataframe, timeperiod=50)
        dataframe['sma100'] = talib.SMA(dataframe, timeperiod=100)
        dataframe["percent"] = (dataframe["close"] - dataframe["open"]) / dataframe["open"]
        dataframe["percent5"] = dataframe["percent"].rolling(5).sum()
        dataframe["percent3"] = dataframe["percent"].rolling(3).sum()
        dataframe["percent10"] = dataframe["percent"].rolling(10).sum()
        dataframe["percent20"] = dataframe["percent"].rolling(20).sum()
        dataframe["percent50"] = dataframe["percent"].rolling(50).sum()
        dataframe['sar'] = talib.SAR(dataframe)

        # Normalization
        tib = dataframe['trend_ichimoku_base']
        dataframe['trend_ichimoku_base'] = (tib-tib.min())/(tib.max()-tib.min())
        tkd = dataframe['trend_kst_diff']
        dataframe['trend_kst_diff'] = (tkd-tkd.min())/(tkd.max()-tkd.min())

        informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1d")
        informative["rsi"] = talib.RSI(informative)
        informative["max3"] = talib.MAX(informative['close'], timeperiod=3)
        informative["min3"] = talib.MIN(informative['close'], timeperiod=3)
        informative['pct_change_1'] = informative['close'].pct_change(1)
        informative['pct_change_3'] = informative['close'].pct_change(3)
        informative['pct_change_5'] = informative['close'].pct_change(5)
        informative['sma3'] = talib.SMA(informative, timeperiod=3)
        informative['sma5'] = talib.SMA(informative, timeperiod=5)
        informative['sma10'] = talib.SMA(informative, timeperiod=10)
        informative['sar'] = talib.SAR(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["bb_percent"] = (
            (informative["close"] - informative["bb_lowerband"]) /
            (informative["bb_upperband"] - informative["bb_lowerband"])
        )
        informative["bb_width"] = (
            (informative["bb_upperband"] - informative["bb_lowerband"]) / informative["bb_middleband"]
        )

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

    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []
        IND = 'trend_ichimoku_base'
        REAL = self.buy_real.value
        OPR = self.buy_cat.value
        DFIND = dataframe[IND]
        # print(DFIND.mean())
        if OPR == ">R":
            conditions.append(DFIND > REAL)
        elif OPR == "=R":
            conditions.append(np.isclose(DFIND, REAL))
        elif OPR == "<R":
            conditions.append(DFIND < REAL)

        IND = 'trend_kst_diff'
        REAL = self.buy_real_diff.value
        OPR = self.buy_cat_diff.value
        DFIND = dataframe[IND]

        if OPR == ">R":
            conditions.append(DFIND > REAL)
        elif OPR == "=R":
            conditions.append(np.isclose(DFIND, REAL))
        elif OPR == "<R":
            conditions.append(DFIND < REAL)

        if conditions:
            dataframe.loc[
                (reduce(lambda x, y: x & y, conditions))
                & (dataframe['pct_change'] < - self.buy_pct.value)
                & (dataframe['close'] <= dataframe['min200'] * 1.002)
                & (dataframe['pct_change_1_1d'] > self.buy_pct_1.value)
                & (dataframe['pct_change_3_1d'] > self.buy_pct_3.value)
                & (dataframe['pct_change_5_1d'] > 0) #self.buy_pct_5.value)
                #& (dataframe['close_1d'] < dataframe['bb_lowerband_1d'] * self.buy_bb_lowerband.value)
                & (dataframe['bb_width_1d'] >= self.buy_bb_width.value)
                & (dataframe['close'] <= dataframe['sma5_1d'])
                & (dataframe['sma10_1d'].shift(1) <= dataframe['sma10_1d'])
                ,
                'buy'] = 1

        return dataframe

    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # conditions = []
        # IND = 'trend_kst_diff'
        # REAL = self.sell_real.value
        # OPR = self.sell_cat.value
        # DFIND = dataframe[IND]
        # # print(DFIND.mean())
        #
        # if OPR == ">R":
        #     conditions.append(DFIND > REAL)
        # elif OPR == "=R":
        #     conditions.append(np.isclose(DFIND, REAL))
        # elif OPR == "<R":
        #     conditions.append(DFIND < REAL)
        #
        # if conditions:
        #     dataframe.loc[
        #         reduce(lambda x, y: x & y, conditions),
        #         'sell'] = 1

        return dataframe
