# source: https://raw.githubusercontent.com/smpsandro1239/Freqtrade/848a371110f887d668304a694752a4fdcb47f943/user_data/strategies/WaveHyperNWEnhanced.py
# -*- coding: utf-8 -*-
"""
Github_smpsandro1239_Freqtrade__WaveHyperNWEnhanced__20250812_223243 - Versão melhorada da WaveHyperNW Strategy
Implementações nativas + melhorias de performance e sinais
"""
import logging
from datetime import datetime

import numpy as np
import pandas as pd

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

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class Github_smpsandro1239_Freqtrade__WaveHyperNWEnhanced__20250812_223243(IStrategy):
    """
    Estratégia WaveHyperNW Melhorada

    Melhorias implementadas:
    - Implementações nativas (sem TA-Lib)
    - WaveTrend otimizado
    - Nadaraya-Watson melhorado
    - Filtros de volatilidade
    - Stop loss dinâmico
    - Gestão de risco aprimorada
    """

    INTERFACE_VERSION = 3

    # Configurações básicas
    timeframe = "5m"
    stoploss = -0.08  # Melhorado: stop mais conservador
    minimal_roi = {
        "0": 0.05,  # Melhorado: ROI mais realista
        "5": 0.035,
        "10": 0.025,
        "15": 0.015,
        "30": 0.008,
        "60": 0.003,
    }

    # Trailing stop melhorado
    trailing_stop = True
    trailing_stop_positive = 0.02
    trailing_stop_positive_offset = 0.035
    trailing_only_offset_is_reached = True

    # Parâmetros WaveTrend otimizados
    wt_channel_len = IntParameter(6, 12, default=8, space="buy")
    wt_average_len = IntParameter(14, 22, default=18, space="buy")
    wt_overbought = DecimalParameter(45, 65, default=55, space="sell")
    wt_oversold = DecimalParameter(-65, -45, default=-55, space="buy")

    # Parâmetros Nadaraya-Watson
    nw_bandwidth = DecimalParameter(2.0, 5.0, default=3.5, space="buy")
    nw_lookback = IntParameter(15, 35, default=25, space="buy")
    nw_std_multiplier = DecimalParameter(0.8, 2.0, default=1.2, space="buy")

    # Filtros de qualidade
    min_volume_ratio = DecimalParameter(0.3, 1.0, default=0.5, space="buy")
    max_volatility = DecimalParameter(0.02, 0.08, default=0.05, space="buy")
    trend_strength_min = DecimalParameter(0.4, 0.8, default=0.6, space="buy")

    # Proteções melhoradas
    cooldown_lookback = IntParameter(5, 30, default=12, space="protection")
    stop_duration = IntParameter(20, 120, default=45, space="protection")
    max_drawdown_protection = DecimalParameter(
        0.05, 0.15, default=0.10, space="protection"
    )
    use_stop_protection = CategoricalParameter(
        [True, False], default=True, space="protection"
    )

    def get_int_value(self, param):
        """Helper para converter parâmetros para int"""
        return int(param.value)

    @property
    def protections(self):
        if not self.use_stop_protection.value:
            return []
        return [
            {
                "method": "CooldownPeriod",
                "stop_duration_candles": self.get_int_value(self.stop_duration),
            },
            {
                "method": "StoplossGuard",
                "lookback_period_candles": self.get_int_value(self.cooldown_lookback),
                "trade_limit": 3,  # Mais conservador
                "stop_duration_candles": self.get_int_value(self.stop_duration),
                "only_per_pair": False,
            },
            {
                "method": "MaxDrawdown",
                "lookback_period_candles": 200,
                "trade_limit": 5,
                "stop_duration_candles": self.get_int_value(self.stop_duration) * 2,
                "max_allowed_drawdown": self.max_drawdown_protection.value,
            },
        ]

    def ema(self, series: pd.Series, period: int) -> pd.Series:
        """Implementação nativa da EMA"""
        return series.ewm(span=period, adjust=False).mean()

    def sma(self, series: pd.Series, period: int) -> pd.Series:
        """Implementação nativa da SMA"""
        return series.rolling(window=period).mean()

    def rsi(self, series: pd.Series, period: int = 14) -> pd.Series:
        """Implementação nativa do RSI"""
        delta = series.diff()
        gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
        rs = gain / loss
        return 100 - (100 / (1 + rs))

    def atr(self, high: pd.Series, low: pd.Series, close: pd.Series, period: int = 14):
        """Implementação nativa do ATR"""
        tr1 = high - low
        tr2 = abs(high - close.shift())
        tr3 = abs(low - close.shift())
        tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
        return tr.rolling(window=period).mean()

    def wavetrend(self, dataframe: pd.DataFrame) -> tuple:
        """
        Implementação melhorada do WaveTrend
        """
        # Average Price
        ap = (dataframe["high"] + dataframe["low"] + dataframe["close"]) / 3

        # Parâmetros
        channel_len = self.get_int_value(self.wt_channel_len)
        average_len = self.get_int_value(self.wt_average_len)

        # ESA (Exponential Smoothed Average)
        esa = self.ema(ap, channel_len)

        # Deviation
        d = self.ema(abs(ap - esa), channel_len)

        # Channel Index
        ci = (ap - esa) / (0.015 * d)

        # WaveTrend lines
        wt1 = self.ema(ci, average_len)
        wt2 = self.sma(wt1, 4)

        return wt1, wt2

    def nadaraya_watson(self, dataframe: pd.DataFrame) -> tuple:
        """
        Implementação melhorada do Nadaraya-Watson Estimator
        """
        close = dataframe["close"].values
        bandwidth = self.nw_bandwidth.value
        lookback = self.get_int_value(self.nw_lookback)

        # Kernel Gaussiano
        weights = np.array(
            [
                np.exp(-(i**2) / (2 * bandwidth**2))
                for i in range(-lookback, lookback + 1)
            ]
        )
        weights = weights / weights.sum()

        # Aplicar convolução
        nw_estimate = np.convolve(close, weights, mode="same")
        nw_series = pd.Series(nw_estimate, index=dataframe.index)

        # Bandas baseadas em desvio padrão
        std = dataframe["close"].rolling(lookback).std()
        multiplier = self.nw_std_multiplier.value

        nw_upper = nw_series + multiplier * std
        nw_lower = nw_series - multiplier * std

        return nw_series, nw_upper, nw_lower

    def calculate_trend_strength(self, dataframe: pd.DataFrame) -> pd.Series:
        """
        Calcular força da tendência (0 = bearish, 1 = bullish)
        """
        # EMA alignment
        ema_8 = dataframe["ema_8"]
        ema_21 = dataframe["ema_21"]
        ema_50 = dataframe["ema_50"]

        ema_score = (
            (ema_8 > ema_21).astype(int) * 0.4
            + (ema_21 > ema_50).astype(int) * 0.3
            + (dataframe["close"] > ema_8).astype(int) * 0.3
        )

        # RSI momentum
        rsi_normalized = (dataframe["rsi"] - 50) / 50
        rsi_score = (rsi_normalized.clip(-1, 1) + 1) / 2

        # WaveTrend momentum
        wt_score = ((dataframe["wt1"] + 100) / 200).clip(0, 1)

        # Combine scores
        trend_strength = ema_score * 0.5 + rsi_score * 0.3 + wt_score * 0.2

        return trend_strength.clip(0, 1)

    def calculate_volatility_filter(self, dataframe: pd.DataFrame) -> pd.Series:
        """
        Filtro de volatilidade para evitar mercados muito voláteis
        """
        # ATR normalizado
        atr_normalized = dataframe["atr"] / dataframe["close"]

        # Volatilidade baseada em desvio padrão
        price_volatility = dataframe["close"].rolling(20).std() / dataframe["close"]

        # Combinar métricas
        volatility = (atr_normalized + price_volatility) / 2

        return volatility < self.max_volatility.value

    def populate_indicators(self, dataframe, metadata):
        """
        Calcular todos os indicadores
        """
        # Indicadores básicos
        dataframe["rsi"] = self.rsi(dataframe["close"])
        dataframe["ema_8"] = self.ema(dataframe["close"], 8)
        dataframe["ema_21"] = self.ema(dataframe["close"], 21)
        dataframe["ema_50"] = self.ema(dataframe["close"], 50)
        dataframe["atr"] = self.atr(
            dataframe["high"], dataframe["low"], dataframe["close"]
        )

        # Volume
        dataframe["volume_sma"] = self.sma(dataframe["volume"], 24)
        dataframe["volume_ratio"] = dataframe["volume"] / dataframe["volume_sma"]

        # WaveTrend melhorado
        dataframe["wt1"], dataframe["wt2"] = self.wavetrend(dataframe)

        # Nadaraya-Watson melhorado
        dataframe["nw_estimate"], dataframe["nw_upper"], dataframe["nw_lower"] = (
            self.nadaraya_watson(dataframe)
        )

        # Trend strength
        dataframe["trend_strength"] = self.calculate_trend_strength(dataframe)

        # Filtros de qualidade
        dataframe["volatility_ok"] = self.calculate_volatility_filter(dataframe)

        # Momentum adicional
        dataframe["price_momentum"] = dataframe["close"].pct_change(5)
        dataframe["volume_momentum"] = dataframe["volume"].pct_change(3)

        # Support/Resistance dinâmico
        dataframe["resistance"] = dataframe["high"].rolling(20).max()
        dataframe["support"] = dataframe["low"].rolling(20).min()
        dataframe["price_position"] = (dataframe["close"] - dataframe["support"]) / (
            dataframe["resistance"] - dataframe["support"]
        )

        return dataframe

    def populate_entry_trend(self, dataframe, metadata):
        """
        Sinais de entrada melhorados
        """
        # Condições WaveTrend melhoradas
        wt_bullish = (
            (dataframe["wt1"] > dataframe["wt2"])
            & (dataframe["wt1"] < self.wt_oversold.value)
            & (dataframe["wt1"].shift(1) <= dataframe["wt1"])  # Momentum crescente
        )

        # Condições Nadaraya-Watson
        nw_support = (dataframe["close"] <= dataframe["nw_lower"]) | (
            (dataframe["close"] < dataframe["nw_estimate"]) & (dataframe["rsi"] < 40)
        )

        # Condições de tendência
        trend_favorable = (
            dataframe["trend_strength"] > self.trend_strength_min.value
        ) & (dataframe["ema_8"] > dataframe["ema_21"])

        # Condições de volume
        volume_ok = (dataframe["volume_ratio"] > self.min_volume_ratio.value) & (
            dataframe["volume_momentum"] > -0.2
        )  # Volume não em queda livre

        # Filtros de qualidade
        quality_filters = (
            dataframe["volatility_ok"]
            & (dataframe["price_momentum"] > -0.01)  # Não em queda livre
            & (dataframe["price_position"] < 0.8)  # Não muito próximo da resistência
        )

        # Confirmação adicional: RSI não overbought
        rsi_ok = dataframe["rsi"] < 65

        # Sinal final
        entry_signal = (
            wt_bullish
            & nw_support
            & trend_favorable
            & volume_ok
            & quality_filters
            & rsi_ok
        )

        dataframe.loc[entry_signal, "enter_long"] = 1
        dataframe.loc[entry_signal, "enter_tag"] = "WT_NW_enhanced"

        return dataframe

    def populate_exit_trend(self, dataframe, metadata):
        """
        Sinais de saída melhorados
        """
        # Saída por WaveTrend overbought
        wt_exit = (
            (dataframe["wt1"] < dataframe["wt2"])
            & (dataframe["wt1"] > self.wt_overbought.value)
            & (dataframe["wt1"].shift(1) >= dataframe["wt1"])  # Momentum decrescente
        )

        # Saída por RSI overbought
        rsi_exit = dataframe["rsi"] > 75

        # Saída por deterioração da tendência
        trend_deterioration = (dataframe["trend_strength"] < 0.3) | (
            dataframe["ema_8"] < dataframe["ema_21"]
        )

        # Saída por profit taking
        profit_taking = (
            dataframe["close"] > dataframe["ema_8"] * 1.04
        ) | (  # 4% acima da EMA
            (dataframe["close"] > dataframe["ema_8"] * 1.025)
            & (dataframe["rsi"] > 70)
            & (dataframe["volume_ratio"] > 1.5)
        )

        # Saída por Nadaraya-Watson resistance
        nw_resistance = (dataframe["close"] >= dataframe["nw_upper"]) & (
            dataframe["rsi"] > 60
        )

        # Saída por volume anômalo
        volume_spike = dataframe["volume_ratio"] > 4.0

        # Saída por posição próxima à resistência
        resistance_exit = (dataframe["price_position"] > 0.9) & (dataframe["rsi"] > 65)

        # Sinal de saída
        exit_signal = (
            wt_exit
            | rsi_exit
            | trend_deterioration
            | profit_taking
            | nw_resistance
            | volume_spike
            | resistance_exit
        )

        # Confirmar com volume mínimo
        volume_confirm = dataframe["volume_ratio"] > 0.3

        dataframe.loc[exit_signal & volume_confirm, "exit_long"] = 1

        return dataframe

    def custom_stoploss(
        self,
        pair: str,
        trade: "Trade",
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs
    ) -> float:
        """
        Stop loss dinâmico baseado no ATR e volatilidade
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()

        if "atr" in last_candle and "trend_strength" in last_candle:
            # ATR normalizado
            atr_normalized = last_candle["atr"] / current_rate

            # Ajustar stop baseado na força da tendência
            trend_multiplier = 1.0 + (1.0 - last_candle["trend_strength"])

            # Stop loss dinâmico
            dynamic_stop = atr_normalized * 2.5 * trend_multiplier

            # Limites: mínimo 4%, máximo 12%
            dynamic_stop = max(0.04, min(0.12, dynamic_stop))

            return -dynamic_stop

        # Fallback
        return self.stoploss
