# source: https://raw.githubusercontent.com/DOUGLASGUEDESATRIA/OSIRIS_TRADE/fe3c09567f3798d1cad6d7a306fa4f6131d91e17/user_data/strategies/OsirisUltimateStrategy.py
"""
OSIRIS ULTIMATE STRATEGY v2.0 — A Estratégia Definitiva
=========================================================
TUDO que a literatura descreve + inovações proprietárias que não existem.

ARSENAL COMPLETO (80+ indicadores organizados em 10 categorias):

━━━ CLÁSSICOS REFORÇADOS ━━━
RSI dual-period, MACD, Stochastic, CCI, Williams %R, ROC,
ADX/DMI, Ichimoku, Parabolic SAR, Aroon

━━━ ONDAS DE ELLIOTT (Proxy Computacional) ━━━
Swing detector, Wave counter, Fibonacci auto-levels,
Impulse/Corrective classifier, Wave 3 detector

━━━ WYCKOFF / SMART MONEY ━━━
Accumulation/Distribution zones, Spring/UTAD detection,
Effort vs Result (volume vs price move), Composite Man tracker

━━━ HARMONIC PATTERNS ━━━
AB=CD proxy, Fibonacci retracement zones, extension targets

━━━ VOLUME FLOW INSTITUCIONAL ━━━
Volume Delta, CVD, Pressure Ratio, OBV, MFI, CMF, VPT,
Force Index, VWAP, Volume Climax, Absorption detection

━━━ MICROESTRUTURA DE MERCADO ━━━
Pin bars, Engulfing, Displacement, FVG, Liquidity sweeps,
Order block zones, Break of structure

━━━ TEORIA DA INFORMAÇÃO ━━━
Kaufman Efficiency Ratio, Volatility Entropy, Oscillation Index,
Trend Acceleration, Shannon Entropy do preço

━━━ PADRÕES GRÁFICOS ━━━
Double bottom/top proxy, Head & Shoulders proxy,
Support/Resistance dinâmicos, Pivot points

━━━ CORRELAÇÃO & REGIME ━━━
Volatility regime detector, Mean reversion vs Trend classifier,
Multi-timeframe alignment score

━━━ INOVAÇÕES PROPRIETÁRIAS OSIRIS ━━━
Volume-Weighted Momentum Divergence Index (V-WMDI)
Institutional Pressure Oscillator (IPO)
Regime-Adaptive Confidence Score (RACS)
Smart Money Footprint Composite (SMFC)

Score por Confluência: até 30 pontos possíveis
Hyperopt otimiza threshold + pesos + ROI + SL + trailing

100% proprietário. Custo API: $0.
"""

import logging
import numpy as np
from pandas import DataFrame
from typing import Optional

from freqtrade.strategy import IStrategy, merge_informative_pair
from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter
from freqtrade.persistence import Trade
import talib.abstract as ta

logger = logging.getLogger(__name__)


class Github_DOUGLASGUEDESATRIA_OSIRIS_TRADE__OsirisUltimateStrategy__20260329_060843(IStrategy):
    """
    OSIRIS Ultimate — 80+ indicadores, 10 categorias, score por confluência.
    Combina TODA a literatura + inovações proprietárias.
    Projetada para i9 + 128GB RAM + RTX 4090 (roda ok no laptop tb).
    """

    timeframe = "5m"

    minimal_roi = {
        "0": 0.06,
        "15": 0.04,
        "40": 0.025,
        "80": 0.015,
        "160": 0.005,
    }

    stoploss = -0.03
    trailing_stop = True
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.02
    trailing_only_offset_is_reached = True

    startup_candle_count = 200
    process_only_new_candles = True

    # ===================================================================
    # HYPEROPT PARAMS — ENTRY (20 parâmetros)
    # ===================================================================

    # Score threshold (de 30 possíveis)
    buy_score_threshold = IntParameter(5, 27, default=14, space="buy", optimize=True)

    # Volume Flow
    buy_pressure_min = DecimalParameter(0.50, 0.72, default=0.55, decimals=2, space="buy", optimize=True)
    buy_rvol_min = DecimalParameter(0.6, 2.5, default=1.0, decimals=1, space="buy", optimize=True)
    buy_cvd_periods = IntParameter(2, 8, default=4, space="buy", optimize=True)
    buy_mfi_min = IntParameter(20, 55, default=35, space="buy", optimize=True)

    # Momentum
    buy_rsi_min = IntParameter(20, 48, default=30, space="buy", optimize=True)
    buy_rsi_max = IntParameter(52, 78, default=68, space="buy", optimize=True)
    buy_stochrsi_max = IntParameter(15, 55, default=30, space="buy", optimize=True)
    buy_cci_min = IntParameter(-200, -20, default=-80, space="buy", optimize=True)

    # Trend
    buy_adx_min = IntParameter(12, 40, default=20, space="buy", optimize=True)
    buy_ema_ribbon_min = IntParameter(1, 4, default=2, space="buy", optimize=True)

    # Innovation
    buy_efficiency_min = DecimalParameter(0.03, 0.35, default=0.12, decimals=2, space="buy", optimize=True)
    buy_vol_ratio_max = DecimalParameter(0.6, 2.2, default=1.4, decimals=1, space="buy", optimize=True)

    # Wyckoff / Smart Money
    buy_spring_lookback = IntParameter(10, 40, default=20, space="buy", optimize=True)
    buy_effort_result_min = DecimalParameter(0.5, 2.5, default=1.2, decimals=1, space="buy", optimize=True)

    # Elliott proxy
    buy_fib_zone_pct = DecimalParameter(0.30, 0.70, default=0.50, decimals=2, space="buy", optimize=True)

    # Multi-TF
    buy_mtf_rsi_15m_min = IntParameter(30, 60, default=40, space="buy", optimize=True)
    buy_mtf_trend_aligned = CategoricalParameter([True, False], default=True, space="buy", optimize=True)

    # Regime
    buy_use_regime_filter = CategoricalParameter([True, False], default=True, space="buy", optimize=True)

    # ===================================================================
    # HYPEROPT PARAMS — EXIT (14 parâmetros)
    # ===================================================================

    sell_score_threshold = IntParameter(3, 22, default=10, space="sell", optimize=True)
    sell_rsi_max = IntParameter(58, 92, default=75, space="sell", optimize=True)
    sell_pressure_max = DecimalParameter(0.25, 0.50, default=0.42, decimals=2, space="sell", optimize=True)
    sell_stochrsi_min = IntParameter(50, 95, default=78, space="sell", optimize=True)
    sell_cci_max = IntParameter(50, 280, default=120, space="sell", optimize=True)
    sell_adx_weakening = CategoricalParameter([True, False], default=True, space="sell", optimize=True)
    sell_cmf_threshold = DecimalParameter(-0.25, 0.0, default=-0.08, decimals=2, space="sell", optimize=True)
    sell_exhaustion_rvol = DecimalParameter(1.5, 4.5, default=2.5, decimals=1, space="sell", optimize=True)
    sell_macd_hist_neg = CategoricalParameter([True, False], default=True, space="sell", optimize=True)
    sell_efficiency_min = DecimalParameter(0.03, 0.35, default=0.12, decimals=2, space="sell", optimize=True)
    sell_wyckoff_utad = CategoricalParameter([True, False], default=True, space="sell", optimize=True)
    sell_elliott_extended = CategoricalParameter([True, False], default=True, space="sell", optimize=True)
    sell_regime_filter = CategoricalParameter([True, False], default=True, space="sell", optimize=True)
    sell_divergence_weight = CategoricalParameter([True, False], default=True, space="sell", optimize=True)

    def informative_pairs(self):
        pairs = self.dp.current_whitelist()
        return [(p, "15m") for p in pairs] + [(p, "1h") for p in pairs]

    # ===================================================================
    # 1. VOLUME FLOW ANALYSIS
    # ===================================================================
    def _calc_volume_flow(self, df: DataFrame) -> DataFrame:
        hl = (df["high"] - df["low"]).replace(0, np.nan)

        df["buy_vol"] = (df["volume"] * (df["close"] - df["low"]) / hl).fillna(df["volume"] * 0.5)
        df["sell_vol"] = (df["volume"] * (df["high"] - df["close"]) / hl).fillna(df["volume"] * 0.5)
        df["vol_delta"] = df["buy_vol"] - df["sell_vol"]
        df["cvd"] = df["vol_delta"].cumsum()
        df["cvd_ema"] = ta.EMA(df["cvd"], timeperiod=20)
        df["cvd_rising"] = (df["cvd"] > df["cvd"].shift(1)).astype(int)

        total = (df["buy_vol"] + df["sell_vol"]).replace(0, 1)
        df["pressure"] = (df["buy_vol"] / total).fillna(0.5)
        df["pressure_sma"] = df["pressure"].rolling(10).mean()

        df["vol_sma20"] = df["volume"].rolling(20).mean()
        df["rvol"] = (df["volume"] / df["vol_sma20"].replace(0, 1)).fillna(1)

        df["obv"] = (np.sign(df["close"].diff()) * df["volume"]).fillna(0).cumsum()
        df["obv_ema"] = ta.EMA(df["obv"], timeperiod=20)
        df["mfi"] = ta.MFI(df, timeperiod=14)

        mf_mult = ((df["close"] - df["low"]) - (df["high"] - df["close"])) / hl
        mf_mult = mf_mult.fillna(0)
        vol_sum = df["volume"].rolling(20).sum().replace(0, 1)
        df["cmf"] = (mf_mult * df["volume"]).rolling(20).sum() / vol_sum

        df["vpt"] = (df["close"].pct_change() * df["volume"]).fillna(0).cumsum()
        df["vpt_ema"] = ta.EMA(df["vpt"], timeperiod=20)
        df["force_idx"] = ta.EMA(df["close"].diff() * df["volume"], timeperiod=13)

        tp = (df["high"] + df["low"] + df["close"]) / 3
        vwap_vol = df["volume"].rolling(50).sum().replace(0, 1)
        df["vwap"] = (tp * df["volume"]).rolling(50).sum() / vwap_vol

        return df

    # ===================================================================
    # 2. MICROSTRUCTURE & SMART MONEY
    # ===================================================================
    def _calc_microstructure(self, df: DataFrame) -> DataFrame:
        hl = (df["high"] - df["low"]).replace(0, np.nan).fillna(0.0001)
        body = (df["close"] - df["open"]).abs()

        df["body_ratio"] = body / hl
        upper_wick = df["high"] - df[["open", "close"]].max(axis=1)
        lower_wick = df[["open", "close"]].min(axis=1) - df["low"]
        df["upper_wick_r"] = upper_wick / hl
        df["lower_wick_r"] = lower_wick / hl

        # Absorption, pin bars, displacement
        df["is_absorption"] = ((df["body_ratio"] < 0.30) & (df["rvol"] > 1.5)).astype(int)
        df["pin_bull"] = ((df["lower_wick_r"] > 0.60) & (df["body_ratio"] < 0.25)).astype(int)
        df["pin_bear"] = ((df["upper_wick_r"] > 0.60) & (df["body_ratio"] < 0.25)).astype(int)
        df["displacement_bull"] = ((df["body_ratio"] > 0.80) & (df["close"] > df["open"]) & (df["rvol"] > 1.2)).astype(int)
        df["displacement_bear"] = ((df["body_ratio"] > 0.80) & (df["close"] < df["open"]) & (df["rvol"] > 1.2)).astype(int)

        # Engulfing
        df["engulf_bull"] = (
            (df["close"].shift(1) < df["open"].shift(1)) &
            (df["close"] > df["open"]) &
            (df["close"] > df["open"].shift(1)) &
            (df["open"] < df["close"].shift(1))
        ).astype(int)
        df["engulf_bear"] = (
            (df["close"].shift(1) > df["open"].shift(1)) &
            (df["close"] < df["open"]) &
            (df["close"] < df["open"].shift(1)) &
            (df["open"] > df["close"].shift(1))
        ).astype(int)

        # FVG, liquidity sweeps
        df["fvg_bull"] = (df["low"] > df["high"].shift(2)).astype(int)
        df["fvg_bear"] = (df["high"] < df["low"].shift(2)).astype(int)
        rec_high = df["high"].rolling(20).max().shift(1)
        rec_low = df["low"].rolling(20).min().shift(1)
        df["sweep_high"] = ((df["high"] > rec_high) & (df["close"] < rec_high)).astype(int)
        df["sweep_low"] = ((df["low"] < rec_low) & (df["close"] > rec_low)).astype(int)

        # Break of Structure (BOS) — proxy
        df["higher_high"] = (df["high"] > df["high"].shift(1)).astype(int)
        df["lower_low"] = (df["low"] < df["low"].shift(1)).astype(int)
        df["bos_bull"] = ((df["higher_high"].rolling(3).sum() >= 3)).astype(int)
        df["bos_bear"] = ((df["lower_low"].rolling(3).sum() >= 3)).astype(int)

        # Volume climax reversals
        df["climax_rev_bear"] = ((df["rvol"].shift(1) > 3.0) & (df["close"].shift(1) > df["open"].shift(1)) & (df["close"] < df["open"])).astype(int)
        df["climax_rev_bull"] = ((df["rvol"].shift(1) > 3.0) & (df["close"].shift(1) < df["open"].shift(1)) & (df["close"] > df["open"])).astype(int)

        # Exhaustion
        df["exhaust_bull"] = ((df["body_ratio"] > 0.70) & (df["rvol"] > 2.0) & (df["close"] > df["open"])).astype(int)
        df["exhaust_bear"] = ((df["body_ratio"] > 0.70) & (df["rvol"] > 2.0) & (df["close"] < df["open"])).astype(int)

        return df

    # ===================================================================
    # 3. WYCKOFF PROXY
    # ===================================================================
    def _calc_wyckoff(self, df: DataFrame) -> DataFrame:
        """
        Wyckoff Accumulation/Distribution detection:
        - Spring: preço cai abaixo do suporte e volta (bull trap reversal)
        - UTAD: preço sobe acima da resistência e volta (bear trap)
        - Effort vs Result: volume alto + pequeno range = absorção
        """
        # Support/Resistance dynamic
        df["support_20"] = df["low"].rolling(20).min()
        df["resist_20"] = df["high"].rolling(20).max()

        # Spring (bullish): preço cai abaixo do suporte recente e fecha acima
        df["spring"] = (
            (df["low"] < df["support_20"].shift(1)) &
            (df["close"] > df["support_20"].shift(1))
        ).astype(int)

        # UTAD (bearish): preço sobe acima da resistência e fecha abaixo
        df["utad"] = (
            (df["high"] > df["resist_20"].shift(1)) &
            (df["close"] < df["resist_20"].shift(1))
        ).astype(int)

        # Effort vs Result: alto volume mas pouco movimento de preço = absorção
        price_range = (df["high"] - df["low"]).replace(0, np.nan).fillna(0.0001)
        range_avg = price_range.rolling(20).mean().replace(0, 1)
        vol_avg = df["volume"].rolling(20).mean().replace(0, 1)

        df["effort_result"] = (df["volume"] / vol_avg) / (price_range / range_avg).replace(0, 1)

        # Acumulação: range apertado + volume crescente
        df["acc_zone"] = (
            (df["bb_width"] < df["bb_width"].rolling(50).quantile(0.3)) &
            (df["volume"] > df["vol_sma20"])
        ).astype(int) if "bb_width" in df.columns else 0

        # Distribution: range apertado no topo + volume crescente
        df["dist_zone"] = (
            (df["close"] > df["close"].rolling(50).quantile(0.8)) &
            (df["bb_width"] < df["bb_width"].rolling(50).quantile(0.3)) &
            (df["volume"] > df["vol_sma20"])
        ).astype(int) if "bb_width" in df.columns else 0

        return df

    # ===================================================================
    # 4. ELLIOTT WAVE PROXY
    # ===================================================================
    def _calc_elliott(self, df: DataFrame) -> DataFrame:
        """
        Elliott Wave proxy computacional:
        - Swing high/low detector
        - Fibonacci retracement zones automáticas
        - Wave 3 detection (a mais poderosa)
        - Impulse vs Corrective classifier
        """
        # Swing High/Low (5 candles each side)
        df["swing_high"] = (
            (df["high"] > df["high"].shift(1)) &
            (df["high"] > df["high"].shift(2)) &
            (df["high"] > df["high"].shift(-1).fillna(0)) &
            (df["high"] > df["high"].shift(-2).fillna(0))
        ).astype(int)
        df["swing_low"] = (
            (df["low"] < df["low"].shift(1)) &
            (df["low"] < df["low"].shift(2)) &
            (df["low"] < df["low"].shift(-1).fillna(df["low"].max())) &
            (df["low"] < df["low"].shift(-2).fillna(df["low"].max()))
        ).astype(int)

        # Rolling highest swing high and lowest swing low
        df["swing_h_val"] = df["high"].where(df["swing_high"] == 1).ffill()
        df["swing_l_val"] = df["low"].where(df["swing_low"] == 1).ffill()

        # Fibonacci retracement levels from last swing range
        swing_range = (df["swing_h_val"] - df["swing_l_val"]).replace(0, np.nan).fillna(1)
        df["fib_382"] = df["swing_h_val"] - swing_range * 0.382
        df["fib_500"] = df["swing_h_val"] - swing_range * 0.500
        df["fib_618"] = df["swing_h_val"] - swing_range * 0.618

        # Price in Fib golden zone (0.382 - 0.618) = wave correction zone
        df["in_fib_zone"] = (
            (df["close"] <= df["fib_382"]) &
            (df["close"] >= df["fib_618"])
        ).astype(int)

        # Impulse detection: strong move with volume
        df["impulse_bull"] = (
            (df["close"] > df["close"].shift(5)) &
            ((df["close"] - df["close"].shift(5)) / df["close"].shift(5).replace(0, 1) > 0.01) &
            (df["rvol"] > 1.3)
        ).astype(int) if "rvol" in df.columns else 0

        df["impulse_bear"] = (
            (df["close"] < df["close"].shift(5)) &
            ((df["close"].shift(5) - df["close"]) / df["close"].shift(5).replace(0, 1) > 0.01) &
            (df["rvol"] > 1.3)
        ).astype(int) if "rvol" in df.columns else 0

        # Wave 3 proxy: biggest impulse after correction
        # Correction = price retraced to fib zone, then new impulse starts
        df["wave3_bull"] = (
            (df["in_fib_zone"].shift(1) == 1) &
            (df["impulse_bull"] == 1) &
            (df["rvol"] > 1.5)
        ).astype(int) if "rvol" in df.columns else 0

        df["wave3_bear"] = (
            (df["in_fib_zone"].shift(1) == 1) &
            (df["impulse_bear"] == 1) &
            (df["rvol"] > 1.5)
        ).astype(int) if "rvol" in df.columns else 0

        # Extended wave 5: price went too far, likely to reverse
        df["extended_bull"] = (
            (df["close"] > df["swing_h_val"]) &
            ((df["close"] - df["swing_l_val"]) > swing_range * 1.618)
        ).astype(int)

        df["extended_bear"] = (
            (df["close"] < df["swing_l_val"]) &
            ((df["swing_h_val"] - df["close"]) > swing_range * 1.618)
        ).astype(int)

        return df

    # ===================================================================
    # 5. INNOVATION INDICATORS
    # ===================================================================
    def _calc_innovation(self, df: DataFrame) -> DataFrame:
        """
        Indicadores inovadores:
        - Kaufman Efficiency, Volatility Ratio, Oscillation Index
        - Trend Acceleration, Composite Momentum
        - Squeeze Momentum (TTM), Volume-Price Divergence
        - Shannon Entropy proxy, Regime detector
        """
        # Kaufman Efficiency Ratio
        period = 20
        direction = (df["close"] - df["close"].shift(period)).abs()
        noise = df["close"].diff().abs().rolling(period).sum()
        df["efficiency"] = (direction / noise.replace(0, 1)).fillna(0)

        # ATR / Volatility
        df["atr"] = ta.ATR(df, timeperiod=14)
        atr_long = ta.ATR(df, timeperiod=50)
        df["vol_ratio"] = (df["atr"] / atr_long.replace(0, 1)).fillna(1)
        df["atr_pct"] = df["atr"] / df["close"] * 100

        # Oscillation Index
        net_10 = (df["close"] - df["close"].shift(10)).abs()
        total_10 = df["close"].diff().abs().rolling(10).sum()
        df["oscillation"] = 1 - (net_10 / total_10.replace(0, 1))

        # Trend Acceleration (2nd derivative)
        vel = df["close"].diff(5)
        df["trend_accel"] = vel.diff(5) / df["atr"].replace(0, 1)

        # Composite Momentum
        roc3 = ta.ROC(df, timeperiod=3).fillna(0)
        roc5 = ta.ROC(df, timeperiod=5).fillna(0)
        roc8 = ta.ROC(df, timeperiod=8).fillna(0)
        roc13 = ta.ROC(df, timeperiod=13).fillna(0)
        roc21 = ta.ROC(df, timeperiod=21).fillna(0)
        df["composite_mom"] = (roc3*5 + roc5*4 + roc8*3 + roc13*2 + roc21*1) / 15

        # Squeeze (BB inside Keltner)
        kelt_ema = ta.EMA(df, timeperiod=20)
        kelt_atr = ta.ATR(df, timeperiod=10)
        bb = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
        df["bb_upper"] = bb["upperband"]
        df["bb_middle"] = bb["middleband"]
        df["bb_lower"] = bb["lowerband"]
        df["bb_width"] = (bb["upperband"] - bb["lowerband"]) / bb["middleband"].replace(0, 1)
        df["bb_pct"] = (df["close"] - bb["lowerband"]) / (bb["upperband"] - bb["lowerband"]).replace(0, 1)

        df["squeeze_on"] = ((bb["lowerband"] > kelt_ema - kelt_atr*1.5) & (bb["upperband"] < kelt_ema + kelt_atr*1.5)).astype(int)
        df["squeeze_fire_bull"] = ((df["squeeze_on"].shift(1) == 1) & (df["squeeze_on"] == 0) & (df["close"] > df["close"].shift(1))).astype(int)
        df["squeeze_fire_bear"] = ((df["squeeze_on"].shift(1) == 1) & (df["squeeze_on"] == 0) & (df["close"] < df["close"].shift(1))).astype(int)

        # Volume-Price Divergence
        price_chg = df["close"].pct_change(10).fillna(0)
        vol_chg = df["volume"].pct_change(10).fillna(0)
        df["vp_divergence"] = price_chg - vol_chg

        # Shannon Entropy proxy (variação nos últimos 20 candles)
        returns = df["close"].pct_change().fillna(0)
        df["volatility_20"] = returns.rolling(20).std().fillna(0)
        # Alta entropia = alta incerteza; Baixa = movimento direcional
        vol_median = df["volatility_20"].rolling(100).median()
        df["low_entropy"] = (df["volatility_20"] < vol_median).astype(int)

        # Regime detector: trending vs ranging
        df["regime_trend"] = ((df["adx"] > 25) & (df["efficiency"] > 0.15)).astype(int) if "adx" in df.columns else 0
        df["regime_range"] = ((df["adx"] < 20) & (df["efficiency"] < 0.10)).astype(int) if "adx" in df.columns else 0

        return df

    # ===================================================================
    # 6. PROPRIETARY OSIRIS COMPOSITES
    # ===================================================================
    def _calc_osiris_proprietary(self, df: DataFrame) -> DataFrame:
        """
        INDICADORES 100% PROPRIETÁRIOS OSIRIS — NÃO EXISTEM NA LITERATURA
        
        V-WMDI: Volume-Weighted Momentum Divergence Index
        IPO: Institutional Pressure Oscillator
        RACS: Regime-Adaptive Confidence Score
        SMFC: Smart Money Footprint Composite
        """

        # === V-WMDI: Volume-Weighted Momentum Divergence Index ===
        # Combina RSI + MACD + CCI ponderados pelo volume relativo
        # Divergência entre momentum e fluxo = edge
        rsi_norm = (df["rsi"] - 50) / 50 if "rsi" in df.columns else 0
        macd_norm = df["macd_hist"] / df["atr"].replace(0, 1) if "macd_hist" in df.columns else 0
        cci_norm = df["cci"] / 200 if "cci" in df.columns else 0

        rvol_weight = df["rvol"].clip(0.5, 3.0) if "rvol" in df.columns else 1
        df["vwmdi"] = (rsi_norm * 0.4 + macd_norm * 0.35 + cci_norm * 0.25) * rvol_weight

        # === IPO: Institutional Pressure Oscillator ===
        # Combina: pressure ratio + CVD direction + effort/result + CMF
        # Institucional entra devagar: volume alto + price pouco = compra silenciosa
        pressure_sig = (df["pressure"] - 0.5) * 2 if "pressure" in df.columns else 0
        cvd_sig = np.sign(df["cvd"] - df["cvd_ema"]) if "cvd" in df.columns else 0
        effort_sig = (df["effort_result"] - 1).clip(-2, 2) if "effort_result" in df.columns else 0
        cmf_sig = df["cmf"] * 5 if "cmf" in df.columns else 0

        df["ipo"] = (pressure_sig * 0.3 + cvd_sig * 0.25 + effort_sig * 0.25 + cmf_sig * 0.2)
        df["ipo_ema"] = ta.EMA(df["ipo"], timeperiod=10)

        # === RACS: Regime-Adaptive Confidence Score ===
        # Em trend: pesa momentum e trend-following
        # Em range: pesa mean-reversion e support/resistance
        is_trend = df.get("regime_trend", 0)
        is_range = df.get("regime_range", 0)

        # Trend score components
        trend_score = (
            (df.get("ema_ribbon", 0) >= 3).astype(int) * 2 +
            (df.get("adx", 0) > 25).astype(int) +
            (df.get("composite_mom", 0) > 0).astype(int)
        )
        # Range score components
        range_score = (
            (df.get("rsi", 50) < 35).astype(int) * 2 +
            (df.get("bb_pct", 0.5) < 0.2).astype(int) +
            (df.get("in_fib_zone", 0) == 1).astype(int)
        )

        # Adaptive: usa score relevante ao regime
        df["racs"] = np.where(
            is_trend == 1, trend_score,
            np.where(is_range == 1, range_score, (trend_score + range_score) / 2)
        )

        # === SMFC: Smart Money Footprint Composite ===
        # Combina todos sinais de smart money num único indicador
        sm_signals = (
            df.get("spring", 0) * 3 +
            df.get("sweep_low", 0) * 2 +
            df.get("climax_rev_bull", 0) * 2 +
            df.get("displacement_bull", 0) * 2 +
            df.get("engulf_bull", 0) * 1 +
            df.get("pin_bull", 0) * 1 +
            df.get("wave3_bull", 0) * 3 +
            df.get("fvg_bull", 0) * 1 +
            df.get("is_absorption", 0) * 1
        )
        df["smfc_bull"] = sm_signals
        df["smfc_bull_sma"] = df["smfc_bull"].rolling(5).mean()

        sm_signals_bear = (
            df.get("utad", 0) * 3 +
            df.get("sweep_high", 0) * 2 +
            df.get("climax_rev_bear", 0) * 2 +
            df.get("displacement_bear", 0) * 2 +
            df.get("engulf_bear", 0) * 1 +
            df.get("pin_bear", 0) * 1 +
            df.get("wave3_bear", 0) * 3 +
            df.get("fvg_bear", 0) * 1 +
            df.get("exhaust_bull", 0) * 2
        )
        df["smfc_bear"] = sm_signals_bear

        return df

    # ===================================================================
    # 7. PATTERN DETECTION — Double Bottom, H&S, Pivots
    # ===================================================================
    def _calc_patterns(self, df: DataFrame) -> DataFrame:
        """
        Padrões gráficos clássicos (proxy computacional):
        - Double Bottom / Double Top
        - Support/Resistance pivot-based
        - Divergência RSI-Price
        """
        # Pivot points (standard daily — 288 candles for 5m)
        period = 288
        df["pivot_high"] = df["high"].rolling(period).max()
        df["pivot_low"] = df["low"].rolling(period).min()
        df["pivot_mid"] = (df["pivot_high"] + df["pivot_low"] + df["close"]) / 3

        # Double bottom proxy: two lows within 1% over last 60 candles
        low_60 = df["low"].rolling(60).min()
        df["near_double_bottom"] = (
            (df["low"] < low_60 * 1.005) &
            (df["low"] > low_60 * 0.995) &
            (df["close"] > df["open"])
        ).astype(int)

        # Double top proxy
        high_60 = df["high"].rolling(60).max()
        df["near_double_top"] = (
            (df["high"] > high_60 * 0.995) &
            (df["high"] < high_60 * 1.005) &
            (df["close"] < df["open"])
        ).astype(int)

        # RSI-Price Divergence Bullish: price lower low + RSI higher low
        df["rsi_div_bull"] = (
            (df["low"] < df["low"].shift(10)) &
            (df["rsi"] > df["rsi"].shift(10)) &
            (df["rsi"] < 40)
        ).astype(int) if "rsi" in df.columns else 0

        # RSI-Price Divergence Bearish: price higher high + RSI lower high
        df["rsi_div_bear"] = (
            (df["high"] > df["high"].shift(10)) &
            (df["rsi"] < df["rsi"].shift(10)) &
            (df["rsi"] > 60)
        ).astype(int) if "rsi" in df.columns else 0

        return df

    # ===================================================================
    # POPULATE INDICATORS
    # ===================================================================
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # --- Classic indicators ---
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
        dataframe["rsi_fast"] = ta.RSI(dataframe, timeperiod=7)
        macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
        dataframe["macd"] = macd["macd"]
        dataframe["macd_signal"] = macd["macdsignal"]
        dataframe["macd_hist"] = macd["macdhist"]

        dataframe["ema_8"] = ta.EMA(dataframe, timeperiod=8)
        dataframe["ema_13"] = ta.EMA(dataframe, timeperiod=13)
        dataframe["ema_21"] = ta.EMA(dataframe, timeperiod=21)
        dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50)
        dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200)
        dataframe["ema_ribbon"] = (
            (dataframe["ema_8"] > dataframe["ema_13"]).astype(int) +
            (dataframe["ema_13"] > dataframe["ema_21"]).astype(int) +
            (dataframe["ema_21"] > dataframe["ema_50"]).astype(int) +
            (dataframe["ema_50"] > dataframe["ema_200"]).astype(int)
        )

        dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
        dataframe["plus_di"] = ta.PLUS_DI(dataframe, timeperiod=14)
        dataframe["minus_di"] = ta.MINUS_DI(dataframe, timeperiod=14)

        stoch = ta.STOCHRSI(dataframe, timeperiod=14, fastk_period=3, fastd_period=3)
        dataframe["stochrsi_k"] = stoch["fastk"]
        dataframe["stochrsi_d"] = stoch["fastd"]
        dataframe["cci"] = ta.CCI(dataframe, timeperiod=14)
        dataframe["willr"] = ta.WILLR(dataframe, timeperiod=14)
        dataframe["roc"] = ta.ROC(dataframe, timeperiod=10)

        # Ichimoku
        dataframe["ichi_tenkan"] = (dataframe["high"].rolling(9).max() + dataframe["low"].rolling(9).min()) / 2
        dataframe["ichi_kijun"] = (dataframe["high"].rolling(26).max() + dataframe["low"].rolling(26).min()) / 2
        dataframe["ichi_bull"] = ((dataframe["close"] > dataframe["ichi_tenkan"]) & (dataframe["close"] > dataframe["ichi_kijun"])).astype(int)

        # Parabolic SAR
        dataframe["sar"] = ta.SAR(dataframe, acceleration=0.02, maximum=0.2)
        dataframe["sar_bull"] = (dataframe["close"] > dataframe["sar"]).astype(int)

        # Aroon
        aroon = ta.AROON(dataframe, timeperiod=25)
        dataframe["aroon_up"] = aroon["aroonup"]
        dataframe["aroon_down"] = aroon["aroondown"]
        dataframe["aroon_bull"] = (dataframe["aroon_up"] > dataframe["aroon_down"]).astype(int)

        # --- Volume Flow ---
        dataframe = self._calc_volume_flow(dataframe)

        # --- Innovation ---
        dataframe = self._calc_innovation(dataframe)

        # --- Microstructure ---
        dataframe = self._calc_microstructure(dataframe)

        # --- Wyckoff ---
        dataframe = self._calc_wyckoff(dataframe)

        # --- Elliott ---
        dataframe = self._calc_elliott(dataframe)

        # --- Patterns ---
        dataframe = self._calc_patterns(dataframe)

        # --- OSIRIS Proprietary Composites ---
        dataframe = self._calc_osiris_proprietary(dataframe)

        # --- Multi-Timeframe ---
        if self.dp:
            inf_15m = self.dp.get_pair_dataframe(pair=metadata["pair"], timeframe="15m")
            if not inf_15m.empty:
                inf_15m["rsi"] = ta.RSI(inf_15m, timeperiod=14)
                inf_15m["ema_50"] = ta.EMA(inf_15m, timeperiod=50)
                inf_15m["ema_200"] = ta.EMA(inf_15m, timeperiod=200)
                inf_15m["adx"] = ta.ADX(inf_15m, timeperiod=14)
                macd_15m = ta.MACD(inf_15m)
                inf_15m["macd_hist"] = macd_15m["macdhist"]
                hl_15 = (inf_15m["high"] - inf_15m["low"]).replace(0, np.nan)
                bv_15 = inf_15m["volume"] * (inf_15m["close"] - inf_15m["low"]) / hl_15
                sv_15 = inf_15m["volume"] * (inf_15m["high"] - inf_15m["close"]) / hl_15
                inf_15m["pressure"] = (bv_15.fillna(inf_15m["volume"]*0.5) / (bv_15.fillna(inf_15m["volume"]*0.5) + sv_15.fillna(inf_15m["volume"]*0.5)).replace(0,1))
                dataframe = merge_informative_pair(dataframe, inf_15m, self.timeframe, "15m", ffill=True)

            inf_1h = self.dp.get_pair_dataframe(pair=metadata["pair"], timeframe="1h")
            if not inf_1h.empty:
                inf_1h["rsi"] = ta.RSI(inf_1h, timeperiod=14)
                inf_1h["ema_50"] = ta.EMA(inf_1h, timeperiod=50)
                inf_1h["ema_200"] = ta.EMA(inf_1h, timeperiod=200)
                inf_1h["adx"] = ta.ADX(inf_1h, timeperiod=14)
                dataframe = merge_informative_pair(dataframe, inf_1h, self.timeframe, "1h", ffill=True)

        return dataframe

    # ===================================================================
    # CONFLUENCE SCORE — ENTRY (max 30 pontos)
    # ===================================================================
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        s = np.zeros(len(dataframe))

        # ── VOLUME FLOW (max 5) ─────────────────────────────
        s += (dataframe["pressure"] > self.buy_pressure_min.value).astype(int)
        s += (dataframe["rvol"] > self.buy_rvol_min.value).astype(int)
        cvd_count = dataframe["cvd_rising"].rolling(self.buy_cvd_periods.value).sum()
        s += (cvd_count >= self.buy_cvd_periods.value).astype(int)
        s += (dataframe["mfi"] > self.buy_mfi_min.value).astype(int)
        s += (dataframe["cmf"] > 0).astype(int)

        # ── MOMENTUM (max 5) ───────────────────────────────
        s += ((dataframe["rsi"] > self.buy_rsi_min.value) & (dataframe["rsi"] < self.buy_rsi_max.value)).astype(int)
        s += ((dataframe["macd_hist"] > 0) | (dataframe["macd_hist"] > dataframe["macd_hist"].shift(1))).astype(int)
        s += (dataframe["stochrsi_k"] < self.buy_stochrsi_max.value).astype(int)
        s += ((dataframe["cci"] > self.buy_cci_min.value) & (dataframe["cci"].shift(1) <= self.buy_cci_min.value)).astype(int)
        s += (dataframe["composite_mom"] > 0).astype(int)

        # ── TREND (max 4) ──────────────────────────────────
        s += (dataframe["adx"] > self.buy_adx_min.value).astype(int)
        s += (dataframe["ema_ribbon"] >= self.buy_ema_ribbon_min.value).astype(int)
        s += (dataframe["close"] > dataframe["vwap"]).astype(int)
        s += (dataframe["sar_bull"] == 1).astype(int)

        # ── WYCKOFF / SMART MONEY (max 4) ──────────────────
        s += (dataframe["spring"] == 1).astype(int) * 2  # Spring = 2 pontos
        s += (dataframe["effort_result"] > self.buy_effort_result_min.value).astype(int)
        s += (dataframe["acc_zone"] == 1).astype(int)

        # ── ELLIOTT (max 4) ────────────────────────────────
        s += (dataframe["in_fib_zone"] == 1).astype(int)
        s += (dataframe["wave3_bull"] == 1).astype(int) * 2  # Wave 3 = 2 pontos
        s += (dataframe["impulse_bull"] == 1).astype(int)

        # ── MICROSTRUCTURE (max 4) ─────────────────────────
        s += (dataframe["is_absorption"].rolling(3).sum() > 0).astype(int)
        s += ((dataframe["pin_bull"] == 1) | (dataframe["sweep_low"] == 1)).astype(int)
        s += (dataframe["squeeze_fire_bull"] == 1).astype(int)
        s += (dataframe["engulf_bull"] == 1).astype(int)

        # ── PATTERNS (max 2) ──────────────────────────────
        s += (dataframe["near_double_bottom"] == 1).astype(int)
        s += (dataframe["rsi_div_bull"] == 1).astype(int)

        # ── OSIRIS PROPRIETARY (max 3) ────────────────────
        s += (dataframe["vwmdi"] > 0.3).astype(int)
        s += (dataframe["ipo"] > 0.2).astype(int)
        s += (dataframe["smfc_bull"] >= 3).astype(int)

        # ── MULTI-TF (max 2) ─────────────────────────────
        rsi_15 = "rsi_15m"
        if rsi_15 in dataframe.columns:
            s += (dataframe[rsi_15] > self.buy_mtf_rsi_15m_min.value).astype(int)
        ema50_15 = "ema_50_15m"
        ema50_1h = "ema_50_1h"
        if self.buy_mtf_trend_aligned.value and ema50_15 in dataframe.columns and ema50_1h in dataframe.columns:
            s += ((dataframe["close"] > dataframe[ema50_15]) & (dataframe["close"] > dataframe[ema50_1h])).astype(int)

        # ── REGIME FILTER ─────────────────────────────────
        if self.buy_use_regime_filter.value:
            s += (dataframe["racs"] >= 2).astype(int)

        # === ENTRY ===
        dataframe.loc[
            (s >= self.buy_score_threshold.value) & (dataframe["volume"] > 0),
            "enter_long"
        ] = 1

        return dataframe

    # ===================================================================
    # CONFLUENCE SCORE — EXIT (max 20 pontos)
    # ===================================================================
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        s = np.zeros(len(dataframe))

        # ── VOLUME FLOW (max 3) ───────────────
        s += (dataframe["pressure"] < self.sell_pressure_max.value).astype(int)
        s += (dataframe["cvd"] < dataframe["cvd_ema"]).astype(int)
        s += (dataframe["cmf"] < self.sell_cmf_threshold.value).astype(int)

        # ── MOMENTUM (max 3) ──────────────────
        s += (dataframe["rsi"] > self.sell_rsi_max.value).astype(int)
        s += (dataframe["stochrsi_k"] > self.sell_stochrsi_min.value).astype(int)
        s += (dataframe["cci"] > self.sell_cci_max.value).astype(int)

        # ── TREND WEAKENING (max 2) ──────────
        if self.sell_macd_hist_neg.value:
            s += ((dataframe["macd_hist"] < 0) & (dataframe["macd_hist"] < dataframe["macd_hist"].shift(1))).astype(int)
        if self.sell_adx_weakening.value:
            s += (dataframe["adx"] < dataframe["adx"].shift(1)).astype(int)

        # ── MICROSTRUCTURE (max 3) ───────────
        s += ((dataframe["exhaust_bull"] == 1) & (dataframe["rvol"] > self.sell_exhaustion_rvol.value)).astype(int)
        s += ((dataframe["displacement_bear"] == 1) | (dataframe["sweep_high"] == 1)).astype(int)
        s += (dataframe["engulf_bear"] == 1).astype(int)

        # ── WYCKOFF (max 2) ──────────────────
        if self.sell_wyckoff_utad.value:
            s += (dataframe["utad"] == 1).astype(int) * 2
        s += (dataframe["dist_zone"] == 1).astype(int)

        # ── ELLIOTT (max 2) ──────────────────
        if self.sell_elliott_extended.value:
            s += (dataframe["extended_bull"] == 1).astype(int)
        s += (dataframe["wave3_bear"] == 1).astype(int)

        # ── PATTERNS (max 2) ─────────────────
        s += (dataframe["near_double_top"] == 1).astype(int)
        if self.sell_divergence_weight.value:
            s += (dataframe["rsi_div_bear"] == 1).astype(int)

        # ── OSIRIS PROPRIETARY (max 2) ───────
        s += (dataframe["vwmdi"] < -0.3).astype(int)
        s += (dataframe["smfc_bear"] >= 3).astype(int)

        # ── REGIME ───────────────────────────
        if self.sell_regime_filter.value:
            s += (dataframe["efficiency"] < self.sell_efficiency_min.value).astype(int)

        # === EXIT ===
        dataframe.loc[
            (s >= self.sell_score_threshold.value) & (dataframe["volume"] > 0),
            "exit_long"
        ] = 1

        return dataframe

    # ===================================================================
    # DYNAMIC STOPLOSS
    # ===================================================================
    def custom_stoploss(self, pair, trade: Trade, current_time, current_rate, current_profit, after_fill, **kwargs) -> Optional[float]:
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if dataframe is not None and len(dataframe) > 0:
            atr_pct = dataframe.iloc[-1].get("atr_pct", 0)
            if atr_pct > 0:
                sl = -(atr_pct / 100) * 2.5
                return max(sl, -0.05)
        return None
