# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib


# --------------------------------
import pandas as pd  # noqa
pd.options.mode.chained_assignment = None  # default='warn'

from technical.util import resample_to_interval, resampled_merge
from freqtrade.exchange import timeframe_to_minutes

from functools import reduce
from datetime import datetime, timedelta

# ObeliskRSI v6.1 - 2021-03-06
#
# by Obelisk 
# https://github.com/brookmiles/
#
# Run at your own risk.
# I don't know what I'm doing.
# Backtests quite a bit better than it actually works.
# Let me know if you manage to improve it!
#
# Buys when RSI crosses the low threshold
# Doesn't wait for reversal, trying to anticipate the "bottom" of the dip.
#
# Sells on ROI (often), Stoploss (sometimes), or RSI high (rarely)
#
# Custom Stoploss starts wide and then narrows over time
# Combined with ROI, every trade will be forced to close in custom_stop_ramp_minutes, win or lose
#
# Bull/Bear trends
#
# Uses two different sets of buy/sell thresholds, which are used depending on the longer RSI trend
# Enter earlier and leave later when bullish; enter later and leave earlier when bearish
#
# Strengths
#
# Choppy markets - requires strong enough dips to trigger a buy, then we want a bounce back up
#
# Weaknesses
#
# Steady bull runs - no dips to buy, missed opportunity
# Sudden ongoing bear runs - if the market keeps dipping and dropping repeatedly that's bad,
# so use protections, eg.
#
# "protections": [
#     {
#         "method": "StoplossGuard",
#         "lookback_period": 720,
#         "trade_limit": 2,
#         "stop_duration": 720,
#         "only_per_pair": true
#     },
# ],  
# 
# Works best on high volume pairs with decent volatility eg.
#
# "SXP/USD",
# "MATIC/USD",
# "SUSHI/USD",
# "CHZ/USD",
#
# and not ETH/USD or BTC/USD


def easeInCubic(t):
    return t * t * t

def clamp(num, min_value, max_value):
    return max(min(num, max_value), min_value)

def clamp01(num):
    return clamp(num, 0, 1)


class ObeliskRSI_v6_1(IStrategy):

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

    startup_candle_count = 240
    process_only_new_candles = True

    # ROI table:
    minimal_roi = {
        "0": 0.15,
        "35": 0.04,
        "65": 0.01,
        "115": 0
    }

    # Buy hyperspace params:
    buy_params = {
     'bear-buy-rsi-value': 21, 
     'bull-buy-rsi-value': 35
    }

    # Sell hyperspace params:
    sell_params = {
     'bear-sell-rsi-value': 55, 
     'bull-sell-rsi-value': 69 
    }

    # Stoploss:
    stoploss = -0.30

    use_custom_stoploss = True
    custom_stop_ramp_minutes = 110

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

        # longer RSI used for determining trend
        resample_rsi_interval = timeframe_to_minutes(self.timeframe) * 12
        resample_rsi_key = 'resample_{}_rsi'.format(resample_rsi_interval)

        dataframe_long = resample_to_interval(dataframe, resample_rsi_interval)
        dataframe_long['rsi'] = ta.RSI(dataframe_long, timeperiod=14)
        dataframe = resampled_merge(dataframe, dataframe_long)
        dataframe[resample_rsi_key].fillna(method='ffill', inplace=True)

        # bull used to select between two different sets of buy/sell threshold values
        # based on long RSI
        dataframe['bull'] = dataframe[resample_rsi_key].gt(60).astype('int')

        # normal rsi acts mainly as the buy trigger
        # used for sell as well, but more likely to ROI or stop out
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)

        return dataframe

    # buy low
    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        params = self.buy_params

        conditions = []
        conditions.append(dataframe['volume'] > 0)

        conditions.append(
            ((dataframe['bull'] > 0) & qtpylib.crossed_below(dataframe['rsi'], params['bull-buy-rsi-value'])) |
            (~(dataframe['bull'] > 0) & qtpylib.crossed_below(dataframe['rsi'], params['bear-buy-rsi-value']))
            )

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

        return dataframe

    # sell high
    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        params = self.sell_params

        conditions = []
        conditions.append(dataframe['volume'] > 0)

        conditions.append(
            ((dataframe['bull'] > 0) & (dataframe['rsi'] > params['bull-sell-rsi-value'])) |
            (~(dataframe['bull'] > 0) & (dataframe['rsi'] > params['bear-sell-rsi-value']))
            )

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

        return dataframe

    # Custom stoploss starts at the basic stoploss, and ramps towards zero in a curve, 
    # narrowing the trailing stoploss until forcing the trade to stop out after custom_stop_ramp_minutes

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

        since_open = int((current_time.timestamp() - trade.open_date_utc.timestamp()) // 60)

        sl_pct = 1 - easeInCubic( clamp01( since_open / self.custom_stop_ramp_minutes ) )
        sl_ramp = abs(self.stoploss) * sl_pct

        return sl_ramp + 0.001 # we can't go all the way to zero
