# source: https://raw.githubusercontent.com/remiotore/freqtrade/44beaeb6a420cd8e9f2e4ea93e11d6cfa192ee03/strategies/ViTradeTuned.py




import numpy as np  # noqa
import pandas as pd  # noqa
from pandas import DataFrame
from typing import Optional, Union

from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
                                IStrategy, IntParameter)
from freqtrade.strategy import merge_informative_pair


import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from technical.consensus import Consensus


PAIR_INF_TIMEFRAME = '1h'

class Github_remiotore_freqtrade__ViTradeTuned__20260111_210550(IStrategy):
    INTERFACE_VERSION = 3

    can_short: bool = False

    timeframe = '5m'

    process_only_new_candles = True

    trailing_stop = True
    trailing_stop_positive = 0.01032
    trailing_stop_positive_offset = 0.03518
    trailing_only_offset_is_reached = True













    minimal_roi = {
        "0": 0.109,
        "34": 0.056,
        "87": 0.032,
        "143": 0
    }

    stoploss = -0.1

    trailing_stop = True  # value loaded from strategy
    trailing_stop_positive = 0.049  # value loaded from strategy
    trailing_stop_positive_offset = 0.13  # value loaded from strategy
    trailing_only_offset_is_reached = True  # value loaded from strategy

    buy_signal_buy_score_consensus = IntParameter(low=1, high=100, default=21, space='buy', optimize=True, load=True)
    buy_signal_sell_score_consensus = IntParameter(low=1, high=100, default=41, space='buy', optimize=True, load=True)
    buy_signal_buy_score_delta_threshold = DecimalParameter(low=0.01, high=0.1, default=0.03, space='buy', optimize=True, load=True)

    buy_signal_bb_perc = DecimalParameter(low=0.1, high=1, default=0.83, space='buy', optimize=True, load=True)

    buy_signal_bb_std_dev = IntParameter(low=1, high=3, default=1, space='buy', optimize=True, load=True)
    buy_signal_bb_window = IntParameter(low=8, high=300, default=83, space='buy', optimize=True, load=True)



    buy_signal_rolling_window_higher_tf = IntParameter(low=1, high=50, default=21, space='buy', optimize=True, load=True)
    buy_signal_skip_spikes_higher_tf = DecimalParameter(low=0.01, high=0.25, default=0.2, space='buy', optimize=True, load=True)

    sell_signal_sell_score_consensus = IntParameter(low=1, high=100, default=18, space='sell', optimize=True, load=True)
    sell_signal_bb_perc = DecimalParameter(low=0.1, high=0.5, default=0.3, space='sell', optimize=True, load=True)

    use_sell_signal = True
    sell_profit_only = True
    ignore_roi_if_buy_signal = True
    startup_candle_count = 100

    order_types = {
        'entry': 'limit',
        'exit': 'limit',
        'stoploss': 'market',
        'stoploss_on_exchange': False
    }

    order_time_in_force = {
        'entry': 'GTC',
        'exit': 'GTC'
    }

    plot_config = {
        'main_plot': {
            'tema': {},
            'sar': {'color': 'white'},
        },
        'subplots': {
            "MACD": {
                'macd': {'color': 'blue'},
                'macdsignal': {'color': 'orange'},
            },
            "RSI": {
                'rsi': {'color': 'red'},
            }
        }
    }

    def informative_pairs(self):
        """
        Define additional, informative pair/interval combinations to be cached from the exchange.
        These pair/interval combinations are non-tradeable, unless they are part
        of the whitelist as well.
        For more information, please consult the documentation
        :return: List of tuples in the format (pair, interval)
            Sample: return [("ETH/USDT", "5m"),
                            ("BTC/USDT", "15m"),
                            ]
        """
        return []

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=PAIR_INF_TIMEFRAME)

        informative['rolling_max'] = informative['close'].rolling(window=21).max()
        informative['roc'] = informative['close'].pct_change()

        dataframe = merge_informative_pair(dataframe, informative, self.timeframe, PAIR_INF_TIMEFRAME, ffill=True)

        c = Consensus(dataframe)
        c.evaluate_hull()
        c.evaluate_adx()
        c.evaluate_macd()
        c.evaluate_cci()
        c.evaluate_osc()
        c.evaluate_vwma()

        score = c.score()

        dataframe['score_sell'] = score['sell']
        dataframe['score_buy'] = score['buy']
        dataframe['buy_agreement'] = score['buy_agreement']
        dataframe['buy_disagreement'] = score['buy_disagreement']
        dataframe['sell_agreement'] = score['sell_agreement']
        dataframe['sell_disagreement'] = score['sell_disagreement']

        mid, lower = bollinger_bands(dataframe['low'], window_size=self.buy_signal_bb_window.value,
                                     num_of_std=self.buy_signal_bb_std_dev.value)
        dataframe['mid'] = np.nan_to_num(mid)
        dataframe['lower'] = np.nan_to_num(lower)
        dataframe['bbdelta'] = (dataframe['mid'] - dataframe['lower']).abs()
        dataframe['pricedelta'] = (dataframe['open'] - dataframe['close']).abs()
        dataframe['closedelta'] = (dataframe['close'] - dataframe['close'].shift()).abs()
        dataframe['tail'] = (dataframe['close'] - dataframe['low']).abs()

        bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=self.buy_signal_bb_std_dev.value)
        dataframe['bb_low'] = bollinger['lower']
        dataframe['bb_mid'] = bollinger['mid']
        dataframe['bb_upper'] = bollinger['upper']
        dataframe['bb_perc'] = (dataframe['close'] - dataframe['bb_low']) / (dataframe['bb_upper'] - dataframe['bb_low'])

        dataframe['roc'] = dataframe['close'].pct_change()
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=7)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Based on TA indicators, populates the entry signal for the given dataframe
        :param dataframe: DataFrame
        :param metadata: Additional information, like the currently traded pair
        :return: DataFrame with entry columns populated
        """

        dataframe.loc[
            (

                    (dataframe["close_"+PAIR_INF_TIMEFRAME].rolling(self.buy_signal_rolling_window_higher_tf.value).max().ge(dataframe['close']))
                    &

                    (dataframe['roc_'+PAIR_INF_TIMEFRAME] < self.buy_signal_skip_spikes_higher_tf.value)
                    &
                        (
                            dataframe['score_sell'].ge(self.buy_signal_sell_score_consensus.value) |
                            dataframe['score_buy'].ge(self.buy_signal_buy_score_consensus.value)
                        )
                    &
                    (dataframe['bb_perc'].le(self.buy_signal_bb_perc.value))
                    &
                    (dataframe['closedelta'].gt(dataframe['close'] * self.buy_signal_buy_score_delta_threshold.value))




                    &
                    (dataframe['volume'] > 0)  # Make sure Volume is not 0
            ),
            'enter_long'] = 1

        dataframe.loc[
            (
                    dataframe["close_" + PAIR_INF_TIMEFRAME].rolling(
                        self.buy_signal_rolling_window_higher_tf.value).min().le(dataframe['close']) &
                    (dataframe['roc_' + PAIR_INF_TIMEFRAME].gt(-self.buy_signal_skip_spikes_higher_tf.value)) &
                    dataframe['score_buy'].le(self.buy_signal_sell_score_consensus.value) &
                    dataframe['bb_perc'].ge(1 - self.buy_signal_bb_perc.value) &
                    dataframe['closedelta'].lt(dataframe['close'] * self.buy_signal_buy_score_delta_threshold.value) &
                    dataframe['volume'] > 0
            ),
            'enter_short'] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                (dataframe['bb_perc'] > self.sell_signal_bb_perc.value) &
                (dataframe['score_sell'].ge(self.sell_signal_sell_score_consensus.value))
            ),

            'exit_long'] = 1

        dataframe.loc[
            (
                    dataframe['bb_perc'].lt(1 - self.sell_signal_bb_perc.value) &
                    dataframe['score_buy'].ge(self.sell_signal_sell_score_consensus.value)
            ),
            'exit_short'] = 1

        return dataframe


def bollinger_bands(stock_price, window_size, num_of_std):
    rolling_mean = stock_price.rolling(window=window_size).mean()
    rolling_std = stock_price.rolling(window=window_size).std()
    lower_band = rolling_mean - (rolling_std * num_of_std)

    return rolling_mean, lower_band
