# GodStraNew Strategy
# Author: @Mablue (Masoud Azizi)
# github: https://github.com/mablue/
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --spaces buy roi trailing sell --strategy GodStraNew
# --- Do not remove these libs ---
from datetime import timedelta, datetime

from freqtrade import data
from freqtrade.persistence import Trade
from freqtrade.strategy.parameters import  CategoricalParameter, DecimalParameter, IntParameter

from numpy.lib import math
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame

# --------------------------------

# Add your lib to import here
# TODO: talib is fast but have not more indicators
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from functools import reduce
import numpy as np
from random import shuffle

operators = [
    "D",  # Disabled gene
    ">",  # Indicator, bigger than cross indicator
    "<",  # Indicator, smaller than cross indicator
    "=",  # Indicator, equal with cross indicator
    "C",  # Indicator, crossed the cross indicator
    "CA",  # Indicator, crossed above the cross indicator
    "CB",  # Indicator, crossed below the cross indicator
    ">R",  # Normalized indicator, bigger than real number
    "=R",  # Normalized indicator, equal with real number
    "<R",  # Normalized indicator, smaller than real number
    "/>R",  # Normalized indicator devided to cross indicator, bigger than real number
    "/=R",  # Normalized indicator devided to cross indicator, equal with real number
    "/<R",  # Normalized indicator devided to cross indicator, smaller than real number
    "UT",  # Indicator, is in UpTrend status
    "DT",  # Indicator, is in DownTrend status
    "OT",  # Indicator, is in Off trend status(RANGE)
    "CUT",  # Indicator, Entered to UpTrend status
    "CDT",  # Indicator, Entered to DownTrend status
    "COT"  # Indicator, Entered to Off trend status(RANGE)
]
# number of candles to check up,don,off trend.
TREND_CHECK_CANDLES = 8
DECIMALS = 2

buy_crossed_indicator0 = 'MINUS_DM-5'
buy_crossed_indicator1 = 'DX-5'
buy_crossed_indicator2 = 'LINEARREG-50'
buy_indicator0 = 'MA-20'
buy_indicator1 = 'STOCH-1-10'
buy_indicator2 = 'CDLDRAGONFLYDOJI-5'
buy_operator0 = "/<R"
buy_operator1 = ">"
buy_operator2 = "/<R"

sell_crossed_indicator0 = 'MIDPOINT-10'
sell_crossed_indicator1 = 'CDLTRISTAR-10"'
sell_crossed_indicator2 = 'CDLBELTHOLD-50'
sell_indicator0 = 'CDLLONGLINE-20'
sell_indicator1 = 'STOCHF-1-50'
sell_indicator2 = 'MACDEXT-0-5'
sell_operator0 = "<"
sell_operator1 = ">R"
sell_operator2 = ">"

# buy_real_num0 =  0.1,
# buy_real_num1 =  0.0,
# buy_real_num2 =  0.5

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


def gene_calculator(dataframe, indicator):
    # Cuz Timeperiods not effect calculating CDL patterns recognations
    if 'CDL' in indicator:
        splited_indicator = indicator.split('-')
        splited_indicator[1] = "0"
        new_indicator = "-".join(splited_indicator)
        # print(indicator, new_indicator)
        indicator = new_indicator

    gene = indicator.split("-")

    gene_name = gene[0]
    gene_len = len(gene)

    if indicator in dataframe.keys():
        # print(f"{indicator}, calculated befoure")
        # print(len(dataframe.keys()))
        return dataframe[indicator]
    else:
        result = None
        # For Pattern Recognations
        if gene_len == 1:
            # print('gene_len == 1\t', indicator)
            result = getattr(ta, gene_name)(
                dataframe
            )
            return normalize(result)
        elif gene_len == 2:
            # print('gene_len == 2\t', indicator)
            gene_timeperiod = int(gene[1])
            result = getattr(ta, gene_name)(
                dataframe,
                timeperiod=gene_timeperiod,
            )
            return normalize(result)
        # For
        elif gene_len == 3:
            # print('gene_len == 3\t', indicator)
            gene_timeperiod = int(gene[2])
            gene_index = int(gene[1])
            result = getattr(ta, gene_name)(
                dataframe,
                timeperiod=gene_timeperiod,
            ).iloc[:, gene_index]
            return normalize(result)
        # For trend operators(MA-5-SMA-4)
        elif gene_len == 4:
            # print('gene_len == 4\t', indicator)
            gene_timeperiod = int(gene[1])
            sharp_indicator = f'{gene_name}-{gene_timeperiod}'
            dataframe[sharp_indicator] = getattr(ta, gene_name)(
                dataframe,
                timeperiod=gene_timeperiod,
            )
            return normalize(ta.SMA(dataframe[sharp_indicator].fillna(0), TREND_CHECK_CANDLES))
        # For trend operators(STOCH-0-4-SMA-4)
        elif gene_len == 5:
            # print('gene_len == 5\t', indicator)
            gene_timeperiod = int(gene[2])
            gene_index = int(gene[1])
            sharp_indicator = f'{gene_name}-{gene_index}-{gene_timeperiod}'
            dataframe[sharp_indicator] = getattr(ta, gene_name)(
                dataframe,
                timeperiod=gene_timeperiod,
            ).iloc[:, gene_index]
            return normalize(ta.SMA(dataframe[sharp_indicator].fillna(0), TREND_CHECK_CANDLES))


def condition_generator(dataframe, operator, indicator, crossed_indicator, real_num):

    condition = (dataframe['volume'] > 10)

    # TODO : it ill callculated in populate indicators.

    dataframe[indicator] = gene_calculator(dataframe, indicator)
    dataframe[crossed_indicator] = gene_calculator(dataframe, crossed_indicator)

    indicator_trend_sma = f"{indicator}-SMA-{TREND_CHECK_CANDLES}"
    if operator in ["UT", "DT", "OT", "CUT", "CDT", "COT"]:
        dataframe[indicator_trend_sma] = gene_calculator(
            dataframe, indicator_trend_sma)

    if operator == ">":
        condition = (
            dataframe[indicator] > dataframe[crossed_indicator]
        )
    elif operator == "=":
        condition = (
            np.isclose(dataframe[indicator], dataframe[crossed_indicator])
        )
    elif operator == "<":
        condition = (
            dataframe[indicator] < dataframe[crossed_indicator]
        )
    elif operator == "C":
        condition = (
            (qtpylib.crossed_below(dataframe[indicator], dataframe[crossed_indicator])) |
            (qtpylib.crossed_above(
                dataframe[indicator], dataframe[crossed_indicator]))
        )
    elif operator == "CA":
        condition = (
            qtpylib.crossed_above(
                dataframe[indicator], dataframe[crossed_indicator])
        )
    elif operator == "CB":
        condition = (
            qtpylib.crossed_below(
                dataframe[indicator], dataframe[crossed_indicator])
        )
    elif operator == ">R":
        condition = (
            dataframe[indicator] > real_num
        )
    elif operator == "=R":
        condition = (
            np.isclose(dataframe[indicator], real_num)
        )
    elif operator == "<R":
        condition = (
            dataframe[indicator] < real_num
        )
    elif operator == "/>R":
        condition = (
            dataframe[indicator].div(dataframe[crossed_indicator]) > real_num
        )
    elif operator == "/=R":
        condition = (
            np.isclose(dataframe[indicator].div(
                dataframe[crossed_indicator]), real_num)
        )
    elif operator == "/<R":
        condition = (
            dataframe[indicator].div(dataframe[crossed_indicator]) < real_num
        )
    elif operator == "UT":
        condition = (
            dataframe[indicator] > dataframe[indicator_trend_sma]
        )
    elif operator == "DT":
        condition = (
            dataframe[indicator] < dataframe[indicator_trend_sma]
        )
    elif operator == "OT":
        condition = (

            np.isclose(dataframe[indicator], dataframe[indicator_trend_sma])
        )
    elif operator == "CUT":
        condition = (
            (
                qtpylib.crossed_above(
                    dataframe[indicator],
                    dataframe[indicator_trend_sma]
                )
            ) &
            (
                dataframe[indicator] > dataframe[indicator_trend_sma]
            )
        )
    elif operator == "CDT":
        condition = (
            (
                qtpylib.crossed_below(
                    dataframe[indicator],
                    dataframe[indicator_trend_sma]
                )
            ) &
            (
                dataframe[indicator] < dataframe[indicator_trend_sma]
            )
        )
    elif operator == "COT":
        condition = (
            (
                (
                    qtpylib.crossed_below(
                        dataframe[indicator],
                        dataframe[indicator_trend_sma]
                    )
                ) |
                (
                    qtpylib.crossed_above(
                        dataframe[indicator],
                        dataframe[indicator_trend_sma]
                    )
                )
            ) &
            (
                np.isclose(
                    dataframe[indicator],
                    dataframe[indicator_trend_sma]
                )
            )
        )

    return condition, dataframe


class GodStraJD3_5_2(IStrategy):
    # #################### RESULTS PASTE PLACE ####################
    # ROI table:
    minimal_roi = {
        "0": 10,
        # "600": 0.12,
        # "1200": 0.08,
        # "2400": 0.06,
        # "3600": 0.04,
        # "7289": 0
    }

    # Stoploss:
    stoploss = -1
    # Buy hypers
    timeframe = '5m'

    # Trailing stoploss
    trailing_stop = False
    trailing_stop_positive = 0.15
    trailing_stop_positive_offset = 0.20
    trailing_only_offset_is_reached = True

    plot_config = {
        # Main plot indicators (Moving averages, ...)
        'main_plot': {
            'bb_lowerband': {'color': 'red'},
            'bb_upperband': {'color': 'green'},
            'sma100': {'color': 'blue'},
            'sma10': {'color': 'yellow'},
            'min': {'color': 'white'},
            'max': {'color': 'white'},
            'sma20': {'color': 'cyan'}
        },
        'subplots': {
            # Subplots - each dict defines one additional plot
            "BB": {
                'bb_width': {'color': 'white'},
                'bb_min': {'color': 'red'},
            },
            "Ind0": {
                buy_crossed_indicator0: {'color': 'green'},
                buy_indicator0: {'color': 'red'}
            },
            "Ind1": {
                buy_indicator1: {'color': 'yellow'},
                buy_crossed_indicator1: {'color': 'pink'}
            },
            "Ind2": {
                buy_indicator2: {'color': 'cyan'},
                buy_crossed_indicator2: {'color': 'blue'},
            },
            "Sell0": {
                sell_crossed_indicator0: {'color': 'green'},
                sell_indicator0: {'color': 'red'}
            },
            "Sell1": {
                sell_indicator1: {'color': 'yellow'},
                sell_crossed_indicator1: {'color': 'pink'}
            },
            "Sell2": {
                sell_indicator2: {'color': 'cyan'},
                sell_crossed_indicator2: {'color': 'blue'},
            },
            "Rsi": {
                'rsi': {'color': 'pink'},
            },
            # "rolling": {
            #     'bb_rolling': {'color': '#87e470'},
            #     "bb_rolling_min": {'color': '#ac3e2a'}
            # },
            "percent": {
                "percent": {'color': 'green'},
                "percent3": {'color': 'blue'},
                "percent5": {'color': 'red'}
            }
        }
    }


    # #################### END OF RESULT PLACE ####################

    # TODO: Its not dry code!
    # Buy Hyperoptable Parameters/Spaces.
    buy_real_num0 = DecimalParameter(0, 1, decimals=DECIMALS, default=0.1, space='buy')
    buy_real_num1 = DecimalParameter(0, 1, decimals=DECIMALS, default=0.0, space='buy')
    buy_real_num2 = DecimalParameter(0, 1, decimals=DECIMALS, default=0.5, space='buy')

    sell_real_num0 = DecimalParameter(0, 1, decimals=DECIMALS, default=0.09731, space='sell')
    sell_real_num1 = DecimalParameter(0, 1, decimals=DECIMALS, default=0.81657, space='sell')
    sell_real_num2 = DecimalParameter(0, 1, decimals=DECIMALS, default=0.87267, space='sell')

    @property
    def protections(self):
        return [
            {
                "method": "CooldownPeriod",
                "stop_duration_candles": 12
            },
            {
                "method": "MaxDrawdown",
                "lookback_period_candles": 48,
                "trade_limit": 2,
                "stop_duration_candles": 48,
                "max_allowed_drawdown": 0.04,
                "only_per_pair": False
            },
        ]

    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()

        if (current_profit > 0.015) & (last_candle['percent'] < 0) & ((current_time - trade.open_date_utc).seconds <= 3600):
            return "quick_gain"
        if (current_profit > 0.01) & (last_candle['rsi'] < 20):
            return "small_rsi"
        if (current_profit > 0.005) & (last_candle['percent3'] < -0.01):
            # print("over_bb_band_sma10_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
            return 'quick_lost'
        if (current_profit < -0.02) & ((current_time - trade.open_date_utc).seconds >= 3600):
            return "stop_loss_1h"

        if (current_profit > 0.005) & (last_candle['percent5'] < -0.015):
            # print("over_bb_band_sma10_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
            return 'very_quick_lost'

        # if (current_profit > 0.01) \
        #         & ((previous_5_candle['sma10'] > last_candle['sma10'] * 1.005) \
        #             | (last_candle['percent3'] < -0.01) | (last_candle['percent5'] < -0.01)) \
        #         & ((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 (current_profit > 0.005) & (last_candle['percent5'] < 0) \
        #         & ((current_time - trade.open_date_utc).seconds >= 3600) \
        #         & ((previous_last_candle['sma20'] > last_candle['sma20']) &
        #             ((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 (current_profit > 0) \
        #         & ((current_time - trade.open_date_utc).days >= 3) \
        #         & ((previous_5_candle['sma10'] > last_candle['sma10']) | (last_candle['percent3'] < -0.005) | (last_candle['percent5'] < -0.005)) \
        #         & ((last_candle['percent'] < 0) & (last_candle['percent3'] < 0)):
        #     # print("over_bb_band_sma10_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
        #     return 'old_sma10'
        #
        # if (current_profit > -0.01) \
        #         & ((current_time - trade.open_date_utc).days >= 6) \
        #         & ((previous_5_candle['sma10'] > last_candle['sma10']) | (last_candle['percent3'] < 0) | (last_candle['percent5'] < 0)) \
        #         & ((last_candle['percent'] < 0) & (last_candle['percent3'] < 0)):
        #     # print("over_bb_band_sma10_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
        #     return 'very_old_sma10'

        if (current_profit > 0) \
                & (previous_last_candle['rsi'] > 88) \
                & ((last_candle['percent'] < - current_profit / 3) | (last_candle['percent3'] < - current_profit / 3)):
            # print("over_rsi", pair, trade, " profit=", current_profit, " rate=", current_rate)
            return 'over_rsi'

        # if (current_profit > -0.01) & (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'

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

    def informative_pairs(self):
        return []

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

        dataframe[buy_crossed_indicator0] = gene_calculator(dataframe, buy_crossed_indicator0)
        dataframe[buy_crossed_indicator1] = gene_calculator(dataframe, buy_crossed_indicator1)
        dataframe[buy_crossed_indicator2] = gene_calculator(dataframe, buy_crossed_indicator2)

        dataframe[buy_indicator0] = gene_calculator(dataframe, buy_indicator0)
        dataframe[buy_indicator1] = gene_calculator(dataframe, buy_indicator1)
        dataframe[buy_indicator2] = gene_calculator(dataframe, buy_indicator2)

        dataframe[sell_crossed_indicator0] = gene_calculator(dataframe, sell_crossed_indicator0)
        dataframe[sell_crossed_indicator1] = gene_calculator(dataframe, sell_crossed_indicator1)
        dataframe[sell_crossed_indicator2] = gene_calculator(dataframe, sell_crossed_indicator2)

        dataframe[sell_indicator0] = gene_calculator(dataframe, sell_indicator0)
        dataframe[sell_indicator1] = gene_calculator(dataframe, sell_indicator1)
        dataframe[sell_indicator2] = gene_calculator(dataframe, sell_indicator2)

        # RSI
        dataframe['rsi'] = ta.RSI(dataframe)
        dataframe['sma10'] = ta.SMA(dataframe, timeperiod=10)
        dataframe['sma20'] = ta.SMA(dataframe, timeperiod=20)
        dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50)
        dataframe['sma100'] = ta.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['min'] = ta.MIN(dataframe['close'], timeperiod=200)
        dataframe['min20'] = ta.MIN(dataframe['close'], timeperiod=20)
        dataframe["volume10"] = dataframe["volume"].rolling(10).mean()

        dataframe['max'] = ta.MAX(dataframe['close'], timeperiod=200)
        dataframe['max_min'] = dataframe['max'] / dataframe['min']
        # 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["close"] - dataframe["bb_lowerband"]) /
            (dataframe["bb_upperband"] - dataframe["bb_lowerband"])
        )
        dataframe["bb_width"] = (
            (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe["bb_middleband"]
        )
        dataframe['bb_min'] = ta.MIN(dataframe['bb_lowerband'], timeperiod=36)

        # Bollinger Bands - Weighted (EMA based instead of SMA)
        weighted_bollinger = qtpylib.weighted_bollinger_bands(
            qtpylib.typical_price(dataframe), window=20, stds=2
        )
        dataframe["wbb_upperband"] = weighted_bollinger["upper"]
        dataframe["wbb_lowerband"] = weighted_bollinger["lower"]
        dataframe["wbb_middleband"] = weighted_bollinger["mid"]
        dataframe["wbb_percent"] = (
            (dataframe["close"] - dataframe["wbb_lowerband"]) /
            (dataframe["wbb_upperband"] - dataframe["wbb_lowerband"])
        )
        dataframe["wbb_width"] = (
            (dataframe["wbb_upperband"] - dataframe["wbb_lowerband"]) / dataframe["wbb_middleband"]
        )

        # # EMA - Exponential Moving Average
        # dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
        return dataframe

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

        conditions = list()

        condition, dataframe = condition_generator(
            dataframe,
            buy_operator0,
            buy_indicator0,
            buy_crossed_indicator0,
            self.buy_real_num0.value
        )
        conditions.append(condition)

        # backup
        condition, dataframe = condition_generator(
            dataframe,
            buy_operator1,
            buy_indicator1,
            buy_crossed_indicator1,
            self.buy_real_num1.value
        )
        conditions.append(condition)

        condition, dataframe = condition_generator(
            dataframe,
            buy_operator2,
            buy_indicator2,
            buy_crossed_indicator2,
            self.buy_real_num2.value
        )
        conditions.append(condition)

        # condition_1 = (
        #                 reduce(lambda x, y: x & y, conditions) &
        #                 (dataframe['volume10'] * dataframe['close'] / 1000 >= 100) &
        #                 (dataframe['open'] < dataframe['sma20'])
        #         )
        # condition_2 = (
        #                 (dataframe['bb_width'] > 0.11) &
        #                 (dataframe['volume10'] * dataframe['close'] / 1000 >= 100) &
        #                 (dataframe['rsi'].shift(1) < 20) &
        #                 (dataframe['open'] < dataframe['sma20'])
        #         )
        # if (condition_1):
        #     dataframe.loc[condition_1, 'buy'] = 'buy_opt'
        # if (condition_2):
        #     dataframe.loc[condition_2, 'buy'] = 'bb_width'
        if conditions:
            dataframe.loc[
                (
                        reduce(lambda x, y: x & y, conditions) &
                        (dataframe['volume10'] * dataframe['close'] / 1000 >= 100) &
                        (dataframe['close'] < dataframe['sma20']) &
                        (dataframe['close'] < dataframe['sma50']) &
                        (dataframe['close'] * 0.99 < dataframe['bb_lowerband'])
                ) | (
                        (dataframe['close'] < dataframe['bb_lowerband']) &
                        (dataframe['bb_width'] > 0.11) &
                        (dataframe['volume10'] * dataframe['close'] / 1000 >= 100) &
                        (dataframe['rsi'] < 30) &
                        (dataframe['close'] < dataframe['sma20']) &
                        (dataframe['sma100'].shift(30) < dataframe['sma100'])
                ),'buy']=1

        return dataframe

    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = list()

        condition, dataframe = condition_generator(
            dataframe,
            sell_operator0,
            sell_indicator0,
            sell_crossed_indicator0,
            self.sell_real_num0.value
        )
        conditions.append(condition)

        # backup
        condition, dataframe = condition_generator(
            dataframe,
            sell_operator1,
            sell_indicator1,
            sell_crossed_indicator1,
            self.sell_real_num1.value
        )
        conditions.append(condition)

        condition, dataframe = condition_generator(
            dataframe,
            sell_operator2,
            sell_indicator2,
            sell_crossed_indicator2,
            self.sell_real_num2.value
        )
        conditions.append(condition)

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

