# source: https://raw.githubusercontent.com/DOUGLASGUEDESATRIA/OSIRIS_TRADE/fe3c09567f3798d1cad6d7a306fa4f6131d91e17/user_data/strategies/OsirisPhantomStrategy.py
"""
OSIRIS PHANTOM STRATEGY v1.0 - Ultra-Precision Scalping System
================================================================
10 Proprietary Algorithms | Strict 3:1 R:R | 10 Trades/Day Target
Whale Detection | Entropy Analysis | Fractal Breakouts | Smart Money

PROPRIETARY ALGORITHMS (100% original):
1.  Whale Shadow Index (WSI) - Institutional activity detection via volume/price anomalies
2.  Entropy Flow Oscillator (EFO) - Market order/disorder measurement
3.  Quantum Momentum Fusion (QMF) - Multi-TF adaptive-weighted momentum
4.  Adaptive Pressure Matrix (APM) - Candle anatomy buy/sell pressure decomposition
5.  Fractal Breakout Detector (FBD) - Williams fractal breakout with volume confirmation
6.  Volume DNA Helix (VDH) - Dual volume stream divergence/convergence analysis
7.  Micro Regime Classifier (MRC) - Dynamic 5-state market regime detection
8.  Liquidity Void Scanner (LVS) - Price gap/void identification and proximity
9.  Smart Money Divergence (SMD) - Institutional vs retail flow divergence
10. Temporal Cascade Index (TCI) - Multi-timeframe momentum cascade scoring

RISK MANAGEMENT:
- Strict 3:1 Risk:Reward ratio enforced on EVERY trade (custom_exit)
- Progressive trailing: Break-even at 1R, lock profit at trail_rr, tight trail at 2.5R
- Time-based exit to prevent capital lockup
- All managed via buy_risk_pct parameter (hyperopt tunable)

100% proprietario. Desenvolvido exclusivamente para OSIRIS.
"""

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__OsirisPhantomStrategy__20260329_060843(IStrategy):
    """
    OSIRIS PHANTOM - Ultra-Precision Scalper with 10 Proprietary Algorithms.

    Combines 10 never-before-seen indicators + standard TA in a score-based
    confluence system. Enforces strict 3:1 Risk:Reward on every trade.
    Designed for high-frequency scalping: 10+ trades per day across 5 pairs.

    Hardware target: i9 + 128GB RAM + RTX 4090
    """

    INTERFACE_VERSION = 3
    can_short = False  # Spot trading only

    timeframe = "5m"

    # ROI — optimized by Hyperopt
    minimal_roi = {
        "0": 0.05,
        "20": 0.03,
        "60": 0.02,
        "120": 0.01,
    }

    stoploss = -0.025
    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 PARAMETERS - BUY (14 parameters)
    # ===================================================================

    # Core: minimum confluence score to enter (max possible ~22)
    buy_score_min = IntParameter(4, 18, default=8, space="buy", optimize=True)

    # Whale Shadow Index threshold
    buy_wsi_min = DecimalParameter(
        0.5, 3.0, default=1.5, decimals=1, space="buy", optimize=True
    )

    # Entropy Flow — max normalized entropy for entry (lower = more trending)
    buy_efo_max = DecimalParameter(
        0.3, 2.0, default=1.0, decimals=1, space="buy", optimize=True
    )

    # Adaptive Pressure Matrix minimum ratio
    buy_apm_min = DecimalParameter(
        1.0, 2.5, default=1.2, decimals=1, space="buy", optimize=True
    )

    # Volume DNA Helix minimum divergence
    buy_vdh_min = DecimalParameter(
        -0.1, 0.5, default=0.1, decimals=2, space="buy", optimize=True
    )

    # Smart Money Divergence minimum
    buy_smd_min = DecimalParameter(
        -2.0, 5.0, default=0.5, decimals=1, space="buy", optimize=True
    )

    # RSI zone boundaries
    buy_rsi_min = IntParameter(20, 50, default=30, space="buy", optimize=True)
    buy_rsi_max = IntParameter(55, 80, default=70, space="buy", optimize=True)

    # ADX trend strength minimum
    buy_adx_min = IntParameter(10, 35, default=20, space="buy", optimize=True)

    # Temporal Cascade minimum alignment score
    buy_tci_min = IntParameter(1, 3, default=2, space="buy", optimize=True)

    # EMA trend alignment required
    buy_ema_aligned = CategoricalParameter(
        [True, False], default=True, space="buy", optimize=True
    )

    # Allow trading in volatile regimes
    buy_allow_volatile = CategoricalParameter(
        [True, False], default=False, space="buy", optimize=True
    )

    # Allow trading in quiet regimes
    buy_allow_quiet = CategoricalParameter(
        [True, False], default=False, space="buy", optimize=True
    )

    # Max trade duration in hours before time exit
    buy_max_hours = IntParameter(4, 24, default=12, space="buy", optimize=True)

    # ===================================================================
    # HYPEROPT PARAMETERS - SELL (2 parameters)
    # ===================================================================

    # Exit signal score threshold (max 12)
    sell_score_min = IntParameter(3, 10, default=6, space="sell", optimize=True)

    # RSI overbought level for exit signal
    sell_rsi_max = IntParameter(60, 90, default=75, space="sell", optimize=True)

    # ===================================================================
    # INFORMATIVE PAIRS
    # ===================================================================

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

    # ===================================================================
    # ALGORITHM 1: WHALE SHADOW INDEX (WSI)
    # Detection of institutional/whale activity via volume-price anomalies
    # ===================================================================

    def _calc_whale_shadow(self, df: DataFrame) -> DataFrame:
        vol_mean = df["volume"].rolling(50).mean()
        vol_std = df["volume"].rolling(50).std().replace(0, 1)
        vol_zscore = (df["volume"] - vol_mean) / vol_std

        pct_change = df["close"].pct_change()
        pct_mean = pct_change.rolling(50).mean()
        pct_std = pct_change.rolling(50).std().replace(0, 0.0001)
        price_zscore = (pct_change - pct_mean) / pct_std

        # WSI: volume anomaly relative to price anomaly
        df["wsi"] = vol_zscore / np.maximum(np.abs(price_zscore), 0.1)
        df["wsi_smooth"] = ta.EMA(df["wsi"], timeperiod=5)

        # Whale absorption on dip (buying the dip silently)
        df["whale_buy"] = (
            (df["wsi_smooth"] > 1.5) & (df["close"] < df["open"])
        ).astype(int)

        # Whale distribution on rally
        df["whale_sell"] = (
            (df["wsi_smooth"] > 1.5) & (df["close"] > df["open"])
        ).astype(int)

        return df

    # ===================================================================
    # ALGORITHM 2: ENTROPY FLOW OSCILLATOR (EFO)
    # Market order/disorder measurement via statistical entropy proxies
    # ===================================================================

    def _calc_entropy_flow(self, df: DataFrame) -> DataFrame:
        abs_returns = df["close"].pct_change().abs()

        rolling_mean = abs_returns.rolling(20).mean().replace(0, 0.0001)
        rolling_std = abs_returns.rolling(20).std()
        df["efo"] = rolling_std / rolling_mean

        efo_long_mean = df["efo"].rolling(100).mean().replace(0, 1)
        df["efo_norm"] = df["efo"] / efo_long_mean

        # Direction clarity: how unidirectional are recent moves
        signed_returns = df["close"].pct_change()
        pos_count = (signed_returns > 0).rolling(20).sum()
        neg_count = (signed_returns < 0).rolling(20).sum()
        df["direction_clarity"] = (pos_count - neg_count).abs() / 20

        return df

    # ===================================================================
    # ALGORITHM 3: QUANTUM MOMENTUM FUSION (QMF)
    # Multi-timeframe momentum with adaptive signal-strength weighting
    # ===================================================================

    def _calc_quantum_momentum(self, df: DataFrame) -> DataFrame:
        mom_5m = df["rsi"].fillna(50).values - 50

        if "rsi_15m" in df.columns:
            mom_15m = df["rsi_15m"].fillna(50).values - 50
        else:
            mom_15m = np.zeros(len(df))

        if "rsi_1h" in df.columns:
            mom_1h = df["rsi_1h"].fillna(50).values - 50
        else:
            mom_1h = np.zeros(len(df))

        # Weighted fusion: stronger signals contribute more
        total_weight = np.abs(mom_5m) + np.abs(mom_15m) + np.abs(mom_1h)
        total_weight = np.where(total_weight == 0, 1, total_weight)

        qmf = (
            mom_5m * np.abs(mom_5m)
            + mom_15m * np.abs(mom_15m)
            + mom_1h * np.abs(mom_1h)
        ) / total_weight
        df["qmf"] = qmf
        df["qmf_smooth"] = ta.EMA(df["qmf"], timeperiod=5)

        return df

    # ===================================================================
    # ALGORITHM 4: ADAPTIVE PRESSURE MATRIX (APM)
    # Candle anatomy decomposition into buy/sell pressure with volume
    # ===================================================================

    def _calc_pressure_matrix(self, df: DataFrame) -> DataFrame:
        hl_range = (df["high"] - df["low"]).replace(0, 0.0001)
        body = df["close"] - df["open"]

        lower_wick = df[["open", "close"]].min(axis=1) - df["low"]
        upper_wick = df["high"] - df[["open", "close"]].max(axis=1)

        # Buy pressure: lower wick support + bullish body demand
        buy_raw = (lower_wick + np.maximum(body, 0)) / hl_range
        # Sell pressure: upper wick resistance + bearish body supply
        sell_raw = (upper_wick + np.maximum(-body, 0)) / hl_range

        buy_pressure_vol = buy_raw * df["volume"]
        sell_pressure_vol = sell_raw * df["volume"]

        buy_ema = ta.EMA(buy_pressure_vol, timeperiod=10)
        sell_ema = ta.EMA(sell_pressure_vol, timeperiod=10)
        df["apm"] = buy_ema / np.maximum(sell_ema, 0.0001)

        df["apm_momentum"] = df["apm"] - df["apm"].shift(5)

        return df

    # ===================================================================
    # ALGORITHM 5: FRACTAL BREAKOUT DETECTOR (FBD)
    # Williams Fractal levels with volume-confirmed breakout signals
    # ===================================================================

    def _calc_fractal_breakout(self, df: DataFrame) -> DataFrame:
        # Fractal highs/lows (2-bar delay to avoid look-ahead)
        fractal_high = (
            (df["high"].shift(2) > df["high"].shift(3))
            & (df["high"].shift(2) > df["high"].shift(4))
            & (df["high"].shift(2) > df["high"].shift(1))
            & (df["high"].shift(2) > df["high"])
        )

        fractal_low = (
            (df["low"].shift(2) < df["low"].shift(3))
            & (df["low"].shift(2) < df["low"].shift(4))
            & (df["low"].shift(2) < df["low"].shift(1))
            & (df["low"].shift(2) < df["low"])
        )

        df["fractal_high"] = df["high"].shift(2).where(fractal_high).ffill()
        df["fractal_low"] = df["low"].shift(2).where(fractal_low).ffill()

        # Breakout: close crosses above fractal high
        df["fbd_bull"] = (
            (df["close"] > df["fractal_high"])
            & (df["close"].shift(1) <= df["fractal_high"].shift(1))
        ).astype(int)

        df["fbd_bear"] = (
            (df["close"] < df["fractal_low"])
            & (df["close"].shift(1) >= df["fractal_low"].shift(1))
        ).astype(int)

        return df

    # ===================================================================
    # ALGORITHM 6: VOLUME DNA HELIX (VDH)
    # Dual up/down volume streams — divergence = tension, convergence = breakout
    # ===================================================================

    def _calc_volume_dna(self, df: DataFrame) -> DataFrame:
        up_volume = df["volume"].where(df["close"] > df["open"], 0)
        down_volume = df["volume"].where(df["close"] <= df["open"], 0)

        up_strand = ta.EMA(up_volume, timeperiod=14)
        down_strand = ta.EMA(down_volume, timeperiod=14)

        total = np.maximum(up_strand + down_strand, 0.0001)
        df["vdh"] = (up_strand - down_strand) / total

        # Acceleration of divergence
        df["vdh_accel"] = df["vdh"] - df["vdh"].shift(3)

        return df

    # ===================================================================
    # ALGORITHM 7: MICRO REGIME CLASSIFIER (MRC)
    # 5-state market regime: Quiet, Trending Up/Down, Volatile, Ranging
    # ===================================================================

    def _calc_regime(self, df: DataFrame) -> DataFrame:
        ema_fast = ta.EMA(df["close"], timeperiod=8)
        ema_slow = ta.EMA(df["close"], timeperiod=21)

        bb = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
        bb_width = (bb["upperband"] - bb["lowerband"]) / bb["middleband"].replace(0, 1)
        bb_width_pctile = bb_width.rolling(100).rank(pct=True)

        atr_pct = df["atr"] / df["close"]
        atr_pctile = atr_pct.rolling(100).rank(pct=True)

        # 0=quiet, 1=trending_up, 2=trending_down, 3=volatile, 4=ranging
        conditions = [
            (df["adx"] > 25) & (ema_fast > ema_slow),
            (df["adx"] > 25) & (ema_fast < ema_slow),
            bb_width_pctile > 0.80,
            (df["adx"] < 18) & (atr_pctile < 0.30),
        ]
        choices = [1, 2, 3, 0]
        df["regime"] = np.select(conditions, choices, default=4)

        return df

    # ===================================================================
    # ALGORITHM 8: LIQUIDITY VOID SCANNER (LVS)
    # Detects price gaps from fast moves — these act as S/R levels
    # ===================================================================

    def _calc_liquidity_voids(self, df: DataFrame) -> DataFrame:
        body_abs = (df["close"] - df["open"]).abs()
        avg_body = body_abs.rolling(50).mean()

        large_bull = (df["close"] > df["open"]) & (body_abs > 2 * avg_body)
        large_bear = (df["close"] < df["open"]) & (body_abs > 2 * avg_body)

        df["void_support"] = df["open"].where(large_bull).ffill()
        df["void_resistance"] = df["open"].where(large_bear).ffill()

        df["dist_void_support"] = (
            (df["close"] - df["void_support"]) / df["close"] * 100
        )

        # Near void support = potential bounce zone
        df["near_void_support"] = (
            (df["dist_void_support"] > 0) & (df["dist_void_support"] < 0.5)
        ).astype(int)

        return df

    # ===================================================================
    # ALGORITHM 9: SMART MONEY DIVERGENCE (SMD)
    # Compares institutional flow (OBV) vs retail indicators (RSI)
    # ===================================================================

    def _calc_smart_money_div(self, df: DataFrame) -> DataFrame:
        rsi_slope = df["rsi"] - df["rsi"].shift(10)

        df["_obv"] = (np.sign(df["close"].diff()) * df["volume"]).fillna(0).cumsum()
        df["_obv_ema"] = ta.EMA(df["_obv"], timeperiod=14)
        obv_slope = (
            (df["_obv_ema"] - df["_obv_ema"].shift(10))
            / df["_obv_ema"].shift(10).replace(0, 1)
            * 100
        )

        # Positive SMD = institutions accumulating (bullish)
        # Negative SMD = institutions distributing (bearish)
        df["smd"] = obv_slope - rsi_slope
        df["smd_smooth"] = ta.EMA(df["smd"], timeperiod=5)

        df["smart_accumulation"] = (
            (df["smd_smooth"] > 2) & (df["rsi"] < 50)
        ).astype(int)
        df["smart_distribution"] = (
            (df["smd_smooth"] < -2) & (df["rsi"] > 50)
        ).astype(int)

        df.drop(columns=["_obv", "_obv_ema"], inplace=True, errors="ignore")

        return df

    # ===================================================================
    # ALGORITHM 10: TEMPORAL CASCADE INDEX (TCI)
    # Multi-timeframe momentum cascade (-3 to +3 alignment score)
    # ===================================================================

    def _calc_temporal_cascade(self, df: DataFrame) -> DataFrame:
        mom_5m = ta.MOM(df["close"], timeperiod=10)
        sign_5m = np.sign(mom_5m)

        if "close_15m" in df.columns:
            # shift(3) on 5m-resampled 15m data = 1 period on 15m
            mom_15m_raw = df["close_15m"] - df["close_15m"].shift(3)
            sign_15m = np.sign(mom_15m_raw)
        else:
            sign_15m = np.zeros(len(df))

        if "close_1h" in df.columns:
            # shift(12) on 5m-resampled 1h data = 1 period on 1h
            mom_1h_raw = df["close_1h"] - df["close_1h"].shift(12)
            sign_1h = np.sign(mom_1h_raw)
        else:
            sign_1h = np.zeros(len(df))

        # Cascade score: -3 (all bearish) to +3 (all bullish)
        df["tci"] = sign_5m + sign_15m + sign_1h

        return df

    # ===================================================================
    # STANDARD TECHNICAL INDICATORS
    # ===================================================================

    def _calc_standard(self, df: DataFrame) -> DataFrame:
        df["rsi"] = ta.RSI(df, timeperiod=14)

        macd = ta.MACD(df, fastperiod=12, slowperiod=26, signalperiod=9)
        df["macd"] = macd["macd"]
        df["macd_signal"] = macd["macdsignal"]
        df["macd_hist"] = macd["macdhist"]

        df["ema_8"] = ta.EMA(df, timeperiod=8)
        df["ema_21"] = ta.EMA(df, timeperiod=21)
        df["ema_50"] = ta.EMA(df, timeperiod=50)
        df["ema_200"] = ta.EMA(df, timeperiod=200)

        df["ema_aligned_bull"] = (
            (df["ema_8"] > df["ema_21"]) & (df["ema_21"] > df["ema_50"])
        ).astype(int)

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

        df["atr"] = ta.ATR(df, timeperiod=14)

        stoch = ta.STOCHRSI(df, timeperiod=14, fastk_period=3, fastd_period=3)
        df["stochrsi_k"] = stoch["fastk"]

        df["mfi"] = ta.MFI(df, timeperiod=14)

        # Rolling VWAP
        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

    # ===================================================================
    # POPULATE INDICATORS (computed once, cached by Freqtrade)
    # ===================================================================

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Standard TA first (RSI, ADX, ATR needed by proprietary algos)
        dataframe = self._calc_standard(dataframe)

        # Multi-timeframe informative data
        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_21"] = ta.EMA(inf_15m, timeperiod=21)
                inf_15m["ema_50"] = ta.EMA(inf_15m, timeperiod=50)
                inf_15m["adx"] = ta.ADX(inf_15m, timeperiod=14)
                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_21"] = ta.EMA(inf_1h, timeperiod=21)
                inf_1h["ema_50"] = ta.EMA(inf_1h, timeperiod=50)
                inf_1h["adx"] = ta.ADX(inf_1h, timeperiod=14)
                dataframe = merge_informative_pair(
                    dataframe, inf_1h, self.timeframe, "1h", ffill=True
                )

        # 10 Proprietary Algorithms
        dataframe = self._calc_whale_shadow(dataframe)
        dataframe = self._calc_entropy_flow(dataframe)
        dataframe = self._calc_pressure_matrix(dataframe)
        dataframe = self._calc_fractal_breakout(dataframe)
        dataframe = self._calc_volume_dna(dataframe)
        dataframe = self._calc_regime(dataframe)
        dataframe = self._calc_liquidity_voids(dataframe)
        dataframe = self._calc_smart_money_div(dataframe)
        dataframe = self._calc_temporal_cascade(dataframe)
        dataframe = self._calc_quantum_momentum(dataframe)  # Needs merged MTF data

        return dataframe

    # ===================================================================
    # ENTRY SCORING SYSTEM (max ~22 pts)
    # ===================================================================

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        score = np.zeros(len(dataframe))

        # 1. WHALE SHADOW: absorption on bearish candle (2 pts)
        score += np.where(
            (dataframe["wsi_smooth"] > self.buy_wsi_min.value)
            & (dataframe["close"] < dataframe["open"]),
            2, 0,
        )

        # 2. ENTROPY: low entropy = trending market (1 pt)
        score += np.where(dataframe["efo_norm"] < self.buy_efo_max.value, 1, 0)

        # 3. QUANTUM MOMENTUM: positive multi-TF fusion (2 pts)
        score += np.where(dataframe["qmf_smooth"] > 0, 2, 0)

        # 4. PRESSURE MATRIX: buy pressure dominating (2 pts)
        score += np.where(dataframe["apm"] > self.buy_apm_min.value, 2, 0)

        # 5. FRACTAL BREAKOUT: bullish fractal break (3 pts — strong signal)
        score += np.where(dataframe["fbd_bull"] == 1, 3, 0)

        # 6. VOLUME DNA: up-strand dominating (1 pt)
        score += np.where(dataframe["vdh"] > self.buy_vdh_min.value, 1, 0)

        # 7. REGIME: favorable market state (2 pts)
        regime_ok = dataframe["regime"] == 1  # trending up always ok
        if self.buy_allow_volatile.value:
            regime_ok = regime_ok | (dataframe["regime"] == 3)
        if self.buy_allow_quiet.value:
            regime_ok = regime_ok | (dataframe["regime"] == 0)
        score += np.where(regime_ok, 2, 0)

        # 8. LIQUIDITY VOID: near support void (1 pt)
        score += np.where(dataframe["near_void_support"] == 1, 1, 0)

        # 9. SMART MONEY: institutional accumulation (2 pts)
        score += np.where(
            dataframe["smd_smooth"] > self.buy_smd_min.value, 2, 0
        )

        # 10. TEMPORAL CASCADE: multi-TF aligned bullish (2 pts)
        score += np.where(dataframe["tci"] >= self.buy_tci_min.value, 2, 0)

        # BONUS: Standard TA confluence
        # RSI in buy zone (1 pt)
        score += np.where(
            (dataframe["rsi"] > self.buy_rsi_min.value)
            & (dataframe["rsi"] < self.buy_rsi_max.value),
            1, 0,
        )

        # ADX showing trend (1 pt)
        score += np.where(dataframe["adx"] > self.buy_adx_min.value, 1, 0)

        # EMA alignment (conditional, 1 pt)
        if self.buy_ema_aligned.value:
            score += np.where(dataframe["ema_aligned_bull"] == 1, 1, 0)

        # Price above VWAP (1 pt)
        score += np.where(dataframe["close"] > dataframe["vwap"], 1, 0)

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

        return dataframe

    # ===================================================================
    # EXIT SCORING SYSTEM (max 12 pts) — supplementary to 3:1 R:R
    # ===================================================================

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        score = np.zeros(len(dataframe))

        # 1. Whale distribution detected (2 pts)
        score += np.where(dataframe["whale_sell"] == 1, 2, 0)

        # 2. High entropy — market becoming chaotic (1 pt)
        score += np.where(dataframe["efo_norm"] > 1.5, 1, 0)

        # 3. Sell pressure dominating (2 pts)
        score += np.where(dataframe["apm"] < 0.8, 2, 0)

        # 4. RSI overbought (1 pt)
        score += np.where(dataframe["rsi"] > self.sell_rsi_max.value, 1, 0)

        # 5. Bearish fractal breakout (2 pts)
        score += np.where(dataframe["fbd_bear"] == 1, 2, 0)

        # 6. Volume DNA bearish (1 pt)
        score += np.where(dataframe["vdh"] < -0.2, 1, 0)

        # 7. Smart money distributing (2 pts)
        score += np.where(dataframe["smart_distribution"] == 1, 2, 0)

        # 8. Temporal cascade bearish (1 pt)
        score += np.where(dataframe["tci"] <= -2, 1, 0)

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

        return dataframe

    # ===================================================================
    # CUSTOM EXIT — Time-Based Management
    # ===================================================================

    def custom_exit(
        self,
        pair: str,
        trade: Trade,
        current_time,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ) -> Optional[str]:
        trade_duration_hours = (
            (current_time - trade.open_date_utc).total_seconds() / 3600
        )
        max_hours = self.buy_max_hours.value

        # After max_hours: exit if in profit
        if trade_duration_hours > max_hours and current_profit > 0.001:
            return "time_exit_profit"

        # Hard limit: 2× max_hours — close regardless
        if trade_duration_hours > max_hours * 2:
            return "time_exit_max"

        return None
