# source: https://raw.githubusercontent.com/wesire/tradingbot/ab4c8e2493255e8d45788466eec8dca226b7b794/bot/strategy/eth_scalp_strategy.py
"""
ETH/USDT Perpetual Futures Scalping Strategy for Freqtrade.
Tuned for medium volatility trend continuation.
"""
from typing import Optional
import pandas as pd
from pandas import DataFrame
from freqtrade.strategy import IStrategy, informative
import talib.abstract as ta

from bot.strategy.signal_filters import SignalFilters
from bot.strategy.risk_engine import RiskEngine
from bot.config.default_config import config as app_config


class Github_wesire_tradingbot__eth_scalp_strategy__20260218_014553(IStrategy):
    """
    ETH/USDT scalping strategy with regime filtering and trend continuation focus.

    Strategy Logic:
    1. Regime Filter (HTF): EMA crossover + ADX for trend direction/strength
    2. Entry Signals (LTF): Trend continuation entries with momentum confirmation
    3. Filters: Volatility (ATR) and volume spike detection
    4. Risk Management: Dynamic stops, partial TPs, breakeven logic
    
    Differences from BTC strategy:
    - Lower ADX threshold (22 vs 25) - ETH can trend with less strong momentum
    - Higher ATR threshold (0.001 vs 0.0005) - ETH typically more volatile
    - Stronger volume confirmation (1.3x vs 1.2x)
    - Slightly wider stops (2.2 ATR vs 2.0) to handle volatility
    """

    # Strategy metadata
    INTERFACE_VERSION = 3

    # Enable shorts (futures)
    can_short = True

    # Minimal ROI - handled by custom exit logic
    minimal_roi = {
        "0": 0.10,
        "30": 0.05,
        "60": 0.02,
        "120": 0.01
    }

    # Stoploss
    stoploss = -0.025  # Slightly wider hard stop for ETH volatility

    # Trailing stop
    trailing_stop = False

    # Optimal timeframe
    timeframe = "5m"

    # Run "populate_indicators()" only for new candle
    process_only_new_candles = True

    # Use exit signals
    use_exit_signal = True
    exit_profit_only = False
    exit_profit_offset = 0.0

    # Number of candles the strategy requires before producing valid signals
    startup_candle_count: int = 250

    # Strategy parameters (can be optimized)
    # Regime Filter - TODO: calibrate via hyperopt/backtest
    regime_htf_timeframe = "15m"
    regime_ema_fast = 50
    regime_ema_slow = 200
    regime_adx_threshold = 22  # Lower for ETH
    regime_adx_period = 14

    # Entry Signals - TODO: calibrate via hyperopt/backtest
    entry_ema_period = 21
    entry_rsi_period = 14
    entry_rsi_long_threshold = 42  # Slightly higher
    entry_rsi_short_threshold = 58  # Slightly lower

    # Filters - TODO: calibrate via hyperopt/backtest
    filter_atr_period = 14
    filter_atr_min_threshold = 0.001  # Higher for ETH
    filter_volume_period = 20
    entry_volume_multiplier = 1.3  # Stronger volume requirement

    # Position sizing
    position_adjustment_enable = True
    max_entry_position_adjustment = 0  # No DCA/averaging

    def __init__(self, config: dict, *args, **kwargs):
        super().__init__(config)

        # Initialize signal filters and risk engine
        self.signal_filters = SignalFilters(
            atr_period=self.filter_atr_period,
            atr_min_threshold=self.filter_atr_min_threshold,
            volume_period=self.filter_volume_period,
            volume_multiplier=self.entry_volume_multiplier
        )

        self.risk_engine = RiskEngine(
            max_risk_per_trade=app_config.MAX_RISK_PER_TRADE,
            max_daily_drawdown=app_config.MAX_DAILY_DRAWDOWN,
            max_consecutive_losses=app_config.MAX_CONSECUTIVE_LOSSES,
            cooldown_minutes=app_config.COOLDOWN_MINUTES,
            stop_loss_atr_multiplier=2.2,  # Wider for ETH
            stop_loss_max_percent=0.025,
            take_profit_r_levels=app_config.TAKE_PROFIT_R_LEVELS,
            breakeven_after_first_tp=app_config.BREAKEVEN_AFTER_FIRST_TP
        )

    @informative('15m')
    def populate_indicators_15m(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Populate indicators for higher timeframe (regime detection).
        """
        # EMA for regime
        dataframe['ema_fast'] = ta.EMA(dataframe, timeperiod=self.regime_ema_fast)
        dataframe['ema_slow'] = ta.EMA(dataframe, timeperiod=self.regime_ema_slow)

        # ADX for trend strength
        dataframe['adx'] = ta.ADX(dataframe, timeperiod=self.regime_adx_period)

        # Regime determination
        dataframe['regime_bullish'] = (
            (dataframe['ema_fast'] > dataframe['ema_slow']) &
            (dataframe['adx'] > self.regime_adx_threshold)
        )

        dataframe['regime_bearish'] = (
            (dataframe['ema_fast'] < dataframe['ema_slow']) &
            (dataframe['adx'] > self.regime_adx_threshold)
        )

        dataframe['regime_neutral'] = ~(
            dataframe['regime_bullish'] | dataframe['regime_bearish']
        )

        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Populate indicators for execution timeframe.
        """
        # Entry EMA
        dataframe['ema'] = ta.EMA(dataframe, timeperiod=self.entry_ema_period)

        # VWAP (approximation using cumulative)
        dataframe['vwap'] = (
            (dataframe['close'] * dataframe['volume']).cumsum() /
            dataframe['volume'].cumsum()
        )

        # RSI for entry triggers
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=self.entry_rsi_period)

        # MACD for momentum confirmation (ETH-specific)
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macd_signal'] = macd['macdsignal']
        dataframe['macd_hist'] = macd['macdhist']

        # Apply signal filters (ATR, volume)
        dataframe = self.signal_filters.apply_all_filters(
            dataframe,
            enable_mean_reversion=True
        )

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Populate buy/sell signals based on regime and entry conditions.
        """
        # Long Entry Conditions
        long_conditions = [
            # HTF regime is bullish
            (dataframe['regime_bullish_15m'] == True),

            # Price near EMA support
            (dataframe['close'] <= dataframe['ema'] * 1.015),  # Within 1.5% of EMA

            # RSI recovery (slightly different thresholds for ETH)
            (dataframe['rsi'] > self.entry_rsi_long_threshold),
            (dataframe['rsi'].shift(1) <= self.entry_rsi_long_threshold),

            # MACD momentum confirmation
            (dataframe['macd'] > dataframe['macd_signal']),

            # Filters passed
            (dataframe['filters_passed'] == True),

            # Volume condition
            (dataframe['volume'] > 0)
        ]

        # Short Entry Conditions
        short_conditions = [
            # HTF regime is bearish
            (dataframe['regime_bearish_15m'] == True),

            # Price near EMA resistance
            (dataframe['close'] >= dataframe['ema'] * 0.985),  # Within 1.5% of EMA

            # RSI rollover
            (dataframe['rsi'] < self.entry_rsi_short_threshold),
            (dataframe['rsi'].shift(1) >= self.entry_rsi_short_threshold),

            # MACD momentum confirmation
            (dataframe['macd'] < dataframe['macd_signal']),

            # Filters passed
            (dataframe['filters_passed'] == True),

            # Volume condition
            (dataframe['volume'] > 0)
        ]

        # Combine conditions
        dataframe['enter_long'] = 0
        dataframe['enter_short'] = 0

        if long_conditions:
            dataframe.loc[
                pd.concat(long_conditions, axis=1).all(axis=1),
                'enter_long'
            ] = 1

        if short_conditions:
            dataframe.loc[
                pd.concat(short_conditions, axis=1).all(axis=1),
                'enter_short'
            ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Populate exit signals (optional - mainly using custom exit logic).
        """
        dataframe['exit_long'] = 0
        dataframe['exit_short'] = 0

        # Exit long when regime flips bearish
        dataframe.loc[
            (dataframe['regime_bearish_15m'] == True),
            'exit_long'
        ] = 1

        # Exit short when regime flips bullish
        dataframe.loc[
            (dataframe['regime_bullish_15m'] == True),
            'exit_short'
        ] = 1

        return dataframe

    def custom_stoploss(
        self,
        pair: str,
        trade: 'Trade',
        current_time: 'datetime',
        current_rate: float,
        current_profit: float,
        **kwargs
    ) -> Optional[float]:
        """
        Custom stoploss logic using ATR-based dynamic stops.
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        
        if dataframe is None or len(dataframe) == 0:
            return None
        
        last_candle = dataframe.iloc[-1].squeeze()
        
        # Use risk engine to calculate dynamic stop
        stop_distance = self.risk_engine.calculate_stop_loss(
            entry_price=trade.open_rate,
            current_price=current_rate,
            atr=last_candle.get('atr', 0),
            trade_side='long' if trade.is_short is False else 'short'
        )
        
        if stop_distance:
            return stop_distance
        
        return None

    def custom_exit(
        self,
        pair: str,
        trade: 'Trade',
        current_time: 'datetime',
        current_rate: float,
        current_profit: float,
        **kwargs
    ) -> Optional[str]:
        """
        Custom exit logic for partial profits and breakeven.
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        
        if dataframe is None or len(dataframe) == 0:
            return None
        
        last_candle = dataframe.iloc[-1].squeeze()
        
        # Check if we should exit based on risk engine rules
        should_exit, reason = self.risk_engine.check_exit_conditions(
            entry_price=trade.open_rate,
            current_price=current_rate,
            trade_duration_minutes=(current_time - trade.open_date_utc).total_seconds() / 60,
            current_profit_ratio=current_profit,
            atr=last_candle.get('atr', 0)
        )
        
        if should_exit:
            return reason
        
        return None
