# source: https://raw.githubusercontent.com/remiotore/freqtrade/44beaeb6a420cd8e9f2e4ea93e11d6cfa192ee03/strategies/Discord_Consumer.py
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy as np
import talib.abstract as ta
from datetime import datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, informative, merge_informative_pair

## Missing IMports
from pandas import DataFrame

## DCA Imports
import math
import logging

logger = logging.getLogger(__name__)

class Github_remiotore_freqtrade__Discord_Consumer__20260111_210550(IStrategy):
    timeframe = '1m'
    sell_params = {
        # custom stoploss params, come from BB_RPB_TSL
        "pHSL": -0.32,
        "pPF_1": 0.02,
        "pPF_2": 0.047,
        "pSL_1": 0.02,
        "pSL_2": 0.046,
    }

    #...
    process_only_new_candles = False # required for consumers
    use_custom_stoploss = True
    stoploss = -0.999
    #### Comment: Dont know if this is right, you need to test how it needs to be configured for multiple producers
    _columns_to_expect = ['enter_long_NFIX', 'enter_long_Cluc', 'enter_long_BB_RPB', 'enter_long_Elliot']

    #### Comment: Your timeperiod is 1 all the time. I guess this should be adapted to the timeperiod used by 3c
    @informative('3m')
    def populate_indicators_3m(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=7)
        dataframe['uo'] = ta.ULTOSC(dataframe, timeperiod=1)

        return dataframe

    @informative('2h')
    def populate_indicators_2h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        dataframe['uo'] = ta.ULTOSC(dataframe, timeperiod=1)

        return dataframe

    @informative('4h')
    def populate_indicators_4h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

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

        return dataframe


    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Use the websocket api to get pre-populated indicators from another freqtrade instance.
        Use `self.dp.get_producer_df(pair)` to get the dataframe
        """
        pair = metadata['pair']
        timeframe = self.timeframe

        producer_pairs = self.dp.get_producer_pairs()
        # You can specify which producer to get pairs from via:
        # self.dp.get_producer_pairs("my_other_producer")

        #### Comment: Producer 1
        # This func returns the analyzed dataframe, and when it was analyzed
        producer_dataframe_NFIX, _ = self.dp.get_producer_df(pair, producer_name="NFIX")
        # You can get other data if the producer makes it available:
        # self.dp.get_producer_df(
        #   pair,
        #   timeframe="1h",
        #   candle_type=CandleType.SPOT,
        #   producer_name="my_other_producer"
        # )

        if not producer_dataframe_NFIX.empty:
            # If you plan on passing the producer's entry/exit signal directly,
            # specify ffill=False or it will have unintended results
            merged_dataframe = merge_informative_pair(dataframe, producer_dataframe_NFIX,
                                                      timeframe, timeframe,
                                                      append_timeframe=False,
                                                      suffix="NFIX", ffill=False)
            return merged_dataframe
        else:
            dataframe[self._columns_to_expect] = 0

        #### Comment: Producer 2
        # This func returns the analyzed dataframe, and when it was analyzed
        producer_dataframe_Cluc, _ = self.dp.get_producer_df(pair, producer_name="Cluc")
        # You can get other data if the producer makes it available:
        # self.dp.get_producer_df(
        #   pair,
        #   timeframe="1h",
        #   candle_type=CandleType.SPOT,
        #   producer_name="my_other_producer"
        # )

        if not producer_dataframe_Cluc.empty:
            # If you plan on passing the producer's entry/exit signal directly,
            # specify ffill=False or it will have unintended results
            merged_dataframe = merge_informative_pair(dataframe, producer_dataframe_Cluc,
                                                      timeframe, timeframe,
                                                      append_timeframe=False,
                                                      suffix="Cluc", ffill=False)
            return merged_dataframe
        else:
            dataframe[self._columns_to_expect] = 0

        #### Comment: Producer 3
        # This func returns the analyzed dataframe, and when it was analyzed
        producer_dataframe_BB_RPB, _ = self.dp.get_producer_df(pair, producer_name="BB_RPB")
        # You can get other data if the producer makes it available:
        # self.dp.get_producer_df(
        #   pair,
        #   timeframe="1h",
        #   candle_type=CandleType.SPOT,
        #   producer_name="my_other_producer"
        # )

        if not producer_dataframe_BB_RPB.empty:
            # If you plan on passing the producer's entry/exit signal directly,
            # specify ffill=False or it will have unintended results
            merged_dataframe = merge_informative_pair(dataframe, producer_dataframe_BB_RPB,
                                                      timeframe, timeframe,
                                                      append_timeframe=False,
                                                      suffix="BB_RPB", ffill=False)
            return merged_dataframe
        else:
            dataframe[self._columns_to_expect] = 0

        #### Comment: Producer 4
        # This func returns the analyzed dataframe, and when it was analyzed
        producer_dataframe_Elliot, _ = self.dp.get_producer_df(pair, producer_name="Elliot")
        # You can get other data if the producer makes it available:
        # self.dp.get_producer_df(
        #   pair,
        #   timeframe="1h",
        #   candle_type=CandleType.SPOT,
        #   producer_name="my_other_producer"
        # )

        if not producer_dataframe_Elliot.empty:
            # If you plan on passing the producer's entry/exit signal directly,
            # specify ffill=False or it will have unintended results
            merged_dataframe = merge_informative_pair(dataframe, producer_dataframe_Elliot,
                                                      timeframe, timeframe,
                                                      append_timeframe=False,
                                                      suffix="Elliot", ffill=False)
            return merged_dataframe
        else:
            dataframe[self._columns_to_expect] = 0

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Populates the entry signal for the given dataframe
        """
        # Use the dataframe columns as if we calculated them ourselves

        # ----------------------------
        dataframe["enter_long"] = 0 #Added due to error regarding tables not being initialized
        dataframe["enter_tag"] = 0 #Added due to error regarding tables not being initialized
        dataframe.loc[
            (
                (dataframe['enter_long_Cluc'] == 1) &
                (
                    (dataframe['rsi_3m'] > 70) &
                    (dataframe['uo_3m'] > 67) &
                    (dataframe['uo_2h'] > 57) &
                    (dataframe['rsi_4h'] > 63)
                ) &
                (dataframe['volume'] > 0)
            ), ['enter_long', 'enter_tag']] += (1, 'enter_long_Cluc;')
        # ----------------------------
        dataframe.loc[
            (
                (dataframe['enter_long_NFIX'] == 1) &
                (
                    (dataframe['rsi_3m'] > 70) &
                    (dataframe['uo_3m'] > 67) &
                    (dataframe['uo_2h'] > 57) &
                    (dataframe['rsi_4h'] > 63)
                ) &
                (dataframe['volume'] > 0)
            ), ['enter_long', 'enter_tag']] += (1, 'enter_long_NFIX;')
        # ----------------------------
        dataframe.loc[
            (
                (dataframe['enter_long_BB_RPB'] == 1) &
                (
                    (dataframe['rsi_3m'] > 70) &
                    (dataframe['uo_3m'] > 67) &
                    (dataframe['uo_2h'] > 57) &
                    (dataframe['rsi_4h'] > 63)
                ) &
                (dataframe['volume'] > 0)
            ), ['enter_long', 'enter_tag']] += (1, 'enter_long_BB_RPB;')
        # ----------------------------
        dataframe.loc[
            (
                (dataframe['enter_long_Elliot'] == 1) &
                (
                    (dataframe['rsi_3m'] > 70) &
                    (dataframe['uo_3m'] > 67) &
                    (dataframe['uo_2h'] > 57) &
                    (dataframe['rsi_4h'] > 63)
                ) &
                (dataframe['volume'] > 0)
            ), ['enter_long', 'enter_tag']] += (1, 'enter_long_Elliot;')
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[:, 'exit_long'] = 0
        dataframe.loc[:, 'exit_short'] = 0

        return dataframe

    def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
                        current_rate: float, current_profit: float, **kwargs) -> float:

        HSL = self.pHSL.value
        PF_1 = self.pPF_1.value
        SL_1 = self.pSL_1.value
        PF_2 = self.pPF_2.value
        SL_2 = self.pSL_2.value

        dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
        current_candle = dataframe.iloc[-1].squeeze()
        current_profit = trade.calc_profit_ratio(current_candle['close'])

        if current_profit > PF_2:
            sl_profit = SL_2 + (current_profit - PF_2)
        elif current_profit > PF_1:
            sl_profit = SL_1 + ((current_profit - PF_1) * (SL_2 - SL_1) / (PF_2 - PF_1))
        else:
            sl_profit = HSL

        if self.can_short:
            if (-1 + ((1 - sl_profit) / (1 - current_profit))) <= 0:
                return 1
        else:
            if (1 - ((1 + sl_profit) / (1 + current_profit))) <= 0:
                return 1

class Github_remiotore_freqtrade__Discord_Consumer__20260111_210550_dca(Github_remiotore_freqtrade__Discord_Consumer__20260111_210550):

    # DCA options
    position_adjustment_enable = True

    initial_safety_order_trigger = -0.01
    max_safety_orders = 30
    safety_order_step_scale = 1         #SS
    safety_order_volume_scale = 1.05        #OS

    # Auto compound calculation
    max_dca_multiplier = (1 + max_safety_orders)
    if (max_safety_orders > 0):
        if (safety_order_volume_scale > 1):
            max_dca_multiplier = (2 + (safety_order_volume_scale * (math.pow(safety_order_volume_scale, (max_safety_orders - 1)) - 1) / (safety_order_volume_scale - 1)))
        elif (safety_order_volume_scale < 1):
            max_dca_multiplier = (2 + (safety_order_volume_scale * (1 - math.pow(safety_order_volume_scale, (max_safety_orders - 1))) / (1 - safety_order_volume_scale)))

    # Let unlimited stakes leave funds open for DCA orders
    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                            proposed_stake: float, min_stake: float, max_stake: float,
                            **kwargs) -> float:

        if self.config['stake_amount'] == 'unlimited':
            return proposed_stake / self.max_dca_multiplier

        return proposed_stake

    # DCA
    def adjust_trade_position(self, trade: Trade, current_time: datetime,
                              current_rate: float, current_profit: float, min_stake: float,
                              max_stake: float, **kwargs):

        if current_profit > self.initial_safety_order_trigger:
            return None

        count_of_buys = trade.nr_of_successful_buys

        if 1 <= count_of_buys <= self.max_safety_orders:
            safety_order_trigger = (abs(self.initial_safety_order_trigger) * count_of_buys)
            if (self.safety_order_step_scale > 1):
                safety_order_trigger = abs(self.initial_safety_order_trigger) + (abs(self.initial_safety_order_trigger) * self.safety_order_step_scale * (math.pow(self.safety_order_step_scale,(count_of_buys - 1)) - 1) / (self.safety_order_step_scale - 1))
            elif (self.safety_order_step_scale < 1):
                safety_order_trigger = abs(self.initial_safety_order_trigger) + (abs(self.initial_safety_order_trigger) * self.safety_order_step_scale * (1 - math.pow(self.safety_order_step_scale,(count_of_buys - 1))) / (1 - self.safety_order_step_scale))

            if current_profit <= (-1 * abs(safety_order_trigger)):
                try:
                    stake_amount = self.wallets.get_trade_stake_amount(trade.pair, None)
                    # This calculates base order size
                    stake_amount = stake_amount / self.max_dca_multiplier
                    # This then calculates current safety order size
                    stake_amount = stake_amount * math.pow(self.safety_order_volume_scale, (count_of_buys - 1))
                    amount = stake_amount / current_rate
                    logger.info(f"Initiating safety order buy #{count_of_buys} for {trade.pair} with stake amount of {stake_amount} which equals {amount}")
                    return stake_amount
                except Exception as exception:
                    logger.info(f'Error occured while trying to get stake amount for {trade.pair}: {str(exception)}')
                    return None

        return None
