# source: https://raw.githubusercontent.com/droidoz/freqtrade-strategies/b8be25c6a4d1ea3367fcde5cac5d6fb5a7e2d6f7/SMACross_V1.py

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

# --- For conditions
from functools import reduce
# --- For hyperopt
from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter, BooleanParameter
from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal
# --- Other
from typing import Dict, List, Union, Optional

class github_droidoz_freqtrade_strategies__SMACross_V1__20230916_203337(IStrategy):

    """
    author@: @Arthie
    github@: https://github.com/droidoz/freqtrade/
    Other strategies for Freqtrade https://github.com/freqtrade/freqtrade
    
    This is a simple SMA crossover strategy framework to experiment with;
    Feel free to try other SMA timeperiod combo in Hyperoptable parameters.
    
    Note: always dry testing the strategy before going live ! Use at your own risk.
    """

    def version(self) -> str:
        return "v3"

    INTERFACE_VERSION = 3

    plot_config = {
        'main_plot': {
            "bb_lowerband": {
            "color": "#e66100",
            "type": "line",
            "fill_to": "bb_upperband"
            },
            "bb_upperband": {
            "color": "#F0E68C",
            "type": "line" 
            },
            'buy_MA1': {'color': '#8A2BE2', 'fill_to': 'buy_MA2'},
            'buy_MA2': {'color': '#5F9EA0', 'fill_to': 'buy_MA3'},
            },

        'subplots': {
            "MACD": {
            'macd': {'color': '#00FF00', 'fill_to': 'macdhist'},
            'macdhist': {'type': 'bar', 'color': '#FF00FF'},
            'macdsignal': {'color': '#00FFFF'},
            },

            'ADX': {
            'adx': {'color': '#FFF5EE'},    
            'minus_di': {'color': '#E9967A', 'fill_to': 'plus_di'},
            'plus_di' :{'color': '#FF4500'},
            },

            "RSI": {
                'rsi': {'type': 'bar', 'color': '#C71585'}
            },
        }
    }

    # Optional    
    @property
    def protections(self):
        return [
            {
                "method": "CooldownPeriod",
                "stop_duration_candles": 5
            },
            {
                "method": "MaxDrawdown",
                "lookback_period_candles": 48,
                "trade_limit": 20,
                "stop_duration_candles": 4,
                "max_allowed_drawdown": 0.2
            },
            {
                "method": "StoplossGuard",
                "lookback_period_candles": 24,
                "trade_limit": 4,
                "stop_duration_candles": 2,
                "only_per_pair": False
            },
            {
                "method": "LowProfitPairs",
                "lookback_period_candles": 6,
                "trade_limit": 2,
                "stop_duration_candles": 2,
                "required_profit": 0.02
            },
            {
                "method": "LowProfitPairs",
                "lookback_period_candles": 24,
                "trade_limit": 4,
                "stop_duration_candles": 2,
                "required_profit": 0.01
            }
        ]
        
    class HyperOpt:
        
        def generate_estimator(dimensions: List['Dimension'], **kwargs):
            from skopt.learning import ExtraTreesRegressor
            # Corresponds to "ET" - but allows additional parameters.
            return ExtraTreesRegressor(n_estimators=500) # Default = 100

        # Define a custom stoploss space.
        def stoploss_space():
            return [SKDecimal(-0.30, -0.10, decimals=2, name='stoploss')]

        # Define custom ROI space
        def roi_space() -> List[Dimension]:
            return [
                Integer(10, 120, name='roi_t1'),
                Integer(10, 60, name='roi_t2'),
                Integer(10, 40, name='roi_t3'),
                SKDecimal(0.01, 0.04, decimals=2, name='roi_p1'),
                SKDecimal(0.01, 0.07, decimals=2, name='roi_p2'),
                SKDecimal(0.01, 0.10, decimals=2, name='roi_p3'),
            ]

        def generate_roi_table(params: Dict) -> Dict[int, float]:

            roi_table = {}
            roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
            roi_table[params['roi_t3']] = params['roi_p1'] + params['roi_p2']
            roi_table[params['roi_t3'] + params['roi_t2']] = params['roi_p1']
            roi_table[params['roi_t3'] + params['roi_t2'] + params['roi_t1']] = 0

            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], name='trailing_stop'),
                SKDecimal(0.01, 0.35, decimals=3, name='trailing_stop_positive'),
                SKDecimal(0.001, 0.1, decimals=3, name='trailing_stop_positive_offset_p1'),
                Categorical([True, False], name='trailing_only_offset_is_reached'),
            ]

    # Define protections parameters spaces
    cooldown_lookback = IntParameter(2, 48, default=5, space="protection", optimize=True)
    stop_duration = IntParameter(12, 200, default=5, space="protection", optimize=True)
    use_stop_protection = BooleanParameter(default=True, space="protection", optimize=True)

    # Optional
    # Protection hyperspace params:
    protection_params = {
        "cooldown_lookback": 5,
        "stop_duration": 12,
        "use_stop_protection": True,
    }
    
    # Hyperoptable parameters
    buy_adx = DecimalParameter(25, 50, decimals=1, default=30.1, space="buy", optimize=True)
    buy_rsi = IntParameter(20, 40, default=30, space="buy", optimize=True)
    buy_adx_enabled = BooleanParameter(default=True, space="buy", optimize=True)
    buy_rsi_enabled = CategoricalParameter([True, False], default=False, space="buy", optimize=True)
    buy_trigger = CategoricalParameter(["bb_lower", "macd_cross_signal"], default="bb_lower", space="buy", optimize=True)

    buy_MA1 = IntParameter(low=3, high=7, default=5, space='buy', optimize=True)
    buy_MA2 = IntParameter(low=8, high=25, default=8, space='buy', optimize=True)

    sell_trigger = CategoricalParameter(["bb_upper", "macd_cross_signal"], default="bb_upper", space="sell", optimize=True)

    # Buy hyperspace params:
    buy_params = {
        "buy_MA1": 7,
        "buy_MA2": 13,
        "buy_adx": 40.0,
        "buy_adx_enabled": False,
        "buy_rsi": 22,
        "buy_rsi_enabled": False,
        "buy_trigger": "bb_lower",
    }

    # Sell hyperspace params:
    sell_params = {
        "sell_trigger": "bb_upper",
    }

    # ROI table:
    minimal_roi = {
        "0": 0.04,
        "20": 0.03,
        "30": 0.01,
        "120": 0
    }

    # Stoploss:
    stoploss = -0.13

    use_custom_stoploss = False

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

    # Trailing stop:
    trailing_stop = False
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.02
    trailing_only_offset_is_reached = False

    # run "populate_indicators" only for new candle
    process_only_new_candles = True
    
    startup_candle_count = 600

    # Experimental settings (configuration will overide these if set)
    use_exit_signal = True
    exit_profit_only = True
    ignore_roi_if_entry_signal = False

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        
        # ADX
        dataframe['adx'] = ta.ADX(dataframe)

        # BBANDS
        bollinger = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
        dataframe['bb_lowerband'] = bollinger['lowerband']
        dataframe['bb_middleband'] = bollinger['middleband']
        dataframe['bb_upperband'] = bollinger['upperband']

        # SMA
        dataframe['buy_MA1'] = ta.SMA(dataframe, timeperiod=self.buy_MA1.value)
        dataframe['buy_MA2'] = ta.SMA(dataframe, timeperiod=self.buy_MA2.value)

        # MACD
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']
        dataframe['macdhist'] = macd['macdhist']

        # Minus & Plus Directional Indicator / Movement
        dataframe['minus_di'] = ta.MINUS_DI(dataframe)
        dataframe['plus_di'] = ta.PLUS_DI(dataframe)
        
        # RSI
        dataframe['rsi'] = ta.RSI(dataframe)

        return dataframe

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

        conditions = []
        
        # Trend up
        conditions.append(dataframe['plus_di'] > dataframe['minus_di'])
        conditions.append(dataframe['buy_MA1'] > dataframe['buy_MA2']) 
        
        # GUARDS AND TRENDS
        if self.buy_adx_enabled.value:
            conditions.append(dataframe['adx'] > self.buy_adx.value)
        if self.buy_rsi_enabled.value:
            conditions.append(dataframe['rsi'] < self.buy_rsi.value)

        # TRIGGERS
        if self.buy_trigger.value == 'bb_lower':
            conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
        if self.buy_trigger.value == 'macd_cross_signal':
            conditions.append(qtpylib.crossed_above(
                dataframe['macd'], dataframe['macdsignal']
            ))
       
        # Volume is above 0
        conditions.append(dataframe['volume'] > 0)

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

        return dataframe

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

        conditions = []
        
        # TRIGGERS
        if self.sell_trigger.value == 'bb_upper':
            conditions.append(dataframe['close'] > dataframe['bb_upperband'])
        if self.sell_trigger.value == 'macd_cross_signal':
            conditions.append(qtpylib.crossed_below(
                dataframe['macd'], dataframe['macdsignal']
            ))
            

        # Volume above 0
        conditions.append(dataframe['volume'] > 0)

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

        return dataframe
