# source: https://raw.githubusercontent.com/Sypers/SeniorProject/c219e2539e43c205542af9f2b59d8e473881d52e/user_data/strategies/MediumTerm.py
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# --- Do not remove these libs ---
from datetime import datetime  # noqa
from functools import reduce
from typing import Optional, Union  # noqa
import numpy as np  # noqa
import pandas as pd  # noqa
import ta.utils
from pandas import DataFrame, Series  # noqa
from ta.momentum import StochRSIIndicator
from ta.trend import SMAIndicator

# --------------------------------
# Add your lib to import here
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.strategy import (DecimalParameter,
                                IStrategy, IntParameter, BooleanParameter)


# BEST Results from strategy github_Sypers_SeniorProject__MediumTerm__20221106_141807 backtesting:
# ============================================================= BACKTESTING REPORT ============================================================
# |      Pair |   Entries |   Avg Profit % |   Cum Profit % |   Tot Profit USDT |   Tot Profit % |     Avg Duration |   Win  Draw  Loss  Win% |
# |-----------+-----------+----------------+----------------+-------------------+----------------+------------------+-------------------------|
# | FLUX/USDT |        12 |           7.31 |          87.77 |            87.849 |           8.78 |   1 day, 9:40:00 |     7     0     5  58.3 |
# |  XRP/USDT |        18 |           1.39 |          24.95 |            25.036 |           2.50 |  3 days, 3:20:00 |    11     0     7  61.1 |
# | ALGO/USDT |        17 |           1.19 |          20.25 |            20.254 |           2.03 |  3 days, 1:25:00 |    10     0     7  58.8 |
# | Backtesting from            | 2021-09-04 12:00:00 |
# | Backtesting to              | 2022-09-27 08:00:00 |

class github_Sypers_SeniorProject__MediumTerm__20221106_141807(IStrategy):
    # Strategy interface version - allow new iterations of the strategy interface.
    # Check the documentation or the Sample strategy to get the latest version.
    INTERFACE_VERSION = 3
    # Optimal timeframe for the strategy.
    timeframe = '4h'
    # Can this strategy go short?
    can_short: bool = False
    # Minimal ROI designed for the strategy.
    # This attribute will be overridden if the config file contains "minimal_roi".
    # Return on investment values has been optimized using hyperopt
    minimal_roi = {
        "0": 0.607,
        "1763": 0.272,
        "4615": 0.065,
        "8337": 0
    }
    # Optimal stoploss designed for the strategy.
    # This attribute will be overridden if the config file contains "stoploss".
    # Stoploss and trailing stoploss has been optimized with hyperopt
    stoploss = -0.125

    # Trailing stoploss
    trailing_stop = True
    trailing_stop_positive = 0.012
    trailing_stop_positive_offset = 0.092
    trailing_only_offset_is_reached = False

    # Run "populate_indicators()" only for new candle.
    process_only_new_candles = True

    # These values can be overridden in the config.
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    # Number of candles the strategy requires before producing valid signals
    startup_candle_count: int = 21

    # Strategy/Hyperopt parameters
    # Using the strategy for backtesting or trading will only take the default value.
    # the value the stochastic indicator has to reach for a buy signal.
    buy_stoch = DecimalParameter(low=0.01, high=0.3, default=0.155, space="buy")
    # the value the stochastic indicator has to reach for a sell signal.
    sell_stoch = DecimalParameter(low=0.65, high=1, default=0.805, space="sell")
    # value for number of candles for trade locking after buy/sell
    candle_cooldown = IntParameter(0, 5, default=3, space="protection")
    # enable or disable RSI buy signal.
    enable_buy_rsi = BooleanParameter(default=True, space="buy")
    # enable or disable Stochastic buy signal.
    enable_buy_stoch = BooleanParameter(default=True, space="buy")
    # enable or disable MACD buy signal.
    enable_buy_macd = BooleanParameter(default=True, space="buy")
    # enable or disable RSI sell signal.
    enable_sell_rsi = BooleanParameter(default=True, space="sell")
    # enable or disable Stochastic sell signal.
    enable_sell_stoch = BooleanParameter(default=True, space="sell")
    # enable or disable MACD sell signal.
    enable_sell_macd = BooleanParameter(default=True, space="sell")

    # Optional order type mapping.
    order_types = {
        'entry': 'limit',
        'exit': 'limit',
        'stoploss': 'market',
        'stoploss_on_exchange': False
    }

    # Optional order time in force.
    order_time_in_force = {
        'entry': 'gtc',
        'exit': 'gtc'
    }

    @property
    def protections(self):
        return [
            {
                # Lock pair trading when a sell signal is issued for a certain number of candles to avoid repeated signals
                "method": "CooldownPeriod",
                "stop_duration_candles": self.candle_cooldown.value
            }
        ]

    @property
    def plot_config(self):
        return {
            # Main plot indicators (Moving averages, ...)
            'main_plot': {

            },
            'subplots': {
                # Subplots - each dict defines one additional plot
                'MACD':{
                    'macd': {'color': 'blue', 'fill_to': 'macdhist'},
                    'macdsignal': {'color': 'orange'},
                    'macdhist': {'type': 'bar', 'plotly': {'opacity': 0.9}}
                },
                "RSI": {
                    'rsi': {'color': 'red'}
                },
                'StochasticRSI': {
                    'stoch': {'color': 'green'}
                }
            }
        }

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        rsi = ta.momentum.RSIIndicator(dataframe['close'])  # load RSI indicator
        stoch = ta.momentum.StochRSIIndicator(dataframe['close'])  # load Stochastic indicator
        macd = ta.trend.MACD(dataframe['close'], window_slow=21, window_fast=8, window_sign=5)  # load MACD indicator
        # attach indicators data to a new column to the pair dataframe
        dataframe['stoch'] = stoch.stochrsi()
        dataframe['rsi'] = rsi.rsi()
        dataframe['macd'] = macd.macd()
        dataframe['macdsignal'] = macd.macd_signal()
        dataframe['stoch_buy'] = self.stoch_check(dataframe, True)
        dataframe['stoch_sell'] = self.stoch_check(dataframe, False)
        """
        Adds several different TA indicators to the given DataFrame

        Performance Note: For the best performance be frugal on the number of indicators
        you are using. Let uncomment only the indicator you are using in your strategies
        or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
        :param dataframe: Dataframe with data from the exchange
        :param metadata: Additional information, like the currently traded pair
        :return: a Dataframe with all mandatory indicators for the strategies
        """

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Based on TA indicators, populates the entry signal for the given dataframe
        :param dataframe: DataFrame
        :param metadata: Additional information, like the currently traded pair
        :return: DataFrame with entry columns populated
        """
        conditions = []
        if self.enable_buy_rsi.value:
            conditions.append(dataframe['rsi'] >= 50)
        if self.enable_buy_stoch.value:
            conditions.append(
                (dataframe['stoch_buy']) | dataframe['stoch_buy'].shift() | dataframe['stoch_buy'].shift(2)
                | dataframe['stoch_buy'].shift(3))
        if self.enable_buy_macd.value:
            conditions.append(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal']) |
                              qtpylib.crossed_above(dataframe['macd'].shift(), dataframe['macdsignal'].shift()) |
                              qtpylib.crossed_above(dataframe['macd'].shift(2), dataframe['macdsignal'].shift(2)))
        # for hyperopt if there is no conditions then return dataframe with no signals
        if len(conditions) == 0:
            return dataframe
        # locate the row which meets these conditions:
        dataframe.loc[
            (
                reduce(lambda x, y: x & y, conditions)
            ),
            'enter_long'] = 1
        # dataframe.loc[
        #     (
        #         # RSI value is more than 50 means a bullish (up) trend
        #         (dataframe['rsi'] >= 50) &
        #         # check if there is stochasticRSI buy signal in the previous 3 rows/candles
        #         ((dataframe['stoch_buy']) | dataframe['stoch_buy'].shift() | dataframe['stoch_buy'].shift(2)
        #          | dataframe['stoch_buy'].shift(3)) &
        #         # check if the MACD value crossed above the MACD Signal value within 2 rows
        #         (qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal']))
        #     ), 'enter_long'] = 1  # issue a buy signal if a row is located with all conditions met
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Based on TA indicators, populates the exit signal for the given dataframe
        :param dataframe: DataFrame
        :param metadata: Additional information, like the currently traded pair
        :return: DataFrame with exit columns populated
        """
        conditions = []
        if self.enable_sell_rsi.value:
            conditions.append(dataframe['rsi'] < 50)
        if self.enable_sell_stoch.value:
            conditions.append((dataframe['stoch_sell']) | (dataframe['stoch_sell'].shift()) | (
                dataframe['stoch_sell']).shift(2) | dataframe['stoch_sell'].shift(3))
        if self.enable_sell_macd.value:
            conditions.append(qtpylib.crossed_below(dataframe['macd'], dataframe['macdsignal']) |
                              qtpylib.crossed_below(dataframe['macd'].shift(), dataframe['macdsignal'].shift()) |
                              qtpylib.crossed_below(dataframe['macd'].shift(2), dataframe['macdsignal'].shift(2)))
        # for hyperopt if there is no conditions then return dataframe with no signals
        if len(conditions) == 0:
            return dataframe
        # locate the row which meets these conditions:
        dataframe.loc[
            (
                reduce(lambda x, y: x & y, conditions)
            ), 'exit_long'] = 1
        # dataframe.loc[(
        #     # RSI value is less than 50 means a bearish (down) trend
        #     (dataframe['rsi'] < 50) &
        #     # check if there is stochasticRSI sell signal in the previous 3 rows/candles
        #     ((dataframe['stoch_sell']) | (dataframe['stoch_sell'].shift()) | (
        #         dataframe['stoch_sell']).shift(2)| dataframe['stoch_sell'].shift(3)) &
        #     # check if the MACD value crossed below the MACD Signal value within 2 rows
        #     (qtpylib.crossed_below(dataframe['macd'], dataframe['macdsignal']))
        #      ), 'exit_long'] = 1  # issue a sell signal if a row is located with all conditions met
        return dataframe

    def stoch_check(self, dataframe: DataFrame, buy_signal: bool) -> Series:
        """
        Check the dataframe for Stochastic crossover with stochastic buy and sell values.
        :param dataframe: Dataframe that contains the pair data including the indicators data.
        :param buy_signal: True to check for buy signals, False to check for sell signals of the indicator.
        :return: A pandas Series that includes the rows where Stochastic crossed with Stochastic buy or sell values
        """
        if buy_signal:
            series = qtpylib.crossed_below(dataframe['stoch'], self.buy_stoch.value)
        else:
            series = qtpylib.crossed_above(dataframe['stoch'], self.sell_stoch.value)
        return series
