# source: https://raw.githubusercontent.com/RoboticAutomations/freqtradestrategies/52c4bd40414645f079a980cf4486dc7af4061efb/AIRA%20-%20open%20code%20v2/strategies/orderbook-volume/QMR.py
# directory_url: https://github.com/RoboticAutomations/freqtradestrategies/blob/main/AIRA - open code v2/strategies/orderbook-volume/
# User: RoboticAutomations
# Repository: freqtradestrategies
# --------------------# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
import numpy as np
import pandas as pd
from datetime import datetime
from pandas import DataFrame
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, RealParameter
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib

# QuantitativeMeanReversion
class Github_RoboticAutomations_freqtradestrategies__QMR__20260915_233254(IStrategy):
    """
    Estrategia: Mean Reversion + ICT Liquidity Sweeps
    Timeframe: 15m
    Conceptos: POC (Point of Control), Range High/Low, Dynamic Stake (Kelly)
    """
    INTERFACE_VERSION = 3
    timeframe = '15m'
    can_short = True

    lookback_window = IntParameter(1, 100, default=50, space="buy")
    rsi_threshold_long = IntParameter(20, 40, default=30, space="buy")
    rsi_threshold_short = IntParameter(60, 80, default=70, space="sell")
    
    leverage_value = IntParameter(1, 5, default=3, space="buy")
    kelly_fraction = DecimalParameter(0.05, 0.2, default=0.1, space="buy")

    minimal_roi = {} 
    stoploss = -0.99
    exit_profit_only = True

    @property
    def plot_config(self):
        return {
            "main_plot": {
                "typical_price": {"color": "purple", "style": "line"},
                "poc": {"color": "red", "style": "dash"},
                "vwap": {"color": "orange", "style": "dash"},
            },
            "subplots": {
                "RSI": {
                    "rsi": {"color": "yellow"},
                },
                "ATR": {
                    "atr": {"color": "yellow"},
                },
            },
        }

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # 1. RSI para Exhaustión
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)

        # 2. Definición de Rango (ICT Liquidity Pools)
        # Identificamos el máximo y mínimo de las últimas N velas
        dataframe['range_hi'] = dataframe['high'].rolling(window=self.lookback_window.value).max()
        dataframe['range_lo'] = dataframe['low'].rolling(window=self.lookback_window.value).min()

        # 3. Point of Control (POC) Simplificado - Reversión a la Media
        # En modelos cuantitativos, el Typical Price es una aproximación robusta al valor justo
        dataframe['typical_price'] = (dataframe['high'] + dataframe['low'] + dataframe['close']) / 3
        dataframe['poc'] = ta.SMA(dataframe['typical_price'], timeperiod=self.lookback_window.value)

        # 4. Volatilidad para Gestión de Riesgo (ATR)
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)

        # 5. VWAP rolling = POC proxy
        dataframe['vwap'] = (
            (dataframe['typical_price'] * dataframe['volume'])
            .rolling(self.lookback_window.value)
            .sum()
            / dataframe['volume'].rolling(self.lookback_window.value).sum()
        )

        # Distance to value (mean deviation)
        dataframe['dev_from_vwap'] = (dataframe['close'] - dataframe['vwap']) / dataframe['atr']

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Lógica de Entrada basada en Barrido de Liquidez (Liquidity Sweep)
        """
        # LONG: El precio barre el mínimo del rango (Discount) + RSI sobrevendido
        dataframe.loc[
            (
                (dataframe['low'] <= dataframe['range_lo']) &
                (dataframe['rsi'] < self.rsi_threshold_long.value) &
                (dataframe['close'] > dataframe['low']) &
                (dataframe['volume'] > 0)
            ),
            ['enter_long', 'enter_tag']] = (1, 'long_sweep_reversion')

        # SHORT: El precio barre el máximo del rango (Premium) + RSI sobrecomprado
        dataframe.loc[
            (
                (dataframe['high'] >= dataframe['range_hi']) &
                (dataframe['rsi'] > self.rsi_threshold_short.value) &
                (dataframe['close'] < dataframe['high']) &
                (dataframe['volume'] > 0)
            ),
            ['enter_short', 'enter_tag']] = (1, 'short_sweep_reversion')

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Lógica de Salida: Reversión Completa al POC
        """
        # Salida Long: El precio alcanza o supera el POC (Fair Value)
        dataframe.loc[
            (dataframe['close'] >= dataframe['poc']),
            ['exit_long', 'exit_tag']] = (1, 'exit_at_poc')

        # Salida Short: El precio cae por debajo del POC
        dataframe.loc[
            (dataframe['close'] <= dataframe['poc']),
            ['exit_short', 'exit_tag']] = (1, 'exit_at_poc')

        return dataframe

    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                            proposed_stake: float, min_stake: float | None, max_stake: float,
                            leverage: float, entry_tag: str | None, side: str, **kwargs) -> float:
        """
        GESTIÓN DINÁMICA: Implementación del Criterio de Kelly
        Calcula el tamaño de la posición basado en la ventaja estadística.
        """
        # Estimación de Win Rate (p) y Ratio Riesgo/Beneficio (R) basada en backtest previo
        win_rate = 0.55 
        risk_reward = 1.5
        
        # Fórmula de Kelly: f* = (p*R - q) / R
        q = 1 - win_rate
        kelly_f = ((win_rate * risk_reward) - q) / risk_reward
        
        # Aplicamos la fracción de Kelly y reserva de balance (Hedging Reserve)
        # Mantenemos un 30% del balance libre para Cross Margin/Funding
        effective_stake = max_stake * kelly_f * float(self.kelly_fraction.value)
        
        return min(max(effective_stake, min_stake or 0), max_stake * 0.7)

    def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
                            time_in_force: str, current_time: datetime, entry_tag: str | None,
                            side: str, **kwargs) -> bool:
        """
        Configuración de Apalancamiento Dinámico (Cross Margin Simulation)
        """
        # Aquí se podría integrar lógica para ajustar el leverage en el exchange
        # según la volatilidad (ATR). A mayor ATR, menor leverage.
        return True