# source: https://raw.githubusercontent.com/IISweetHeartII/freqtrade-strategies-lab/fee95ce63a145ad5d15d53c7c3af5d8acbebc3e2/user_data/strategies/E0V1E_LongShort.py
"""
Github_IISweetHeartII_freqtrade_strategies_lab__E0V1E_LongShort__20260216_081316 Strategy (Improved)
====================================
Based on E0V1E + E0V1EN backtest results analysis:
- buy_new performed better than buy_1 (+15% vs -23% in 2024)
- Added time-based exits from E0V1EN
- Added 24h price change filter
- Refined short entry conditions
- Added CooldownPeriod protection

Long Entry: RSI oversold + price below SMA (mean reversion)
Short Entry: RSI overbought + price above SMA (mean reversion)
"""

from datetime import datetime, timedelta
import talib.abstract as ta
import pandas_ta as pta
from freqtrade.persistence import Trade
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
from freqtrade.strategy import DecimalParameter, IntParameter
from functools import reduce
import warnings

warnings.simplefilter(action="ignore", category=RuntimeWarning)


class Github_IISweetHeartII_freqtrade_strategies_lab__E0V1E_LongShort__20260216_081316(IStrategy):
    minimal_roi = {"0": 1}
    timeframe = '5m'
    process_only_new_candles = True
    startup_candle_count = 300  # 288 for 24h + buffer

    can_short = True

    order_types = {
        'entry': 'market',
        'exit': 'market',
        'emergency_exit': 'market',
        'force_entry': 'market',
        'force_exit': "market",
        'stoploss': 'market',
        'stoploss_on_exchange': False,
        'stoploss_on_exchange_interval': 60,
        'stoploss_on_exchange_market_ratio': 0.99
    }

    # Tighter stoploss than original -25% (reduces big losses)
    stoploss = -0.15
    trailing_stop = True
    trailing_stop_positive = 0.002
    trailing_stop_positive_offset = 0.03
    trailing_only_offset_is_reached = True

    # ============ LONG PARAMETERS ============
    # Based on buy_new success (better than buy_1)
    is_optimize_long = True
    buy_rsi_fast = IntParameter(20, 50, default=34, space='buy', optimize=is_optimize_long)
    buy_rsi_min = IntParameter(15, 40, default=28, space='buy', optimize=is_optimize_long)
    buy_sma15 = DecimalParameter(0.93, 0.99, default=0.96, decimals=3, space='buy', optimize=is_optimize_long)
    buy_cti = DecimalParameter(-1, 1, default=0.69, decimals=2, space='buy', optimize=is_optimize_long)

    # 24h price change filter (from E0V1EN)
    buy_24h_min_pct = DecimalParameter(-30.0, 0.0, default=-15.0, decimals=1, space='buy', optimize=True)
    buy_24h_max_pct = DecimalParameter(0.0, 100.0, default=50.0, decimals=1, space='buy', optimize=True)

    # ============ SHORT PARAMETERS ============
    # Stricter conditions to only short in true overbought zones
    is_optimize_short = True
    sell_rsi_fast = IntParameter(65, 90, default=75, space='sell', optimize=is_optimize_short)
    sell_rsi_min = IntParameter(55, 80, default=65, space='sell', optimize=is_optimize_short)  # Add RSI lower bound
    sell_rsi_max = IntParameter(70, 90, default=80, space='sell', optimize=is_optimize_short)
    sell_sma15 = DecimalParameter(1.02, 1.10, default=1.05, decimals=3, space='sell', optimize=is_optimize_short)
    sell_cti_min = DecimalParameter(0.0, 0.8, default=0.3, decimals=2, space='sell', optimize=is_optimize_short)  # CTI positive range only

    # 24h price change filter for shorts
    sell_24h_min_pct = DecimalParameter(-50.0, 50.0, default=10.0, decimals=1, space='sell', optimize=True)
    sell_24h_max_pct = DecimalParameter(50.0, 200.0, default=100.0, decimals=1, space='sell', optimize=True)

    # ============ EXIT PARAMETERS ============
    exit_fastx_long = IntParameter(50, 100, default=84, space='sell', optimize=True)
    exit_fastx_short = IntParameter(0, 50, default=16, space='sell', optimize=True)

    exit_cci_long = IntParameter(50, 150, default=80, space='sell', optimize=True)
    exit_cci_short = IntParameter(-150, -50, default=-80, space='sell', optimize=True)

    @property
    def protections(self):
        return [
            {
                "method": "CooldownPeriod",
                "stop_duration_candles": 96  # 8 hours cooldown
            }
        ]

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # RSI indicators
        dataframe['sma_15'] = ta.SMA(dataframe, timeperiod=15)
        dataframe['cti'] = pta.cti(dataframe["close"], length=20)
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['rsi_fast'] = ta.RSI(dataframe, timeperiod=4)
        dataframe['rsi_slow'] = ta.RSI(dataframe, timeperiod=20)

        # 24h price change (288 candles = 24h for 5m timeframe)
        dataframe['24h_change_pct'] = (dataframe['close'].pct_change(periods=288) * 100)

        # Exit indicators
        stoch_fast = ta.STOCHF(dataframe, 5, 3, 0, 3, 0)
        dataframe['fastk'] = stoch_fast['fastk']
        dataframe['cci'] = ta.CCI(dataframe, timeperiod=20)

        return dataframe

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

        # ============ LONG ENTRY ============
        # Based on buy_new logic (performed better in backtest)
        long_conditions = (
            (dataframe['rsi_slow'] < dataframe['rsi_slow'].shift(1)) &  # RSI declining (momentum weakening)
            (dataframe['rsi_fast'] < self.buy_rsi_fast.value) &         # Fast RSI oversold
            (dataframe['rsi'] > self.buy_rsi_min.value) &               # But not too extreme (avoid falling knife)
            (dataframe['close'] < dataframe['sma_15'] * self.buy_sma15.value) &  # Price below SMA
            (dataframe['cti'] < self.buy_cti.value) &                   # CTI filter
            (dataframe['24h_change_pct'] > self.buy_24h_min_pct.value) &  # 24h change filter
            (dataframe['24h_change_pct'] < self.buy_24h_max_pct.value)
        )

        dataframe.loc[long_conditions, 'enter_long'] = 1
        dataframe.loc[long_conditions, 'enter_tag'] = 'long_mean_reversion'

        # ============ SHORT ENTRY ============
        # Stricter conditions: only short in true overbought zones
        short_conditions = (
            (dataframe['rsi_slow'] > dataframe['rsi_slow'].shift(1)) &  # RSI rising (momentum increasing)
            (dataframe['rsi_fast'] > self.sell_rsi_fast.value) &        # Fast RSI overbought (75+)
            (dataframe['rsi'] > self.sell_rsi_min.value) &              # RSI lower bound (65+) - confirm overbought
            (dataframe['rsi'] < self.sell_rsi_max.value) &              # RSI upper bound (80-) - avoid extremes
            (dataframe['close'] > dataframe['sma_15'] * self.sell_sma15.value) &  # Price above SMA (5%+)
            (dataframe['cti'] > self.sell_cti_min.value) &              # CTI positive range only (0.3+)
            (dataframe['24h_change_pct'] > self.sell_24h_min_pct.value) &  # 24h filter for shorts
            (dataframe['24h_change_pct'] < self.sell_24h_max_pct.value)
        )

        dataframe.loc[short_conditions, 'enter_short'] = 1
        dataframe.loc[short_conditions, 'enter_tag'] = 'short_mean_reversion'

        return dataframe

    def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
                    current_profit: float, **kwargs):
        dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
        current_candle = dataframe.iloc[-1].squeeze()

        if trade.is_short:
            # ============ SHORT EXIT LOGIC ============
            # Take profit when oversold (price likely to bounce)
            if current_profit > 0:
                if current_candle["fastk"] < self.exit_fastx_short.value:
                    return "fastk_profit_exit_short"

            # CCI exit for shorts (oversold = time to cover)
            if current_profit > -0.03:
                if current_candle["cci"] < self.exit_cci_short.value:
                    return "cci_exit_short"

            # Time-based exits for shorts (from E0V1EN)
            if current_time - timedelta(hours=7) > trade.open_date_utc:
                if current_profit >= -0.05:
                    return "time_exit_short_7h"

            if current_time - timedelta(hours=10) > trade.open_date_utc:
                if current_profit >= -0.10:
                    return "time_exit_short_10h"

        else:
            # ============ LONG EXIT LOGIC ============
            # Take profit when overbought
            if current_profit > 0:
                if current_candle["fastk"] > self.exit_fastx_long.value:
                    return "fastk_profit_exit_long"

            # CCI exit for longs (overbought = time to sell)
            if current_profit > -0.03:
                if current_candle["cci"] > self.exit_cci_long.value:
                    return "cci_exit_long"

            # Time-based exits for longs (from E0V1EN)
            if current_time - timedelta(hours=7) > trade.open_date_utc:
                if current_profit >= -0.05:
                    return "time_exit_long_7h"

            if current_time - timedelta(hours=10) > trade.open_date_utc:
                if current_profit >= -0.10:
                    return "time_exit_long_10h"

        return None

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