# source: https://raw.githubusercontent.com/IvPalmer/Master-Trader/978b204f9571729b05462b2b487f594226d77478/ft_userdata/user_data/strategies/BollingerBounceV1.py
# directory_url: https://github.com/IvPalmer/Master-Trader/blob/main/ft_userdata/user_data/strategies/
# User: IvPalmer
# Repository: Master-Trader
# --------------------import logging
from freqtrade.strategy import IStrategy, informative
from pandas import DataFrame
import numpy as np
import pandas as pd

logger = logging.getLogger(__name__)


class Github_IvPalmer_Master_Trader__BollingerBounceV1__20260923_012354(IStrategy):
    """
    Auto-generated by Strategy Lab.
    Bollinger 2-sigma bounce + RSI + ADX trending, BTC SMA200 gate
    """
    INTERFACE_VERSION: int = 3

    minimal_roi = {
        '0': 0.05,
        '360': 0.03,
        '720': 0.02,
        '1440': 0.01
}

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

    timeframe = '1h'
    process_only_new_candles = True
    use_exit_signal = False
    exit_profit_only = True
    exit_profit_offset = 0.01
    startup_candle_count = 200

    @informative('1h', 'BTC/{stake}')
    def populate_indicators_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['sma200'] = dataframe['close'].rolling(200).mean()
        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['rsi'] = _rsi(dataframe['close'], 14)
        bb_sma = dataframe['close'].rolling(20).mean()
        bb_std = dataframe['close'].rolling(20).std()
        dataframe['bb_lower'] = bb_sma - 2 * bb_std
        dataframe['adx'] = _adx(dataframe, 14)
        dataframe['btc_gate'] = ((dataframe['btc_usdt_close_1h'] > dataframe['btc_usdt_sma200_1h'])).astype(int)
        # Volume capitulation filter: spike = exhaustion, higher probability mean reversion
        dataframe['volume_sma'] = dataframe['volume'].rolling(20).mean()
        dataframe['volume_ratio'] = dataframe['volume'] / (dataframe['volume_sma'] + 1e-10)
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                (dataframe['close'] > dataframe['bb_lower']) &
                (dataframe['close'].shift(1) <= dataframe['bb_lower'].shift(1)) &
                (dataframe['rsi'] >= 30) &
                (dataframe['rsi'] <= 70) &
                (dataframe['adx'] > 25) &
                (dataframe['btc_gate'] == 1) &
                (dataframe['volume_ratio'] >= 1.5) &
                (dataframe['volume'] > 0)
            ),
            'enter_long'] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Exits handled by ROI, stoploss, and trailing stop
        return dataframe



def _rsi(close, period=14):
    delta = close.diff()
    gain = delta.clip(lower=0)
    loss = (-delta).clip(lower=0)
    avg_gain = gain.ewm(alpha=1/period, min_periods=period).mean()
    avg_loss = loss.ewm(alpha=1/period, min_periods=period).mean()
    rs = avg_gain / (avg_loss + 1e-10)
    return 100 - (100 / (1 + rs))


def _adx(df, period=14):
    high, low, close = df['high'], df['low'], df['close']
    plus_dm = high.diff()
    minus_dm = -low.diff()
    plus_dm = plus_dm.where((plus_dm > minus_dm) & (plus_dm > 0), 0.0)
    minus_dm = minus_dm.where((minus_dm > plus_dm) & (minus_dm > 0), 0.0)
    tr = pd.concat([high-low, (high-close.shift(1)).abs(), (low-close.shift(1)).abs()], axis=1).max(axis=1)
    atr = tr.rolling(period).mean()
    plus_di = 100 * (plus_dm.ewm(alpha=1/period, min_periods=period).mean() / (atr + 1e-10))
    minus_di = 100 * (minus_dm.ewm(alpha=1/period, min_periods=period).mean() / (atr + 1e-10))
    dx = 100 * (plus_di - minus_di).abs() / (plus_di + minus_di + 1e-10)
    return dx.ewm(alpha=1/period, min_periods=period).mean()
