# source: https://raw.githubusercontent.com/kalebCard/MemeHunter/3fc6cb2e1a7fbb8cca56e6c66a0da411deab1027/user_data/strategies/MemeHunterFuturesV7_Quantum.py
# directory_url: https://github.com/kalebCard/MemeHunter/blob/main/user_data/strategies/
# User: kalebCard
# Repository: MemeHunter
# --------------------"""
Github_kalebCard_MemeHunter__MemeHunterFuturesV7_Quantum__20261013_014419.py - Estrategia Cuantitativa de Frontera (PB-74).

Integra los avances teóricos y empíricos del proyecto:
1. Motor Alfa Asimétrico Short-Only en 4H (desinflación macro y liquidación post-pump).
2. Filtro de Memoria Estacionaria FFD (Diferenciación Fraccionaria d=0.4) para confirmar tendencia sin pérdida de memoria.
3. Dimensionamiento Continuo de Apuestas (Bet Sizing de Marcos López de Prado / Kelly Sigmoidal) en leverage():
   m = 2 * Z[ (p - 0.5) / sqrt(p * (1 - p)) ] - 1 -> Escala continua [1.0x - 2.0x].
4. Hardening Institucional de Producción:
   - Kill-Switch de equidad total (Drawdown >= 25%).
   - Guardián de Funding Bleed (< -0.05% por 8h).
   - Salidas Técnicas como Airbag ante quiebre de estructura y Trailing Stop de Alta Fidelidad (12% / 4%).
"""

import math
from datetime import datetime
from typing import Optional
import logging
from pathlib import Path

try:
    from freqtrade.strategy import IStrategy
    from freqtrade.persistence import Trade
except ImportError:
    class IStrategy:
        pass
    class Trade:
        pass
try:
    import pandas_ta as ta
except ImportError:
    ta = None
from pandas import DataFrame
import numpy as np
import scipy.stats as ss

LOG = logging.getLogger(__name__)


def calculate_ffd_approx(close_series: np.ndarray, d: float = 0.40, lags: int = 30) -> np.ndarray:
    """Aproximación vectorizada de FFD para generación de indicadores en tiempo real."""
    weights = [1.0]
    for k in range(1, lags):
        w_k = -weights[-1] / k * (d - k + 1)
        weights.append(w_k)
    weights = np.array(weights[::-1])
    
    n = len(close_series)
    ffd = np.full(n, np.nan)
    for i in range(lags - 1, n):
        ffd[i] = np.dot(weights, close_series[i - lags + 1 : i + 1])
    return ffd


def continuous_leverage_calc(conviction_score: float, min_lev: float = 1.0, max_lev: float = 2.0) -> float:
    """
    Traduce la convicción estocástica [0.0, 1.0] a apalancamiento continuo
    mediante la fórmula sigmoidal normal de López de Prado.
    """
    # Mapear score de 0.0-1.0 a probabilidad p en [0.50, 0.95]
    p = 0.50 + 0.45 * min(1.0, max(0.0, conviction_score))
    if p <= 0.51:
        return min_lev
    
    denom = math.sqrt(p * (1.0 - p))
    z = (p - 0.5) / denom
    m = 2.0 * float(ss.norm.cdf(z)) - 1.0  # m in [0, 1]
    
    lev = min_lev + m * (max_lev - min_lev)
    return round(float(lev), 2)


class Github_kalebCard_MemeHunter__MemeHunterFuturesV7_Quantum__20261013_014419(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = '4h'
    can_short = True

    minimal_roi = {
        "0": 0.602,
        "1704": 0.141,
        "2255": 0.047,
        "7483": 0
    }

    stoploss = -0.10

    trailing_stop = True
    trailing_stop_positive = 0.040
    trailing_stop_positive_offset = 0.120
    trailing_only_offset_is_reached = True

    process_only_new_candles = True
    startup_candle_count: int = 200

    order_types = {
        'entry': 'limit',
        'exit': 'market',       # Salidas forzadas como market para evitar no-fills en colapsos
        'stoploss': 'market',
        'stoploss_on_exchange': False,
        'stoploss_on_exchange_interval': 60
    }

    order_time_in_force = {
        'entry': 'GTC',
        'exit': 'GTC'
    }

    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:
        """
        Dimensionamiento continuo de apalancamiento (Bet Sizing Institucional).
        Calcula el apalancamiento exacto [1.0x - 2.0x] según el score de convicción del setup.
        """
        # Ponderación continua según confluencia de factores
        if entry_tag == 'short_trapped_auction':
            score = 0.90  # Confluencia máxima: trampa de compradores + absorción en techo
        elif entry_tag == 'short_structure_shift':
            score = 0.70  # Confluencia alta: quiebre de estructura post-pump
        elif entry_tag == 'short_macro_breakdown':
            score = 0.55  # Confluencia base: desinflación macro bajo EMA200
        else:
            score = 0.50

        lev = continuous_leverage_calc(score, min_lev=1.0, max_lev=2.0)
        return min(lev, max_leverage)

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # 1. Medias Exponenciales Macro
        dataframe['ema_50'] = ta.ema(dataframe['close'], length=50)
        dataframe['ema_200'] = ta.ema(dataframe['close'], length=200)
        dataframe['dist_ema_50'] = (dataframe['close'] - dataframe['ema_50']) / dataframe['ema_50']

        # 2. Osciladores de Momentum
        dataframe['rsi'] = ta.rsi(dataframe['close'], length=14)

        macd = ta.macd(dataframe['close'], fast=12, slow=26, signal=9)
        dataframe['macd'] = macd['MACD_12_26_9']
        dataframe['macdsignal'] = macd['MACDs_12_26_9']
        dataframe['macdhist'] = macd['MACDh_12_26_9']

        dataframe['atr'] = ta.atr(dataframe['high'], dataframe['low'], dataframe['close'], length=14)

        # 3. Diferenciación Fraccionaria FFD (d=0.40) para memoria estacionaria
        close_arr = dataframe['close'].values
        dataframe['ffd_close'] = calculate_ffd_approx(close_arr, d=0.40, lags=30)
        dataframe['ffd_momentum'] = dataframe['ffd_close'].diff()

        # 4. Microestructura: Detección de Participantes Atrapados (Trapped Longs)
        upper_wick = dataframe['high'] - dataframe[['close', 'open']].max(axis=1)
        candle_body = (dataframe['close'] - dataframe['open']).abs()
        candle_range = dataframe['high'] - dataframe['low']

        vol_sma20 = ta.sma(dataframe['volume'], length=20)
        vol_ratio = dataframe['volume'] / vol_sma20.replace(0, np.nan)

        is_wick_dominant = (upper_wick >= candle_body * 1.8) & (upper_wick >= candle_range * 0.45)
        is_volume_spike = vol_ratio >= 1.4
        is_closing_weak = dataframe['close'] <= (dataframe['low'] + candle_range * 0.35)

        dataframe['trapped_longs_signal'] = (is_wick_dominant & is_volume_spike & is_closing_weak).astype(int)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[:, 'enter_long'] = 0
        dataframe.loc[:, 'enter_short'] = 0

        # Setup 1: Absorción de Techo y Liquidación de Longs Atrapados
        short_trapped_conditions = (
            (dataframe['trapped_longs_signal'].rolling(window=3).max() == 1) &
            (dataframe['close'] < dataframe['low'].shift(1)) &
            (dataframe['macdhist'] < dataframe['macdhist'].shift(1)) &
            (dataframe['volume'] > 0)
        )
        dataframe.loc[short_trapped_conditions, 'enter_short'] = 1
        dataframe.loc[short_trapped_conditions, 'enter_tag'] = 'short_trapped_auction'

        # Setup 2: Transición de Estructura Post-Pump (Agotamiento de Impulso)
        short_structure_shift = (
            (dataframe['dist_ema_50'].rolling(window=3).max() >= 0.12) &
            (dataframe['close'] < dataframe['low'].shift(1)) &
            (dataframe['rsi'] < 68) &
            (dataframe['rsi'].shift(1) >= 65) &
            (dataframe['macdhist'] < dataframe['macdhist'].shift(1)) &
            (dataframe['volume'] > 0)
        )
        dataframe.loc[short_structure_shift, 'enter_short'] = 1
        dataframe.loc[short_structure_shift, 'enter_tag'] = 'short_structure_shift'

        # Setup 3: Desinflación Macro Confirmada por FFD
        short_macro_conditions = (
            (dataframe['close'] < dataframe['ema_200']) &
            (dataframe['macd'] < dataframe['macdsignal']) &
            (dataframe['macd'] < 0) &
            (dataframe['rsi'] <= 44) &
            (dataframe['rsi'] >= 22) &
            (dataframe['ffd_momentum'] <= 0) &  # Confirmación de memoria estacionaria FFD
            (dataframe['volume'] > 0)
        )
        dataframe.loc[short_macro_conditions, 'enter_short'] = 1
        dataframe.loc[short_macro_conditions, 'enter_tag'] = 'short_macro_breakdown'

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[:, 'exit_long'] = 0
        dataframe.loc[:, 'exit_short'] = 0

        # Salidas Técnicas como amortiguador (Airbag ante invalidación estructural)
        exit_short = (
            (dataframe['close'] > dataframe['ema_200']) |
            ((dataframe['macd'] > dataframe['macdsignal']) & (dataframe['rsi'] > 58))
        )
        dataframe.loc[exit_short, 'exit_short'] = 1

        return dataframe
