# source: https://raw.githubusercontent.com/wynnforthework/quant-strategies-knowledge/523670ea407d3e361fdf8adcd2c74819d2275e83/freqtrade-strategies/SwingTradingStrategy.py
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy import IntParameter, DecimalParameter
from pandas import DataFrame
import talib.abstract as ta


class Github_wynnforthework_quant_strategies_knowledge__SwingTradingStrategy__20251007_112722(IStrategy):
    can_short: bool = True
    timeframe = '4h'

    # Swing targets: larger moves and longer holds
    minimal_roi = {
        "0": 0.15,
    }
    stoploss = -0.15  # Hard floor – custom ATR stoploss will trail above this

    # We'll use a custom ATR-based stop instead of classical trailing stop
    use_custom_stoploss = True
    trailing_stop = False

    # Ensure enough candles for EMA200 and ATR(14)
    startup_candle_count = 210

    process_only_new_candles = True

    # --- Hyperoptable parameters to enable default buy/sell spaces ---
    buy_rsi_max = IntParameter(55, 80, default=70, space='buy')
    buy_macd_hist_min = DecimalParameter(0.0, 0.010, decimals=3, default=0.000, space='buy')

    sell_rsi_min = IntParameter(60, 90, default=80, space='sell')
    sell_macd_hist_min = DecimalParameter(0.0, 0.010, decimals=3, default=0.000, space='sell')

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # MACD
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']
        dataframe['macdhist'] = macd['macdhist']

        # Trend filter – avoid counter-trend swings
        dataframe['ema200'] = ta.EMA(dataframe, timeperiod=200)

        # Momentum / sanity filter (optional but helpful)
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)

        # ATR for volatility-aware stop management
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)

        # Volume filter helpers
        dataframe['volume_mean_slow'] = dataframe['volume'].rolling(30).mean()

        return dataframe

    def leverage(self, pair, current_time, current_rate, proposed_leverage, max_leverage, entry_tag, side, **kwargs) -> float:
        stop = abs(float(self.stoploss)) if getattr(self, "stoploss", None) is not None else 0.15
        base = 0.05 / stop if stop > 0 else (proposed_leverage or 1.0)
        base = max(1.0, min(float(base), float(max_leverage)))
        return base

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Golden cross detection
        macd_cross_up = (
            (dataframe['macd'] > dataframe['macdsignal']) &
            (dataframe['macd'].shift(1) <= dataframe['macdsignal'].shift(1))
        )
        # MACD histogram threshold (momentum confirmation)
        macd_hist_ok = (dataframe['macdhist'] > float(self.buy_macd_hist_min.value))

        dataframe.loc[
            (
                macd_cross_up &
                (dataframe['close'] > dataframe['ema200']) &  # trend filter
                (dataframe['rsi'] < int(self.buy_rsi_max.value)) &  # avoid buying into extreme overbought
                macd_hist_ok &
                (dataframe['volume'] > 0) &
                (dataframe['volume'] > dataframe['volume_mean_slow'])
            ),
            'enter_long'
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Dead cross detection – primary exit condition
        macd_cross_down = (
            (dataframe['macd'] < dataframe['macdsignal']) &
            (dataframe['macd'].shift(1) >= dataframe['macdsignal'].shift(1))
        )
        # MACD histogram negative enough (loss of momentum)
        macd_hist_weak = ((dataframe['macdsignal'] - dataframe['macd']) > float(self.sell_macd_hist_min.value))

        dataframe.loc[
            (
                macd_cross_down |
                # Safety: loss of momentum or trend break
                (dataframe['rsi'] > int(self.sell_rsi_min.value)) |
                macd_hist_weak |
                (dataframe['close'] < dataframe['ema200'])
            ),
            'exit_long'
        ] = 1

        return dataframe

    # --- Custom ATR-based stoploss ---
    # Define a modest ATR multiplier for swings – can be hyperoptimized later
    atr_multiplier: float = 2.0

    def custom_stoploss(
        self,
        pair: str,
        trade,  # type: ignore[override]
        current_time,
        current_rate: float,
        current_profit: float,
        after_fill: bool,
        **kwargs,
    ) -> float | None:
        """
        ATR-based dynamic stoploss.
        For long trades: stop_price = close - atr_multiplier * ATR(14).
        Convert absolute stop to distance ratio relative to current_rate.
        Returning None leaves current stop unchanged.
        """
        try:
            dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            last = dataframe.iloc[-1].squeeze()
            atr = float(last.get('atr', 0.0))
            close = float(last.get('close', 0.0))
        except Exception:
            return None

        if atr <= 0 or close <= 0:
            return None

        # Long-only logic – for futures shorting, mirror the formula accordingly
        stop_price = close - self.atr_multiplier * atr

        # If stop would be above current price due to extreme spikes, keep unchanged
        if stop_price >= current_rate:
            return None

        # Convert absolute stop to relative distance (sign ignored by framework)
        distance = abs((current_rate - stop_price) / current_rate)

        # Keep within hard floor enforced by self.stoploss (framework will cap it)
        return float(distance) if distance > 0 else None
