# source: https://raw.githubusercontent.com/hextropian/hextropian-freqtrade-bots/ab738b6426e3fa197864c13a7d2521ed50c177b2/user_data/strategies/VolatilityCatcher.py
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement

from pandas import DataFrame

from freqtrade.strategy import IStrategy
from typing import Optional
from datetime import datetime
from freqtrade.persistence import Trade
from datetime import timedelta
from freqtrade.strategy import stoploss_from_open, DecimalParameter, IntParameter


import talib.abstract as ta
import numpy as np
import pandas_ta as pta
import freqtrade.vendor.qtpylib.indicators as qtpylib
import logging
import math

log = logging.getLogger(__name__)

# This strategy leverages the high volatility in the lower timeframes for tokens with high volatility
# ---
# Designed and written by @hextropian (Twitter), a.k.a. as DrWho?#8511 (Discord)
# Use at your own risk - no warranties of success whatsoever.

# Hyperopted parameters for 5m

class github_hextropian_hextropian_freqtrade_bots__VolatilityCatcher__20221112_164747(IStrategy):
  
    INTERFACE_VERSION = 3

    minimal_roi = {
        "0": 0.14,
        "37": 0.078,
        "97": 0.023,
        "160": 0
    }

    stoploss = -0.99 # Disabled, using custom
    use_custom_stoploss = True 

    # Trailing stop:
    trailing_stop = True
    trailing_stop_positive = 0.347
    trailing_stop_positive_offset = 0.358
    trailing_only_offset_is_reached = True

    # Optimal timeframe for the strategy.
    timeframe = '5m'

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

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

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

    rsi_threshold = IntParameter(low=3, high=60, default=50, space='buy', optimize=True, load=True)
    rsi_overbought = IntParameter(low=50, high=98, default=75, space='sell', optimize=True, load=True)
    reversal_threshold = DecimalParameter(0.01, 0.5, default=0.1, decimals=2, space='buy', optimize=True, load=True)

    # trailing stoploss hyperopt parameters
    opt_HSL = False # Optimize?
    # hard stoploss profit
    pHSL = DecimalParameter(-0.200, -0.040, default=-0.08, decimals=3, space='sell', optimize=opt_HSL, 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=opt_HSL, load=True)
    pSL_1 = DecimalParameter(0.008, 0.020, default=0.011, decimals=3, space='sell', optimize=opt_HSL, load=True)

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

    buy_params = {
        "reversal_threshold": 0.36,
        "rsi_threshold": 10,
    }

    sell_params = {
        "pHSL": -0.189,
        "pPF_1": 0.009,
        "pPF_2": 0.065,
        "pSL_1": 0.009,
        "pSL_2": 0.057,
        "rsi_overbought": 89,
    }


    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, **kwargs) -> float:

        # 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
        
        return stoploss_from_open(sl_profit, current_profit)


    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],
                            side: str, **kwargs) -> bool:
        """
        From NostalgiaForInfinityX by iterativ 
        https://github.com/iterativv/NostalgiaForInfinity
        allow force entries and protects against slippage.
        """

        if (entry_tag == 'force_entry'):
            return True

        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)

        if(len(dataframe) < 1):
            return True

        dataframe = dataframe.iloc[-1].squeeze()

        if ((rate > dataframe['close'])):
            slippage = ((rate / dataframe['close']) - 1.0)

            if slippage < 0.044:
                return True
            else:
                log.warning(
                    "Cancelling buy for %s due to slippage %s",
                    pair, slippage
                )
                return False

        return True


    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        
        # 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['rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['volma'] = ta.SMA(dataframe['volume'], timeperiod=60)

        dataframe['ema5c'] = ta.EMA(dataframe['close'], timeperiod=5)
        dataframe['ema4o'] = ta.EMA(dataframe['open'], timeperiod=4)

        return dataframe


    #################################################################################
    ##                                                                             ## 
    ##                            BUY (Enter) conditions                           ##
    ##                                                                             ## 
    ################################################################################# 
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    
        dataframe.loc[
            (
                (
                    (dataframe['close'] > dataframe['open']) & # Current candle is green
                    (dataframe['close'] < dataframe['bb_middleband']) # and closes below the middle BB
                )
                &
                (
                    (dataframe['close'].shift(1) < dataframe['open'].shift(1)) # Previous candle is red
                   &(dataframe['close'].shift(1) <= dataframe['bb_lowerband'].shift(1)) # and closes below the BB
                )      
                &
                (   # Volume is above its MA for any of the last 3 candles
                      (dataframe['volume'] >= dataframe['volma'])
                    | (dataframe['volume'].shift(1) >= dataframe['volma'].shift(1))
                    | (dataframe['volume'].shift(2) >= dataframe['volma'].shift(2))
                )
                &
                (   # Candle marks a reversal on [virtually] the same place
                    (dataframe['open'] - dataframe['close'].shift(1)).abs() <= self.reversal_threshold.value
                )
                &
                (   # Oversold RSI on previous candle
                    (dataframe['rsi'].shift(1) <= self.rsi_threshold.value)
                )
                & (dataframe['volume'] > 0)
            ),
            'enter_long'] = 1

        return dataframe
    
    #################################################################################
    ##                                                                             ## 
    ##                            SELL (Exit) conditions                           ##
    ##                                                                             ## 
    ################################################################################# 
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                   (dataframe['close'].shift(1) > dataframe['open'].shift(1)) & # Previous candle is green
                   (dataframe['close'].shift(1) >= dataframe['bb_upperband']) & # Above its upperband
                   (dataframe['rsi'].shift(1) >= self.rsi_overbought.value) &
                   # Now we look at the current candle:
                   (dataframe['ema5c'] <= dataframe['ema4o']) & # Lost momentum
                   (dataframe['volume'] > 0)
            ),
            'exit_long'] = 1

        return dataframe
