# source: https://raw.githubusercontent.com/yuu19/freqtrade-bot/5d70cfc482fa03358f099097a7f836c02fd760a0/ft_userdata/user_data/strategies/Supertrend.py
"""
リンク: https://github.com/freqtrade/freqtrade-strategies/blob/a9a3fc2b/user_data/strategies/Github_yuu19_freqtrade_bot__Supertrend__20260328_121453.py
Github_yuu19_freqtrade_bot__Supertrend__20260328_121453 strategy:
* Description: Generate a 3 supertrend indicators for 'buy' strategies & 3 supertrend indicators for 'sell' strategies
               Buys if the 3 'buy' indicators are 'up'
               Sells if the 3 'sell' indicators are 'down'
* Author: @juankysoriano (Juan Carlos Soriano)
* github: https://github.com/juankysoriano/

*** NOTE: This Github_yuu19_freqtrade_bot__Supertrend__20260328_121453 strategy is just one of many possible strategies using `Github_yuu19_freqtrade_bot__Supertrend__20260328_121453` as indicator. It should on any case used at your own risk.
          It comes with at least a couple of caveats:
            1. The implementation for the `supertrend` indicator is based on the following discussion: https://github.com/freqtrade/freqtrade-strategies/issues/30 . Concretelly https://github.com/freqtrade/freqtrade-strategies/issues/30#issuecomment-853042401
            2. The implementation for `supertrend` on this strategy is not validated; meaning this that is not proven to match the results by the paper where it was originally introduced or any other trusted academic resources
"""

from freqtrade.strategy import DecimalParameter, IntParameter, IStrategy
import pandas as pd
from pandas import DataFrame
import talib.abstract as ta
import numpy as np

class Github_yuu19_freqtrade_bot__Supertrend__20260328_121453(IStrategy):
    # Buy params, Sell params, ROI, Stoploss and Trailing Stop are values generated by 'freqtrade hyperopt --strategy Github_yuu19_freqtrade_bot__Supertrend__20260328_121453 --hyperopt-loss ShortTradeDurHyperOptLoss --timerange=20210101- --timeframe=1h --spaces all'
    # It's encourage you find the values that better suites your needs and risk management strategies

    INTERFACE_VERSION: int = 3
    # Buy hyperspace params:
    buy_params = {
        "buy_atr_ratio_max": 0.06,
        "buy_m1": 4,
        "buy_m2": 7,
        "buy_m3": 1,
        "buy_p1": 8,
        "buy_p2": 9,
        "buy_p3": 8,
        "buy_rsi_min": 52,
    }

    # Sell hyperspace params:
    sell_params = {
        "sell_m1": 1,
        "sell_m2": 3,
        "sell_m3": 6,
        "sell_p1": 16,
        "sell_p2": 18,
        "sell_p3": 18,
    }

    # ROI table:
    minimal_roi = {
        "0": 0.04,
        "240": 0.02,
        "720": 0.01,
        "1440": 0
    }

    # Stoploss:
    stoploss = -0.12

    # Trailing stop:
    trailing_stop = True
    trailing_stop_positive = 0.02
    trailing_stop_positive_offset = 0.04
    trailing_only_offset_is_reached = True

    timeframe = '1h'

    startup_candle_count = 199

    buy_m1 = IntParameter(1, 7, default=4)
    buy_m2 = IntParameter(1, 7, default=4)
    buy_m3 = IntParameter(1, 7, default=4)
    buy_p1 = IntParameter(7, 21, default=14)
    buy_p2 = IntParameter(7, 21, default=14)
    buy_p3 = IntParameter(7, 21, default=14)
    buy_rsi_min = IntParameter(48, 65, default=52)
    buy_atr_ratio_max = DecimalParameter(0.03, 0.10, decimals=3, default=0.06)

    sell_m1 = IntParameter(1, 7, default=4)
    sell_m2 = IntParameter(1, 7, default=4)
    sell_m3 = IntParameter(1, 7, default=4)
    sell_p1 = IntParameter(7, 21, default=14)
    sell_p2 = IntParameter(7, 21, default=14)
    sell_p3 = IntParameter(7, 21, default=14)

    @property
    def protections(self):
        return [
            {
                "method": "CooldownPeriod",
                "stop_duration_candles": 2,
            },
            {
                "method": "StoplossGuard",
                "lookback_period_candles": 72,
                "trade_limit": 2,
                "stop_duration_candles": 24,
                "only_per_pair": False,
            },
            {
                "method": "LowProfitPairs",
                "lookback_period_candles": 48,
                "trade_limit": 2,
                "stop_duration_candles": 12,
                "required_profit": 0.01,
            },
            {
                "method": "MaxDrawdown",
                "lookback_period_candles": 96,
                "trade_limit": 5,
                "stop_duration_candles": 48,
                "max_allowed_drawdown": 0.08,
            },
        ]

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        indicator_columns = {
            "ema_200": ta.EMA(dataframe, timeperiod=200),
            "rsi": ta.RSI(dataframe, timeperiod=14),
            "atr": ta.ATR(dataframe, timeperiod=14),
        }
        indicator_columns["atr_ratio"] = indicator_columns["atr"] / dataframe["close"]

        indicator_specs = [
            ('supertrend_1_buy', self.buy_m1.range, self.buy_p1.range),
            ('supertrend_2_buy', self.buy_m2.range, self.buy_p2.range),
            ('supertrend_3_buy', self.buy_m3.range, self.buy_p3.range),
            ('supertrend_1_sell', self.sell_m1.range, self.sell_p1.range),
            ('supertrend_2_sell', self.sell_m2.range, self.sell_p2.range),
            ('supertrend_3_sell', self.sell_m3.range, self.sell_p3.range),
        ]
        stx_cache = {}

        for prefix, multiplier_range, period_range in indicator_specs:
            for multiplier in multiplier_range:
                for period in period_range:
                    key = (multiplier, period)
                    if key not in stx_cache:
                        stx_cache[key] = self.supertrend(dataframe, multiplier, period)['STX']
                    indicator_columns[f'{prefix}_{multiplier}_{period}'] = stx_cache[key]

        return pd.concat([dataframe, DataFrame(indicator_columns, index=dataframe.index)], axis=1)

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
               (dataframe[f'supertrend_1_buy_{self.buy_m1.value}_{self.buy_p1.value}'] == 'up') &
               (dataframe[f'supertrend_2_buy_{self.buy_m2.value}_{self.buy_p2.value}'] == 'up') &
               (dataframe[f'supertrend_3_buy_{self.buy_m3.value}_{self.buy_p3.value}'] == 'up') & # The three indicators are 'up' for the current candle
               (dataframe['close'] > dataframe['ema_200']) &
               (dataframe['rsi'] >= self.buy_rsi_min.value) &
               (dataframe['atr_ratio'] <= self.buy_atr_ratio_max.value) &
               (dataframe['volume'] > 0) # There is at least some trading volume
        ),
            'enter_long'] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
               (dataframe[f'supertrend_1_sell_{self.sell_m1.value}_{self.sell_p1.value}'] == 'down') &
               (dataframe[f'supertrend_2_sell_{self.sell_m2.value}_{self.sell_p2.value}'] == 'down') &
               (dataframe[f'supertrend_3_sell_{self.sell_m3.value}_{self.sell_p3.value}'] == 'down') & # The three indicators are 'down' for the current candle
               (dataframe['volume'] > 0) # There is at least some trading volume
            ),
            'exit_long'] = 1

        return dataframe



    """
        Github_yuu19_freqtrade_bot__Supertrend__20260328_121453 Indicator; adapted for freqtrade
        from: https://github.com/freqtrade/freqtrade-strategies/issues/30
    """
    def supertrend(self, dataframe: DataFrame, multiplier, period):
        price_data = dataframe[['high', 'low', 'close']].apply(pd.to_numeric, errors='coerce')
        true_range = ta.TRANGE(price_data)
        average_true_range = ta.SMA(true_range, period)

        mid_price = (price_data['high'] + price_data['low']) / 2.0
        basic_ub = np.asarray(mid_price + multiplier * average_true_range, dtype=float)
        basic_lb = np.asarray(mid_price - multiplier * average_true_range, dtype=float)
        close = price_data['close'].to_numpy(dtype=float, copy=True)

        final_ub = np.zeros(len(dataframe), dtype=float)
        final_lb = np.zeros(len(dataframe), dtype=float)

        for i in range(period, len(dataframe)):
            previous_final_ub = final_ub[i - 1]
            previous_final_lb = final_lb[i - 1]
            previous_close = close[i - 1]

            final_ub[i] = (
                basic_ub[i]
                if basic_ub[i] < previous_final_ub or previous_close > previous_final_ub
                else previous_final_ub
            )
            final_lb[i] = (
                basic_lb[i]
                if basic_lb[i] > previous_final_lb or previous_close < previous_final_lb
                else previous_final_lb
            )

        st = np.zeros(len(dataframe), dtype=float)
        for i in range(period, len(dataframe)):
            previous_st = st[i - 1]
            current_close = close[i]

            if previous_st == final_ub[i - 1]:
                st[i] = final_ub[i] if current_close <= final_ub[i] else final_lb[i]
            elif previous_st == final_lb[i - 1]:
                st[i] = final_lb[i] if current_close >= final_lb[i] else final_ub[i]

        st_series = pd.Series(st, index=dataframe.index, dtype='float64').fillna(0.0)
        stx_series = pd.Series(
            np.where(close < st_series.to_numpy(copy=False), 'down', 'up'),
            index=dataframe.index,
            dtype='object',
        ).where(st_series > 0.0, None)

        return DataFrame(index=dataframe.index, data={
            'ST': st_series,
            'STX': stx_series,
        })
