# source: https://raw.githubusercontent.com/linsua01/freqtrade/5b8b1aca7bbeed4fe34dc6cf9b7c7566b2123d31/Obelisk/Obelisk_TradePro_Ichi_v2_1.py
# --- Do not remove these libs ---
from functools import reduce

from freqtrade.strategy import BooleanParameter, CategoricalParameter, DecimalParameter, IntParameter, IStrategy, \
    stoploss_from_open
from pandas import DataFrame
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal, Real
from typing import Optional, Any, Callable, Dict, List
# --------------------------------
import pandas as pd
import numpy as np
import technical.indicators as ftt

pd.options.mode.chained_assignment = None  # default='warn'


# Obelisk_TradePro_Ichi v2.1 - 2021-04-02
#
# by Obelisk
# https://twitter.com/brookmiles
#
# Originally based on "Crazy Results Best Ichimoku Cloud Trading Strategy Proven 100 Trades" by Trade Pro
# https://www.youtube.com/watch?v=8gWIykJgMNY
#
# Contributions:
#
# JimmyNixx
#  - SSL Channel confirmation
#  - ROCR & RMI confirmations
#
#
# Backtested with pairlist generated from:
# "pairlists": [
#     {
#         "method": "VolumePairList",
#         "number_assets": 50,
#         "sort_key": "quoteVolume",
#         "refresh_period": 1800
#     },
#     {"method": "AgeFilter", "min_days_listed": 10},
#     {"method": "PrecisionFilter"},
#     {"method": "PriceFilter",
#         "low_price_ratio": 0.001,
#         "max_price": 20,
#     },
#     {"method": "SpreadFilter", "max_spread_ratio": 0.002},
#     {
#         "method": "RangeStabilityFilter",
#         "lookback_days": 3,
#         "min_rate_of_change": 0.1,
#         "refresh_period": 1440
#     },
# ],


class github_linsua01_freqtrade__Obelisk_TradePro_Ichi_v2_1__20230417_133932(IStrategy):
    # Optimal timeframe for the strategy
    timeframe = '1h'
    can_short = False
    class HyperOpt:

        # def roi_space():
        #     return [
        #         Integer(0, 1, name='roi_t'),
        #         SKDecimal(0.010, 0.050, decimals=3, name='roi_p'),
        #     ]
        #
        # def generate_roi_table(params: Dict) -> Dict[int, float]:
        #     roi_table = {}
        #     roi_table[0] = params['roi_p']
        #     return roi_table

        def trailing_space() -> List[Dimension]:
            # All parameters here are mandatory, you can only modify their type or the range.
            return [
                # Fixed to true, if optimizing trailing_stop we assume to use trailing stop at all times.
                Categorical([True, False], name='trailing_stop'),

                SKDecimal(0.001, 0.01, decimals=3, name='trailing_stop_positive'),
                # 'trailing_stop_positive_offset' should be greater than 'trailing_stop_positive',
                # so this intermediate parameter is used as the value of the difference between
                # them. The value of the 'trailing_stop_positive_offset' is constructed in the
                # generate_trailing_params() method.
                # This is similar to the hyperspace dimensions used for constructing the ROI tables.
                SKDecimal(0.001, 0.05, decimals=3, name='trailing_stop_positive_offset_p1'),

                Categorical([True, False], name='trailing_only_offset_is_reached'),
            ]

        def stoploss_space():

            return [SKDecimal(-0.1, -0.05, decimals=3, name='stoploss')]

    # WARNING: ichimoku is a long indicator, if you remove or use a
    # shorter startup_candle_count your results will be unstable/invalid
    # for up to a week from the start of your backtest or dry/live run
    # (180 candles = 7.5 days)
    startup_candle_count = 180

    # NOTE: this strat only uses candle information, so processing between
    # new candles is a waste of resources as nothing will change
    process_only_new_candles = True

    minimal_roi = {
        "0": 0.231,
        "222": 0.165,
        "805": 0.075,
        "2074": 0
    }

    # Stoploss:
    stoploss = -0.075

    # Trailing stop:
    trailing_stop = True
    trailing_stop_positive = 0.001
    trailing_stop_positive_offset = 0.015
    trailing_only_offset_is_reached = True

    # plot_config = {
    #     # Main plot indicators (Moving averages, ...)
    #     'main_plot': {
    #         'senkou_a': {
    #             'color': 'green',
    #             'fill_to': 'senkou_b',
    #             'fill_label': 'Ichimoku Cloud',
    #             'fill_color': 'rgba(0,0,0,0.2)',
    #         },
    #         # plot senkou_b, too. Not only the area to it.
    #         'senkou_b': {
    #             'color': 'red',
    #         },
    #         'tenkan_sen': {'color': 'orange'},
    #         'kijun_sen': {'color': 'blue'},
    #
    #         'chikou_span': {'color': 'lightgreen'},
    #
    #         # 'ssl_up': { 'color': 'green' },
    #         # 'ssl_down': { 'color': 'red' },
    #     },
    #     'subplots': {
    #         "Signals": {
    #             'go_long': {'color': 'blue'},
    #             'future_green': {'color': 'green'},
    #             'chikou_high': {'color': 'lightgreen'},
    #             'ssl_high': {'color': 'orange'},
    #         },
    #     }
    # }
    # buy_conversion_line_period = IntParameter(15, 25, default=20, space="buy")
    # buy_base_line_periods = IntParameter(45, 75, default=60, space="buy")
    # buy_laggin_span = IntParameter(100, 140, default=120, space="buy")
    # buy_displacement = IntParameter(20, 40, default=30, space="buy")


    buy_1 = IntParameter(15, 25, default=16, space="buy")
    buy_2 = IntParameter(50, 70, default=67, space="buy")
    buy_3 = IntParameter(90, 150, default=142, space="buy")
    buy_4 = IntParameter(20, 40, default=30, space="buy", optimize=False)
    buy_rocr = IntParameter(15, 35, default=19, space="buy")
    buy_rmi_fast = IntParameter(5, 20, default=5, space="buy")
    buy_ssl = IntParameter(5, 15, default=6, space="buy")

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        for va1 in self.buy_1.range:
            for va2 in self.buy_2.range:
                for va3 in self.buy_3.range:
                    ichimoku = ftt.ichimoku(dataframe, conversion_line_period=va1, base_line_periods=va2,
                                                                laggin_span=va3,
                                                                displacement=30)

                    dataframe[f'chikou_span{va1}{va2}{va3}{30}'] = ichimoku['chikou_span']

                    # cross indicators
                    dataframe[f'tenkan_sen{va1}{va2}{va3}{30}'] = ichimoku['tenkan_sen']
                    dataframe[f'kijun_sen{va1}{va2}{va3}{30}'] = ichimoku['kijun_sen']

                        # cloud, green a > b, red a < b
                    dataframe[f'senkou_a{va1}{va2}{va3}{30}'] = ichimoku['senkou_span_a']
                    dataframe[f'senkou_b{va1}{va2}{va3}{30}'] = ichimoku['senkou_span_b']
                    dataframe[f'leading_senkou_span_a{va1}{va2}{va3}{30}'] = ichimoku['leading_senkou_span_a']
                    dataframe[f'leading_senkou_span_b{va1}{va2}{va3}{30}'] = ichimoku['leading_senkou_span_b']
                    dataframe[f'cloud_green{va1}{va2}{va3}{30}'] = ichimoku['cloud_green'] * 1
                    dataframe[f'cloud_red{va1}{va2}{va3}{30}'] = ichimoku['cloud_red'] * -1
                    dataframe[f'future_green{va1}{va2}{va3}{30}'] = (dataframe[
                                                         f'leading_senkou_span_a{va1}{va2}{va3}{30}'] >
                                                     dataframe[
                                                         f'leading_senkou_span_b{va1}{va2}{va3}{30}']).astype(
                            'int') * 2
                    dataframe[f'chikou_high{va1}{va2}{va3}{30}'] = (
                                (dataframe[
                                     f'chikou_span{va1}{va2}{va3}{30}'] >
                                 dataframe[
                                     f'senkou_a{va1}{va2}{va3}{30}']) &
                                (dataframe[
                                     f'chikou_span{va1}{va2}{va3}{30}'] >
                                 dataframe[
                                     f'senkou_b{va1}{va2}{va3}{30}'])
                        ).shift(30).fillna(0).astype('int')
        # DANGER ZONE START

        # NOTE: Not actually the future, present data that is normally shifted forward for display as the cloud


        # The chikou_span is shifted into the past, so we need to be careful not to read the
        # current value.  But if we shift it forward again by displacement it should be safe to use.
        # We're effectively "looking back" at where it normally appears on the chart.


        # DANGER ZONE END
        for val in self.buy_ssl.range:
            dataframe[f'ssl_down{val}'] = self.SSLChannels(dataframe, val)['SslDown']
            dataframe[f'ssl_up{val}'] = self.SSLChannels(dataframe, val)['SslUp']
            dataframe[f'ssl_high{val}'] = (dataframe[f'ssl_up{val}'] > dataframe[f'ssl_down{val}']).astype('int') * 3
        for val in self.buy_rocr.range:
            dataframe[f'rocr{val}'] = ta.ROCR(dataframe, timeperiod=val)
        for val in self.buy_rmi_fast.range:
            dataframe[f'rmi-fast{val}'] = ftt.RMI(dataframe, length=val, mom=3)


        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = [(dataframe['volume'] > 0)]
        dataframe['go_long'] = (
                                       (dataframe[f'tenkan_sen{self.buy_1.value}{self.buy_2.value}{self.buy_3.value}{30}'] >
                                        dataframe[f'kijun_sen{self.buy_1.value}{self.buy_2.value}{self.buy_3.value}{30}']) &
                                       (dataframe['close'] > dataframe[f'senkou_a{self.buy_1.value}{self.buy_2.value}{self.buy_3.value}{30}']) &
                                       (dataframe['close'] > dataframe[f'senkou_b{self.buy_1.value}{self.buy_2.value}{self.buy_3.value}{30}']) &
                                       (dataframe[f'future_green{self.buy_1.value}{self.buy_2.value}{self.buy_3.value}{30}'] > 0) &
                                       (dataframe[f'chikou_high{self.buy_1.value}{self.buy_2.value}{self.buy_3.value}{30}'] > 0) &
                                       (dataframe[f'ssl_high{self.buy_ssl.value}'] > 0) &
                                       (dataframe[f'rocr{self.buy_rocr.value}'] > dataframe[f'rocr{self.buy_rocr.value}'].shift()) &
                                       (dataframe[f'rmi-fast{self.buy_rmi_fast.value}'] > dataframe[f'rmi-fast{self.buy_rmi_fast.value}'].shift(2))
                               ).astype('int') * 4

        conditions.append(qtpylib.crossed_above(dataframe['go_long'], 0))
        conditions = reduce(lambda x, y: x & y, conditions)
        dataframe.loc[conditions, 'enter_long'] = 1
        # #print(dataframe.tail(50))

        return dataframe

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

        dataframe.loc[
            (dataframe[f'ssl_high{self.buy_ssl.value}'] == 0)
            &
            (
                    (dataframe[f'tenkan_sen{self.buy_1.value}{self.buy_2.value}{self.buy_3.value}{30}'] < dataframe[f'kijun_sen{self.buy_1.value}{self.buy_2.value}{self.buy_3.value}{30}'])
                    |
                    (dataframe['close'] < dataframe[f'kijun_sen{self.buy_1.value}{self.buy_2.value}{self.buy_3.value}{30}'])
            )
            ,
            'exit_long'] = 1

        return dataframe

    def SSLChannels(self, dataframe: DataFrame, length=7):
        df = dataframe.copy()
        df['ATR'] = ta.ATR(df, timeperiod=14)
        df['smaHigh'] = df['high'].rolling(length).mean() + df['ATR']
        df['smaLow'] = df['low'].rolling(length).mean() - df['ATR']
        df['hlv'] = np.where(df['close'] > df['smaHigh'], 1, np.where(df['close'] < df['smaLow'], -1, np.NAN))
        df['hlv'] = df['hlv'].ffill()
        df['sslDown'] = np.where(df['hlv'] < 0, df['smaHigh'], df['smaLow'])
        df['sslUp'] = np.where(df['hlv'] < 0, df['smaLow'], df['smaHigh'])
        return DataFrame(index=df.index, data={
            'SslDown': df['sslDown'],
            'SslUp': df['sslUp']
        })
