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




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

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


from functools import reduce
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib

class Github_remiotore_freqtrade__macdPlus__20260111_210550(IStrategy):
    INTERFACE_VERSION = 3

    timeframe = '1h'

    can_short: bool = True

    order_types = {
    'entry': 'limit',
    'exit': 'limit',
    'stoploss': 'market',
    'stoploss_on_exchange': True
    }
    order_time_in_force = {
        "entry": "GTC",
        "exit": "GTC"
    }


    macd_ma_period = IntParameter(20, 50, default=20, space='buy', optimize=True, load=True)
    macd_signal_period = IntParameter(5, 15, default=14, space='buy', optimize=True, load=True)

    check_macd_position = BooleanParameter(default=False, space='buy', optimize=True, load=True)

    minimal_roi = {
        "0": 0.8
    }

    stoploss = -0.5

    trailing_stop = True
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.09
    trailing_only_offset_is_reached = True
    
    process_only_new_candles = False
    use_exit_signal = True

    startup_candle_count: int = 100

    def _cal_smma(self, series:pd.Series, period: int) -> pd.Series:
        return series.ewm(alpha=1/period, adjust=False, min_periods=period).mean()

    def _cal_zero_lag_ema(self, series:pd.Series, period: int) -> pd.Series:
        ema_1 = ta.EMA(series, timeperiod=period)
        ema_2 = ta.EMA(ema_1, timeperiod=period)
        return 2 * ema_1 - ema_2

    def impulsive_macd(self, dataframe: DataFrame, length_ma: int, length_signal: int) -> tuple:
        mean_hlc = dataframe[['high', 'low', 'close']].mean(axis=1)
        high_smma = self._cal_smma(dataframe['high'], length_ma)
        low_smma = self._cal_smma(dataframe['low'], length_ma)
        middle_zlema = self._cal_zero_lag_ema(mean_hlc, length_ma)

        impulse_macd = np.where(middle_zlema > high_smma, middle_zlema - high_smma, 0)
        impulse_macd = np.where(middle_zlema < low_smma, middle_zlema - low_smma, impulse_macd)

        impulse_macd_signal = ta.SMA(impulse_macd, timeperiod=length_signal)
        impulse_macd_hist = impulse_macd - impulse_macd_signal

        return impulse_macd, impulse_macd_signal, impulse_macd_hist

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

        for ma_period in self.macd_ma_period.range:
            for signal_period in self.macd_signal_period.range:
                macd, macdsignal, macdhist = self.impulsive_macd(dataframe, ma_period, signal_period)
                dataframe[f'impulse_macd_{ma_period}_{signal_period}'] = macd
                dataframe[f'impulse_macdsignal_{ma_period}_{signal_period}'] = macdsignal
                dataframe[f'impulse_macdhist_{ma_period}_{signal_period}'] = macdhist
        
        return dataframe

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

        triggers_long = []
        triggers_short = []

        guards_long = []
        guards_short = []

        triggers_long.append(qtpylib.crossed_above(dataframe[f'impulse_macd_{self.macd_ma_period.value}_{self.macd_signal_period.value}'], dataframe[f'impulse_macdsignal_{self.macd_ma_period.value}_{self.macd_signal_period.value}']))
        triggers_short.append(qtpylib.crossed_below(dataframe[f'impulse_macd_{self.macd_ma_period.value}_{self.macd_signal_period.value}'], dataframe[f'impulse_macdsignal_{self.macd_ma_period.value}_{self.macd_signal_period.value}']))

        if self.check_macd_position.value:
            guards_long.append(dataframe[f'impulse_macdsignal_{self.macd_ma_period.value}_{self.macd_signal_period.value}'] < 0)
            guards_short.append(dataframe[f'impulse_macdsignal_{self.macd_ma_period.value}_{self.macd_signal_period.value}'] > 0)

        guards_long.append(dataframe['volume'] > 0)
        guards_short.append(dataframe['volume'] > 0)

        if triggers_long:
            cond_long = reduce(lambda x, y: x | y, triggers_long) & reduce(lambda x, y: x & y, guards_long)
            dataframe.loc[cond_long, 'enter_long'] = 1
            dataframe.loc[cond_long, 'enter_tag'] = 'long'
        
        if triggers_short:
            cond_short = reduce(lambda x, y: x | y, triggers_short) & reduce(lambda x, y: x & y, guards_short)
            dataframe.loc[cond_short, 'enter_short'] = 1
            dataframe.loc[cond_short, 'enter_tag'] = 'short'
            
        return dataframe

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

        triggers_long = []
        triggers_short = []

        guards_long = []
        guards_short = []

        triggers_short.append(qtpylib.crossed_above(dataframe[f'impulse_macd_{self.macd_ma_period.value}_{self.macd_signal_period.value}'], dataframe[f'impulse_macdsignal_{self.macd_ma_period.value}_{self.macd_signal_period.value}']))
        triggers_long.append(qtpylib.crossed_below(dataframe[f'impulse_macd_{self.macd_ma_period.value}_{self.macd_signal_period.value}'], dataframe[f'impulse_macdsignal_{self.macd_ma_period.value}_{self.macd_signal_period.value}']))

        if self.check_macd_position.value:
            guards_short.append(dataframe[f'impulse_macdsignal_{self.macd_ma_period.value}_{self.macd_signal_period.value}'] < 0)
            guards_long.append(dataframe[f'impulse_macdsignal_{self.macd_ma_period.value}_{self.macd_signal_period.value}'] > 0)

        guards_long.append(dataframe['volume'] > 0)
        guards_short.append(dataframe['volume'] > 0)

        if triggers_long:
            dataframe.loc[
                reduce(lambda x, y: x | y, triggers_long) & reduce(lambda x, y: x & y, guards_long),
                'exit_long'] = 1
        
        if triggers_short:
            dataframe.loc[
                reduce(lambda x, y: x | y, triggers_short) & reduce(lambda x, y: x & y, guards_short),
                'exit_short'] = 1

        return dataframe

    def confirm_trade_entry(
        self,
        pair: str,
        order_type: str,
        amount: float,
        rate: float,
        time_in_force: str,
        current_time: datetime,
        entry_tag: Optional[str],
        side: str,
        **kwargs
    ) -> bool:
        open_trades = Trade.get_open_trades()

        num_shorts, num_longs = 0, 0
        for trade in open_trades:
            if "short" in trade.enter_tag:
                num_shorts += 1
            elif "long" in trade.enter_tag:
                num_longs += 1

        if side == "long" and num_longs >= 5:
            return False

        if side == "short" and num_shorts >= 5:
            return False

        return True

    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                 proposed_leverage: float, max_leverage: float, entry_tag: Optional[str],
                 side: str, **kwargs) -> float:

        return 5
    
    @property
    def plot_config(self):
        return {
            'main_plot': {},
            'subplots': {
                "IMPULSE_MACD": {
                    f"impulse_macd_{self.macd_ma_period.value}_{self.macd_signal_period.value}": {'color': 'blue'},
                    f"impulse_macdsignal_{self.macd_ma_period.value}_{self.macd_signal_period.value}": {'color': 'orange'},
                    f"impulse_macdhist_{self.macd_ma_period.value}_{self.macd_signal_period.value}": {'type': 'bar', 'plotly': {'opacity': 0.9}}
                }
            }
        }
    
