# source: https://raw.githubusercontent.com/Bldnr94/FreqTrade/ac41453a47a5efd9aa04107c103b430ceddc2af7/ft_userdata/user_data/strategies-internet/freqtrade-strategies/strategies/Bandtastic-v2.py
import talib.abstract as ta
import numpy as np  # noqa
import pandas as pd
from functools import reduce
from pandas import DataFrame
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.strategy import IStrategy, CategoricalParameter, DecimalParameter, IntParameter, RealParameter

__author__ = "Robert Roman"
__copyright__ = "Free For Use"
__license__ = "MIT"
__version__ = "1.0"
__maintainer__ = "Robert Roman"
__email__ = "robertroman7@gmail.com"
__BTC_donation__ = "3FgFaG15yntZYSUzfEpxr5mDt1RArvcQrK"


# Optimized With Sharpe Ratio and 1 year data
# 199/40000:  30918 trades. 18982/3408/8528 Wins/Draws/Losses. Avg profit   0.39%. Median profit   0.65%. Total profit  119934.26007495 USDT ( 119.93%). Avg duration 8:12:00 min. Objective: -127.60220

class Github_Bldnr94_FreqTrade__Bandtastic_v2__20240622_175427(IStrategy):
    INTERFACE_VERSION = 3
    can_short = True

    timeframe = '15m'

    # ROI table:
    minimal_roi = {
        "0": 0.162,
        "69": 0.097,
        "229": 0.061,
        "566": 0
    }

    # Stoploss:
    stoploss = -0.345

    # Trailing stop:
    trailing_stop = True
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.058
    trailing_only_offset_is_reached = False

    # Hyperopt Entry Parameters
    entry_fastema = IntParameter(low=1, high=236, default=211, space='entry', optimize=True, load=True)
    entry_slowema = IntParameter(low=1, high=126, default=364, space='entry', optimize=True, load=True)
    entry_rsi = IntParameter(low=15, high=70, default=52, space='entry', optimize=True, load=True)
    entry_mfi = IntParameter(low=15, high=70, default=30, space='entry', optimize=True, load=True)

    entry_rsi_enabled = CategoricalParameter([True, False], space='entry', optimize=True, default=False)
    entry_mfi_enabled = CategoricalParameter([True, False], space='entry', optimize=True, default=False)
    entry_ema_enabled = CategoricalParameter([True, False], space='entry', optimize=True, default=False)
    entry_trigger = CategoricalParameter(["bb_lower1", "bb_lower2", "bb_lower3", "bb_lower4"], default="bb_lower1", space="entry")

    # Hyperopt Exit Parameters
    exit_fastema = IntParameter(low=1, high=365, default=7, space='exit', optimize=True, load=True)
    exit_slowema = IntParameter(low=1, high=365, default=6, space='exit', optimize=True, load=True)
    exit_rsi = IntParameter(low=30, high=100, default=57, space='exit', optimize=True, load=True)
    exit_mfi = IntParameter(low=30, high=100, default=46, space='exit', optimize=True, load=True)

    exit_rsi_enabled = CategoricalParameter([True, False], space='exit', optimize=True, default=False)
    exit_mfi_enabled = CategoricalParameter([True, False], space='exit', optimize=True, default=True)
    exit_ema_enabled = CategoricalParameter([True, False], space='exit', optimize=True, default=False)
    exit_trigger = CategoricalParameter(["exit-bb_upper1", "exit-bb_upper2", "exit-bb_upper3", "exit-bb_upper4"], default="exit-bb_upper2", space="exit")

    # Update strategy level settings
    use_exit_signal = True
    exit_profit_only = True
    exit_profit_offset: 0.01
    ignore_roi_if_entry_signal = False

    # Update unfilled timeout settings
    unfilledtimeout = {
        "entry": 10,
        "exit": 10,
        "exit_timeout_count": 0,
        "unit": "minutes"
    }

    # Update order pricing settings
    order_types = {
        'entry': 'limit',
        'exit': 'limit',
        'stoploss': 'market',
        'stoploss_on_exchange': False,
        'stoploss_on_exchange_interval': 60
    }

    entry_pricing = {
        "price_side": "same",
        "use_order_book": True,
        "order_book_top": 1,
        "price_last_balance": 0.0,
        "check_depth_of_market": {
            "enabled": False,
            "bids_to_ask_delta": 1
        }
    }

    exit_pricing = {
        "price_side": "same",
        "use_order_book": True,
        "order_book_top": 1,
        "price_last_balance": 0.0
    }

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # RSI
        dataframe['rsi'] = ta.RSI(dataframe)
        dataframe['mfi'] = ta.MFI(dataframe)

        # Bollinger Bands 1,2,3 and 4
        bollinger1 = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=1)
        dataframe['bb_lowerband1'] = bollinger1['lower']
        dataframe['bb_middleband1'] = bollinger1['mid']
        dataframe['bb_upperband1'] = bollinger1['upper']

        bollinger2 = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
        dataframe['bb_lowerband2'] = bollinger2['lower']
        dataframe['bb_middleband2'] = bollinger2['mid']
        dataframe['bb_upperband2'] = bollinger2['upper']

        bollinger3 = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=3)
        dataframe['bb_lowerband3'] = bollinger3['lower']
        dataframe['bb_middleband3'] = bollinger3['mid']
        dataframe['bb_upperband3'] = bollinger3['upper']

        bollinger4 = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=4)
        dataframe['bb_lowerband4'] = bollinger4['lower']
        dataframe['bb_middleband4'] = bollinger4['mid']
        dataframe['bb_upperband4'] = bollinger4['upper']

        # Build EMA rows - combine all ranges to a single set to avoid duplicate calculations.
        for period in set(
                list(self.entry_fastema.range)
                + list(self.entry_slowema.range)
                + list(self.exit_fastema.range)
                + list(self.exit_slowema.range)
        ):
            dataframe[f'EMA_{period}'] = ta.EMA(dataframe, timeperiod=period)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        # GUARDS
        if self.entry_rsi_enabled.value:
            conditions.append(dataframe['rsi'] < self.entry_rsi.value)
        if self.entry_mfi_enabled.value:
            conditions.append(dataframe['mfi'] < self.entry_mfi.value)
        if self.entry_ema_enabled.value:
            conditions.append(dataframe[f'EMA_{self.entry_fastema.value}'] > dataframe[f'EMA_{self.entry_slowema.value}'])

        # TRIGGERS
        if self.entry_trigger.value == 'bb_lower1':
            conditions.append(dataframe["close"] < dataframe['bb_lowerband1'])
        if self.entry_trigger.value == 'bb_lower2':
            conditions.append(dataframe["close"] < dataframe['bb_lowerband2'])
        if self.entry_trigger.value == 'bb_lower3':
            conditions.append(dataframe["close"] < dataframe['bb_lowerband3'])
        if self.entry_trigger.value == 'bb_lower4':
            conditions.append(dataframe["close"] < dataframe['bb_lowerband4'])

        # Check that volume is not 0
        conditions.append(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:
        conditions = []

        # GUARDS
        if self.exit_rsi_enabled.value:
            conditions.append(dataframe['rsi'] > self.exit_rsi.value)
        if self.exit_mfi_enabled.value:
            conditions.append(dataframe['mfi'] > self.exit_mfi.value)
        if self.exit_ema_enabled.value:
            conditions.append(dataframe[f'EMA_{self.exit_fastema.value}'] < dataframe[f'EMA_{self.exit_slowema.value}'])

        # TRIGGERS
        if self.exit_trigger.value == 'exit-bb_upper1':
            conditions.append(dataframe["close"] > dataframe['bb_upperband1'])
        if self.exit_trigger.value == 'exit-bb_upper2':
            conditions.append(dataframe["close"] > dataframe['bb_upperband2'])
        if self.exit_trigger.value == 'exit-bb_upper3':
            conditions.append(dataframe["close"] > dataframe['bb_upperband3'])
        if self.exit_trigger.value == 'exit-bb_upper4':
            conditions.append(dataframe["close"] > dataframe['bb_upperband4'])

        # Check that volume is not 0
        conditions.append(dataframe['volume'] > 0)

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

        return dataframe
