# source: https://raw.githubusercontent.com/skywills/freqtrade-run/1ee4e00fd92b2c2923e76588657bc59f785fae7a/user_data/strategies/doublecci_strategy.py
import numpy as np  # noqa
import pandas as pd  # noqa
from pandas import DataFrame, Series, DatetimeIndex, merge
from functools import reduce
from datetime import datetime

from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
                                IStrategy, IntParameter, stoploss_from_open)

# --------------------------------
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib

rangeUpper = 60
rangeLower = 5


def valuewhen(dataframe, condition, source, occurrence):
    copy = dataframe.copy()
    copy['colFromIndex'] = copy.index
    copy = copy.sort_values(
        by=[condition, 'colFromIndex'], ascending=False).reset_index(drop=True)
    copy['valuewhen'] = np.where(
        copy[condition] > 0, copy[source].shift(-occurrence), copy[source])
    copy['barrsince'] = copy['colFromIndex'] - \
        copy['colFromIndex'].shift(-occurrence)
    copy.loc[
        (
            (rangeLower <= copy['barrsince']) &
            (copy['barrsince'] <= rangeUpper)
        ), "in_range"] = 1
    copy['in_range'] = copy['in_range'].fillna(0)
    copy = copy.sort_values(by=['colFromIndex'],
                            ascending=True).reset_index(drop=True)
    return copy['valuewhen'], copy['in_range']


class github_skywills_freqtrade_run__doublecci_strategy__20220912_095522(IStrategy):

    INTERFACE_VERSION = 3

    # Buy hyperspace params:
    buy_params = {
        'use_bull': True,
        'use_hidden_bull': False,
    }
    # Sell hyperspace params:
    sell_params = {
        'use_bear': True,
        'use_hidden_bear': False
    }

    # Can this strategy go short?
    can_short: bool = False

    # disable minimal_roi which is >10000%
    minimal_roi = {
        "0": 1000
    }

    # Optimal stoploss designed for the strategy.
    # This attribute will be overridden if the config file contains "stoploss".
    stoploss = -0.10

    # Trailing stoploss
    trailing_stop = False
    trailing_stop_positive = 0.005
    trailing_stop_positive_offset = 0.02
    trailing_only_offset_is_reached = False
    use_custom_stoploss = True
    # trailing_only_offset_is_reached = False
    # trailing_stop_positive = 0.01
    # trailing_stop_positive_offset = 0.0  # Disabled / not configured

    # Optimal timeframe for the strategy.
    timeframe = '1h'

    # 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

    use_bull = BooleanParameter(
        default=buy_params['use_bull'], space='buy', optimize=False)
    use_hidden_bull = BooleanParameter(
        default=buy_params['use_hidden_bull'], space='buy', optimize=False)
    use_bear = BooleanParameter(
        default=sell_params['use_bear'], space='sell', optimize=True)
    use_hidden_bear = BooleanParameter(
        default=sell_params['use_hidden_bear'], space='sell', optimize=True)

    osc = 'cci_st'
    src = 'close'
    lbL = 5
    lbR = 5

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

    stoch_length: int = 9

    cci_fast_signal_length: int = 5
    cci_fast_signal_mid_band: int = 0
    cci_fast_signal_upper1_band: int = 100
    cci_fast_signal_upper2_band: int = 130
    cci_fast_signal_lower1_band: int = -100
    cci_fast_signal_lower2_band: int = -130

    cci_slow_signal_length: int = 20
    cci_slow_signal_mid_band: int = 0
    cci_slow_signal_upper1_band: int = 70
    cci_slow_signal_upper2_band: int = 100
    cci_slow_signal_lower1_band: int = -70
    cci_slow_signal_lower2_band: int = -100
    cci_signal_lookback = 4

    cci_fast_trend_length: int = 21
    cci_slow_trend_length: int = 55
    cci_exit_trend_length: int = 144
    cci_upper_band: int = 100

    cci_div_trend_length: int = 144

    sma1_length: int = 21
    sma2_length: int = 55
    sma3_length: int = 144

    # 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'
    }

    plot_config = {
        'main_plot': {
            # 'tema': {},
            # 'sar': {'color': 'white'},
        },
        'subplots': {
            "MACD": {
                'macd': {'color': 'blue'},
                'macdsignal': {'color': 'orange'},
            },
            "RSI": {
                'rsi': {'color': 'red'},
            },
            "CCI EXIT": {
                'cci_exit': {'color': 'red'},
            }
        }
    }

    def informative_pairs(self):
        return []

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

        if current_profit >= 0.10:
            stoploss = current_profit / 2
        else:
            stoploss = current_profit - 0.05 if current_profit - 0.05 > 0 else 0

        return stoploss_from_open(stoploss, current_profit, is_short=trade.is_short)
        # if current_profit < 0.05:
        #     return -1 # return a value bigger than the initial stoploss to keep using the initial stoploss

        # if current_profit < 0:
        #     return -0.05

        # if current_profit >= 0.10:
        #     return current_profit / 2
        # else:
        #     return current_profit - 0.05 if  current_profit - 0.05 > 0 else 0

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        #   #https://github.com/freqtrade/freqtrade/issues/2961
        #   dataframe['RSI'] = ta.RSI(dataframe[self.src], self.stoch_length)
        #   dataframe['RSI'] = dataframe['RSI'].fillna(0)

        #   #Stoch
        #   smoothD = 3
        #   SmoothK = 3
        #   stoch = ta.STOCH(dataframe, fastk_length=self.stoch_length, slowk_length=SmoothK,slowd_length=smoothD)
        #   dataframe['slowk'] = stoch['slowk']
        #   dataframe['slowd'] = stoch['slowd']
        #   dataframe['osc'] = dataframe[self.osc]

        #   dataframe['sma1'] = ta.SMA(dataframe, timeperiod=self.sma1_length)
        #   dataframe['sma2'] = ta.SMA(dataframe, timeperiod=self.sma2_length)
        #   dataframe['sma3'] = ta.SMA(dataframe, timeperiod=self.sma3_length)

        # Fast Slow Signal CCI - fast signal=fs, slow signal=ss
        dataframe['cci_fs'] = ta.CCI(
            dataframe, timeperiod=self.cci_fast_signal_length)
        dataframe['cci_ss'] = ta.CCI(
            dataframe, timeperiod=self.cci_slow_signal_length)

        # Fast Slow Trend CCI - fast trend=ft, slow trend=st
        dataframe['cci_ft'] = ta.CCI(
            dataframe, timeperiod=self.cci_fast_trend_length)
        dataframe['cci_st'] = ta.CCI(
            dataframe, timeperiod=self.cci_slow_trend_length)

        dataframe['cci_div'] = ta.CCI(
            dataframe, timeperiod=self.cci_div_trend_length)

        dataframe['cci_exit'] = ta.CCI(
            dataframe, timeperiod=self.cci_exit_trend_length)

        # CCI Trend Divergence - OSC
        dataframe = self.populate_divergence(dataframe, 'cci_div')

        # Retrieve best bid and best ask from the orderbook
        # ------------------------------------
        """
      # first check if dataprovider is available
      if self.dp:
          if self.dp.runmode.value in ('live', 'dry_run'):
              ob = self.dp.orderbook(metadata['pair'], 1)
              dataframe['best_bid'] = ob['bids'][0][0]
              dataframe['best_ask'] = ob['asks'][0][0]
      """
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        # buy signal 1
          1. Wait for signal CCI 20 cross above level -70
          2. signal CCI 5 must cross above level 130 within 4 bar since CCI 20 cross above level -70
        # buy signal 2
          1. if you find two CCI crosses above level 100 in the same bar, it's mean the prices will in strong upturned

        # notes: assume CCI 20 >= -70 and CCI 20 lookback have < -70, assume is uptrend
        """
        conditions = []

        # CCI Bull Divergence
        conditions.append((
            (dataframe['bullCond'] > 0)
        ))

        # buy signal 1
        conditions.append(
            (
                # red > blue
                (dataframe['cci_ft'] > dataframe['cci_st']) &
                (dataframe['cci_st'] >= -100) &
                (dataframe['cci_ss'] >= self.cci_slow_signal_lower1_band) &
                # (qtpylib.crossed_above(dataframe['cci_ss'], self.cci_slow_signal_lower1_band)) &
                (qtpylib.crossed_above(dataframe['cci_fs'], self.cci_fast_signal_upper2_band)) &
                (dataframe['cci_ss'].rolling(self.cci_signal_lookback).min() < self.cci_slow_signal_lower1_band) &
                (dataframe['volume'] > 0)
            )
        )

        conditions.append(
            (
                # red > blue
                (dataframe['cci_ft'] > dataframe['cci_st']) &
                (dataframe['cci_st'] >= -100) &
                (qtpylib.crossed_above(dataframe['cci_ss'], self.cci_slow_signal_upper2_band)) &
                (qtpylib.crossed_above(dataframe['cci_fs'], self.cci_fast_signal_upper1_band)) &
                (dataframe['volume'] > 0)
            )
        )

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions),
                'enter_long'
            ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        # sell signal 1
          1. Wait for signal CCI 20 cross below level 70
          2. signal CCI 5 must cross below level -130 within 4 bar since CCI 20 cross above level 70
        # sell signal 2
          1. if you find two CCI crosses below level -100 in the same bar, it's mean the prices will in strong downtrend
        """
        conditions = []

        conditions.append(
            (
                (qtpylib.crossed_below(dataframe['cci_exit'], self.cci_upper_band)) &
                (dataframe['volume'] > 0)
            ))

        # conditions.append(
        #     # red > blue
        #     (dataframe['cci_ft'] < dataframe['cci_st']) &
        #     (dataframe['cci_st'] <= 100)
        # )

        # conditions.append(
        #     (
        #         (dataframe['cci_st'] > dataframe['cci_ft']) &
        #         (dataframe['cci_ss'] <= self.cci_slow_signal_upper1_band) &
        #         # (qtpylib.crossed_below(dataframe['cci_ss'], self.cci_slow_signal_upper1_band)) &
        #         (qtpylib.crossed_below(dataframe['cci_fs'], self.cci_fast_signal_lower2_band)) &
        #         (dataframe['cci_ss'].rolling(self.cci_signal_lookback).max() > self.cci_slow_signal_upper1_band) &
        #         (dataframe['volume'] > 0)
        #     )
        # )

        # conditions.append(
        #     (
        #         (dataframe['cci_st'] > dataframe['cci_ft']) &
        #         (qtpylib.crossed_below(dataframe['cci_ss'], self.cci_slow_signal_lower2_band)) &
        #         (qtpylib.crossed_below(dataframe['cci_fs'], self.cci_fast_signal_lower1_band)) &
        #         (dataframe['volume'] > 0)
        #     )
        # )

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions),
                'exit_long'
            ] = 1

        return dataframe

    def populate_divergence(self, dataframe: DataFrame, osc: str) -> DataFrame:
        dataframe['osc'] = dataframe[osc]
        # plFound = na(pivotlow(osc, lbL, lbR)) ? false : true
        dataframe['min'] = dataframe['osc'].rolling(self.lbL).min()
        dataframe['prevMin'] = np.where(dataframe['min'] > dataframe['min'].shift(
        ), dataframe['min'].shift(), dataframe['min'])
        dataframe.loc[
            (
                (dataframe['osc'].shift(1) == dataframe['prevMin'].shift(1)) &
                (dataframe['osc'] != dataframe['prevMin'])
            ), 'plFound'] = 1

        # phFound = na(pivothigh(osc, lbL, lbR)) ? false : true
        dataframe['max'] = dataframe['osc'].rolling(self.lbL).max()
        dataframe['prevMax'] = np.where(dataframe['max'] < dataframe['max'].shift(
        ), dataframe['max'].shift(), dataframe['max'])
        dataframe.loc[
            (
                (dataframe['osc'].shift(1) == dataframe['prevMax'].shift(1)) &
                (dataframe['osc'] != dataframe['prevMax'])
            ), 'phFound'] = 1

        # ------------------------------------------------------------------------------
        # Regular Bullish
        # Osc: Higher Low
        # oscHL = osc[lbR] > valuewhen(plFound, osc[lbR], 1) and _inRange(plFound[1])
        dataframe['valuewhen_plFound_osc'], dataframe['inrange_plFound_osc'] = valuewhen(
            dataframe, 'plFound', 'osc', 1)
        dataframe.loc[
            (
                (dataframe['osc'] > dataframe['valuewhen_plFound_osc']) &
                (dataframe['inrange_plFound_osc'] == 1)
            ), 'oscHL'] = 1

        # Price: Lower Low
        # priceLL = low[lbR] < valuewhen(plFound, low[lbR], 1)
        dataframe['valuewhen_plFound_low'], dataframe['inrange_plFound_low'] = valuewhen(
            dataframe, 'plFound', 'low', 1)
        dataframe.loc[
            (dataframe['low'] < dataframe['valuewhen_plFound_low']), 'priceLL'] = 1
        #bullCond = plotBull and priceLL and oscHL and plFound
        dataframe.loc[
            (
                (dataframe['priceLL'] == 1) &
                (dataframe['oscHL'] == 1) &
                (dataframe['plFound'] == 1)
            ), 'bullCond'] = 1

        # plot(
        #      plFound ? osc[lbR] : na,
        #      offset=-lbR,
        #      title="Regular Bullish",
        #      linewidth=2,
        #      color=(bullCond ? bullColor : noneColor)
        #      )
        #
        # plotshape(
        #      bullCond ? osc[lbR] : na,
        #      offset=-lbR,
        #      title="Regular Bullish Label",
        #      text=" Bull ",
        #      style=shape.labelup,
        #      location=location.absolute,
        #      color=bullColor,
        #      textcolor=textColor
        #      )

        # //------------------------------------------------------------------------------
        # // Hidden Bullish
        # // Osc: Lower Low
        #
        # oscLL = osc[lbR] < valuewhen(plFound, osc[lbR], 1) and _inRange(plFound[1])
        dataframe['valuewhen_plFound_osc'], dataframe['inrange_plFound_osc'] = valuewhen(
            dataframe, 'plFound', 'osc', 1)
        dataframe.loc[
            (
                (dataframe['osc'] < dataframe['valuewhen_plFound_osc']) &
                (dataframe['inrange_plFound_osc'] == 1)
            ), 'oscLL'] = 1
        #
        # // Price: Higher Low
        #
        # priceHL = low[lbR] > valuewhen(plFound, low[lbR], 1)
        dataframe['valuewhen_plFound_low'], dataframe['inrange_plFound_low'] = valuewhen(
            dataframe, 'plFound', 'low', 1)
        dataframe.loc[
            (dataframe['low'] > dataframe['valuewhen_plFound_low']), 'priceHL'] = 1
        # hiddenBullCond = plotHiddenBull and priceHL and oscLL and plFound
        dataframe.loc[
            (
                (dataframe['priceHL'] == 1) &
                (dataframe['oscLL'] == 1) &
                (dataframe['plFound'] == 1)
            ), 'hiddenBullCond'] = 1
        #
        # plot(
        #      plFound ? osc[lbR] : na,
        #      offset=-lbR,
        #      title="Hidden Bullish",
        #      linewidth=2,
        #      color=(hiddenBullCond ? hiddenBullColor : noneColor)
        #      )
        #
        # plotshape(
        #      hiddenBullCond ? osc[lbR] : na,
        #      offset=-lbR,
        #      title="Hidden Bullish Label",
        #      text=" H Bull ",
        #      style=shape.labelup,
        #      location=location.absolute,
        #      color=bullColor,
        #      textcolor=textColor
        #      )
        #
        # //------------------------------------------------------------------------------
        # // Regular Bearish
        # // Osc: Lower High
        #
        # oscLH = osc[lbR] < valuewhen(phFound, osc[lbR], 1) and _inRange(phFound[1])
        dataframe['valuewhen_phFound_osc'], dataframe['inrange_phFound_osc'] = valuewhen(
            dataframe, 'phFound', 'osc', 1)
        dataframe.loc[
            (
                (dataframe['osc'] < dataframe['valuewhen_phFound_osc']) &
                (dataframe['inrange_phFound_osc'] == 1)
            ), 'oscLH'] = 1
        #
        # // Price: Higher High
        #
        # priceHH = high[lbR] > valuewhen(phFound, high[lbR], 1)
        dataframe['valuewhen_phFound_high'], dataframe['inrange_phFound_high'] = valuewhen(
            dataframe, 'phFound', 'high', 1)
        dataframe.loc[
            (dataframe['high'] > dataframe['valuewhen_phFound_high']), 'priceHH'] = 1
        #
        # bearCond = plotBear and priceHH and oscLH and phFound
        dataframe.loc[
            (
                (dataframe['priceHH'] == 1) &
                (dataframe['oscLH'] == 1) &
                (dataframe['phFound'] == 1)
            ), 'bearCond'] = 1
        #
        # plot(
        #      phFound ? osc[lbR] : na,
        #      offset=-lbR,
        #      title="Regular Bearish",
        #      linewidth=2,
        #      color=(bearCond ? bearColor : noneColor)
        #      )
        #
        # plotshape(
        #      bearCond ? osc[lbR] : na,
        #      offset=-lbR,
        #      title="Regular Bearish Label",
        #      text=" Bear ",
        #      style=shape.labeldown,
        #      location=location.absolute,
        #      color=bearColor,
        #      textcolor=textColor
        #      )
        #
        # //------------------------------------------------------------------------------
        # // Hidden Bearish
        # // Osc: Higher High
        #
        # oscHH = osc[lbR] > valuewhen(phFound, osc[lbR], 1) and _inRange(phFound[1])
        dataframe['valuewhen_phFound_osc'], dataframe['inrange_phFound_osc'] = valuewhen(
            dataframe, 'phFound', 'osc', 1)
        dataframe.loc[
            (
                (dataframe['osc'] > dataframe['valuewhen_phFound_osc']) &
                (dataframe['inrange_phFound_osc'] == 1)
            ), 'oscHH'] = 1
        #
        # // Price: Lower High
        #
        # priceLH = high[lbR] < valuewhen(phFound, high[lbR], 1)
        dataframe['valuewhen_phFound_high'], dataframe['inrange_phFound_high'] = valuewhen(
            dataframe, 'phFound', 'high', 1)
        dataframe.loc[
            (dataframe['high'] < dataframe['valuewhen_phFound_high']), 'priceLH'] = 1
        #
        # hiddenBearCond = plotHiddenBear and priceLH and oscHH and phFound
        dataframe.loc[
            (
                (dataframe['priceLH'] == 1) &
                (dataframe['oscHH'] == 1) &
                (dataframe['phFound'] == 1)
            ), 'hiddenBearCond'] = 1
        return dataframe
