# source: https://raw.githubusercontent.com/marcosrioj/cryptotrader/3760b3457dcc3a323c182526d85d4dfa32d85b54/user_data/strategies/BollingerSqueezeScalpStrategy.py
# Bollinger Bands Squeeze Scalping Strategy
# Scalping strategy based on Bollinger Bands and volatility squeeze
# Optimized to capture fast movements after low volatility

import talib.abstract as ta
from freqtrade.strategy import IStrategy, DecimalParameter, IntParameter
from pandas import DataFrame
import numpy as np


class Github_marcosrioj_cryptotrader__BollingerSqueezeScalpStrategy__20251031_064108(IStrategy):
    """
    Bollinger Bands Squeeze Scalping Strategy
    
    This strategy uses:
    - Bollinger Bands to identify volatility squeeze
    - RSI for momentum
    - Volume for breakout confirmation
    - ADX for trend strength
    
    Buy signals:
    - Bollinger Bands in squeeze (low volatility)
    - Price breaks above upper band
    - RSI > 50 (upward momentum)
    - Volume spike (2x average)
    - ADX > 20 (trend forming)
    
    Sell signals:
    - Price breaks below lower band
    - RSI < 50 (downward momentum)
    - Or stop loss/take profit
    """

    # Basic strategy configuration
    INTERFACE_VERSION = 3
    
    # Risk settings for scalping
    stoploss = -0.025  # 2.5% stop loss
    
    # Timeframe for scalping
    timeframe = '5m'  # 5 minutes for less noise than 1m
    
    # Trailing stop
    trailing_stop = True
    trailing_stop_positive = 0.008  # 0.8%
    trailing_stop_positive_offset = 0.015  # 1.5%
    trailing_only_offset_is_reached = True
    
    # ROI optimized for scalping
    minimal_roi = {
        "0": 0.04,    # 4% imediato
        "5": 0.025,   # 2.5% após 5 minutos
        "10": 0.02,   # 2% após 10 minutos
        "20": 0.015,  # 1.5% após 20 minutos
        "30": 0.01    # 1% após 30 minutos
    }
    
    # Optimizable parameters
    bb_period = IntParameter(15, 25, default=20, space="buy")
    bb_std = DecimalParameter(1.8, 2.2, default=2.0, space="buy")
    
    rsi_period = IntParameter(10, 16, default=14, space="buy")
    rsi_buy_threshold = IntParameter(45, 55, default=50, space="buy")
    rsi_sell_threshold = IntParameter(45, 55, default=50, space="sell")
    
    volume_spike = DecimalParameter(1.8, 3.0, default=2.0, space="buy")
    
    adx_period = IntParameter(12, 18, default=14, space="buy")
    adx_threshold = IntParameter(18, 25, default=20, space="buy")
    
    squeeze_threshold = DecimalParameter(0.01, 0.03, default=0.02, space="buy")
    
    # Configurações de proteção
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False
    
    startup_candle_count: int = 40

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Adiciona indicadores técnicos para squeeze scalping
        """
        
        # Bollinger Bands
        bollinger = ta.BBANDS(
            dataframe, 
            timeperiod=self.bb_period.value, 
            nbdevup=self.bb_std.value,
            nbdevdn=self.bb_std.value
        )
        dataframe['bb_lowerband'] = bollinger['lowerband']
        dataframe['bb_middleband'] = bollinger['middleband']
        dataframe['bb_upperband'] = bollinger['upperband']
        
        # Bollinger Band Width (para detectar squeeze)
        dataframe['bb_width'] = (
            (dataframe['bb_upperband'] - dataframe['bb_lowerband']) / 
            dataframe['bb_middleband']
        )
        
        # Squeeze detection
        dataframe['bb_squeeze'] = dataframe['bb_width'] < self.squeeze_threshold.value
        
        # Bollinger Band position
        dataframe['bb_percent'] = (
            (dataframe['close'] - dataframe['bb_lowerband']) / 
            (dataframe['bb_upperband'] - dataframe['bb_lowerband'])
        )
        
        # Breakouts
        dataframe['bb_break_up'] = (
            (dataframe['close'] > dataframe['bb_upperband']) &
            (dataframe['close'].shift(1) <= dataframe['bb_upperband'].shift(1))
        )
        dataframe['bb_break_down'] = (
            (dataframe['close'] < dataframe['bb_lowerband']) &
            (dataframe['close'].shift(1) >= dataframe['bb_lowerband'].shift(1))
        )
        
        # RSI
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=self.rsi_period.value)
        
        # Volume analysis
        dataframe['volume_sma'] = ta.SMA(dataframe['volume'], timeperiod=20)
        dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_sma']
        
        # ADX para força da tendência
        dataframe['adx'] = ta.ADX(dataframe, timeperiod=self.adx_period.value)
        
        # MACD para momentum adicional
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']
        dataframe['macdhist'] = macd['macdhist']
        
        # EMA rápida para direção
        dataframe['ema_fast'] = ta.EMA(dataframe, timeperiod=9)
        
        # Keltner Channels (para comparar com BB)
        dataframe['kc_upper'] = dataframe['bb_middleband'] + (ta.ATR(dataframe, timeperiod=20) * 2)
        dataframe['kc_lower'] = dataframe['bb_middleband'] - (ta.ATR(dataframe, timeperiod=20) * 2)
        
        # True squeeze (BB dentro de Keltner)
        dataframe['true_squeeze'] = (
            (dataframe['bb_upperband'] < dataframe['kc_upper']) &
            (dataframe['bb_lowerband'] > dataframe['kc_lower'])
        )
        
        # ATR para stop loss dinâmico
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
        
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Entry signals based on squeeze and breakout
        """
        
        conditions = []
        
        # Condição 1: Squeeze recente (nas últimas 3 velas)
        dataframe['recent_squeeze'] = (
            dataframe['bb_squeeze'] | 
            dataframe['bb_squeeze'].shift(1) | 
            dataframe['bb_squeeze'].shift(2) |
            dataframe['true_squeeze'] |
            dataframe['true_squeeze'].shift(1)
        )
        conditions.append(dataframe['recent_squeeze'])
        
        # Condição 2: Breakout para cima
        conditions.append(dataframe['bb_break_up'])
        
        # Condição 3: RSI com momentum de alta
        conditions.append(dataframe['rsi'] > self.rsi_buy_threshold.value)
        
        # Condição 4: Volume spike confirmando breakout
        conditions.append(dataframe['volume_ratio'] > self.volume_spike.value)
        
        # Condição 5: ADX mostrando força na tendência
        conditions.append(dataframe['adx'] > self.adx_threshold.value)
        
        # Condição 6: MACD positivo
        conditions.append(dataframe['macdhist'] > 0)
        
        # Condição 7: Preço acima da EMA rápida
        conditions.append(dataframe['close'] > dataframe['ema_fast'])
        
        # Condição 8: Não em zona de sobrecompra extrema
        conditions.append(dataframe['bb_percent'] < 0.95)
        
        if conditions:
            dataframe.loc[
                np.logical_and.reduce(conditions),
                'enter_long'
            ] = 1
            
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Exit signals for squeeze scalping
        """
        
        conditions = []
        
        # Condição principal: Breakout para baixo
        conditions.append(dataframe['bb_break_down'])
        
        # Condições alternativas
        alt_conditions = []
        
        # RSI perdendo momentum
        alt_conditions.append(dataframe['rsi'] < self.rsi_sell_threshold.value)
        
        # Preço volta para dentro das bandas após extensão
        alt_conditions.append(
            (dataframe['bb_percent'] > 0.8) & 
            (dataframe['close'] < dataframe['bb_upperband'])
        )
        
        # MACD virando negativo
        alt_conditions.append(
            (dataframe['macd'] < dataframe['macdsignal']) &
            (dataframe['macdhist'] < 0)
        )
        
        # Volume diminuindo após spike
        alt_conditions.append(
            (dataframe['volume_ratio'] < 1.0) &
            (dataframe['volume_ratio'].shift(1) > 2.0)
        )
        
        # Preço abaixo da EMA rápida
        alt_conditions.append(dataframe['close'] < dataframe['ema_fast'])
        
        # Combinar condições
        main_exit = np.logical_and.reduce(conditions) if conditions else False
        alt_exit = np.logical_and.reduce(alt_conditions) if alt_conditions else False
        
        dataframe.loc[main_exit | alt_exit, 'exit_long'] = 1
        
        return dataframe

    def custom_stoploss(self, pair: str, trade, current_time, current_rate, 
                       current_profit, **kwargs):
        """
        Stop loss baseado em ATR
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        
        # Stop loss baseado em ATR
        atr_multiplier = 2.0
        atr_stop = atr_multiplier * last_candle['atr'] / current_rate
        
        return max(-atr_stop, self.stoploss)

    def custom_exit(self, pair: str, trade, current_time, current_rate,
                   current_profit, **kwargs):
        """
        Custom exits for squeeze scalping
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        
        # Saída rápida com lucro alto
        if current_profit > 0.03:
            return 'high_profit_exit'
        
        # Saída se squeeze voltou (fim do movimento)
        if current_profit > 0.01 and last_candle['bb_squeeze']:
            return 'squeeze_return_exit'
        
        # Saída se RSI extremo
        if current_profit > 0.008 and last_candle['rsi'] > 75:
            return 'rsi_extreme_exit'
        
        return None

    def leverage(self, pair: str, current_time, current_rate,
                proposed_leverage: float, max_leverage: float, entry_tag, side: str,
                **kwargs) -> float:
        """
        Alavancagem para scalping
        """
        return 10.0  # Alavancagem 10x (configurar manualmente na Bybit)