# source: https://raw.githubusercontent.com/acsvalentinacs/HOPE-Minibot/65e927a2984b68dfce48fafe50b7a35813bd1102/vps_sync/strategies/HopeHyperopt.py
# -*- coding: utf-8 -*-
"""
HOPE Hyperopt Strategy V2
=========================
Optimized for scalping on 5m timeframe.
Realistic parameter ranges based on actual market data.

Created: 2026-02-07
"""

from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, BooleanParameter
from freqtrade.strategy import merge_informative_pair
from freqtrade.persistence import Trade
from pandas import DataFrame
import talib.abstract as ta
from datetime import datetime, timezone
from typing import Optional
import logging

log = logging.getLogger(__name__)


class Github_acsvalentinacs_HOPE_Minibot__HopeHyperopt__20260218_115300(IStrategy):
    """
    HOPE Hyperopt V2 - realistic scalping parameters.
    No custom_stoploss to let hyperopt optimize stoploss/trailing freely.
    """

    # ================================================================
    # HYPEROPT PARAMETER SPACES (realistic ranges)
    # ================================================================

    # --- BUY ---
    buy_rsi_max = IntParameter(25, 55, default=42, space="buy", optimize=True)
    buy_ema_short = IntParameter(3, 12, default=5, space="buy", optimize=True)
    buy_ema_long = IntParameter(13, 30, default=21, space="buy", optimize=True)
    buy_volume_factor = DecimalParameter(1.0, 2.0, default=1.2, decimals=1, space="buy", optimize=True)
    buy_mom_threshold = DecimalParameter(-0.3, 0.3, default=0.0, decimals=2, space="buy", optimize=True)
    buy_require_btc_uptrend = BooleanParameter(default=True, space="buy", optimize=True)
    buy_bb_lower = BooleanParameter(default=False, space="buy", optimize=True)
    buy_macd_positive = BooleanParameter(default=False, space="buy", optimize=True)

    # --- SELL ---
    sell_rsi_min = IntParameter(60, 82, default=70, space="sell", optimize=True)
    sell_ema_cross = BooleanParameter(default=True, space="sell", optimize=True)

    # --- ROI (realistic for scalping) ---
    minimal_roi = {
        "0": 0.015,
        "5": 0.01,
        "10": 0.005,
        "20": 0.003
    }

    # --- STOPLOSS ---
    stoploss = -0.02

    # --- TRAILING ---
    trailing_stop = True
    trailing_stop_positive = 0.005
    trailing_stop_positive_offset = 0.01
    trailing_only_offset_is_reached = True

    # --- SETTINGS ---
    timeframe = "5m"
    process_only_new_candles = True
    startup_candle_count = 50
    use_exit_signal = True
    exit_profit_only = False
    use_custom_stoploss = False  # Let hyperopt optimize stoploss freely

    order_types = {
        "entry": "market",
        "exit": "market",
        "stoploss": "market",
        "stoploss_on_exchange": False,
    }

    def informative_pairs(self):
        return [("BTC/USDT", "1h")]

    # ================================================================
    # INDICATORS
    # ================================================================

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # BTC 1h trend
        if self.dp:
            try:
                btc_1h = self.dp.get_pair_dataframe("BTC/USDT", "1h")
                if len(btc_1h) > 5:
                    btc_1h["btc_trend"] = (btc_1h["close"] > btc_1h["close"].shift(4)).astype(int)
                    btc_1h["btc_rsi"] = ta.RSI(btc_1h, timeperiod=14)
                    dataframe = merge_informative_pair(
                        dataframe, btc_1h[["date", "btc_trend", "btc_rsi"]],
                        self.timeframe, "1h", ffill=True
                    )
                else:
                    dataframe["btc_trend_1h"] = 1
                    dataframe["btc_rsi_1h"] = 50
            except Exception:
                dataframe["btc_trend_1h"] = 1
                dataframe["btc_rsi_1h"] = 50
        else:
            dataframe["btc_trend_1h"] = 1
            dataframe["btc_rsi_1h"] = 50

        # RSI
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)

        # EMAs (all periods hyperopt might need)
        for p in [3, 5, 7, 8, 10, 12, 13, 15, 18, 20, 21, 25, 26, 30]:
            dataframe[f"ema_{p}"] = ta.EMA(dataframe, timeperiod=p)

        # Volume
        dataframe["volume_sma"] = ta.SMA(dataframe["volume"], timeperiod=10)

        # Momentum
        dataframe["mom"] = ta.MOM(dataframe, timeperiod=5)

        # Bollinger Bands
        bb = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
        dataframe["bb_upper"] = bb["upperband"]
        dataframe["bb_lower"] = bb["lowerband"]
        dataframe["bb_mid"] = bb["middleband"]

        # MACD
        macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
        dataframe["macd"] = macd["macd"]
        dataframe["macd_signal"] = macd["macdsignal"]
        dataframe["macd_hist"] = macd["macdhist"]

        return dataframe

    # ================================================================
    # ENTRY
    # ================================================================

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        ema_s = f"ema_{self.buy_ema_short.value}"
        ema_l = f"ema_{self.buy_ema_long.value}"

        if ema_s not in dataframe.columns:
            dataframe[ema_s] = ta.EMA(dataframe, timeperiod=self.buy_ema_short.value)
        if ema_l not in dataframe.columns:
            dataframe[ema_l] = ta.EMA(dataframe, timeperiod=self.buy_ema_long.value)

        conditions = (
            (dataframe["rsi"] < self.buy_rsi_max.value) &
            (dataframe[ema_s] > dataframe[ema_l]) &
            (dataframe["volume"] > dataframe["volume_sma"] * self.buy_volume_factor.value) &
            (dataframe["mom"] > self.buy_mom_threshold.value) &
            (dataframe["volume"] > 0)
        )

        if self.buy_require_btc_uptrend.value:
            conditions = conditions & (dataframe["btc_trend_1h"] == 1)

        if self.buy_bb_lower.value:
            conditions = conditions & (dataframe["close"] < dataframe["bb_mid"])

        if self.buy_macd_positive.value:
            conditions = conditions & (dataframe["macd_hist"] > 0)

        dataframe.loc[conditions, "enter_long"] = 1
        return dataframe

    # ================================================================
    # EXIT
    # ================================================================

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        ema_s = f"ema_{self.buy_ema_short.value}"
        ema_l = f"ema_{self.buy_ema_long.value}"

        if ema_s not in dataframe.columns:
            dataframe[ema_s] = ta.EMA(dataframe, timeperiod=self.buy_ema_short.value)
        if ema_l not in dataframe.columns:
            dataframe[ema_l] = ta.EMA(dataframe, timeperiod=self.buy_ema_long.value)

        exit_cond = (dataframe["rsi"] > self.sell_rsi_min.value)

        if self.sell_ema_cross.value:
            exit_cond = exit_cond | (dataframe[ema_s] < dataframe[ema_l])

        dataframe.loc[exit_cond, "exit_long"] = 1
        return dataframe

    # ================================================================
    # CONFIRM TRADE EXIT (min hold)
    # ================================================================

    def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str,
                           amount: float, rate: float, time_in_force: str,
                           exit_reason: str, current_time: datetime, **kwargs) -> bool:
        hold_min = (current_time - trade.open_date_utc).total_seconds() / 60
        profit_pct = trade.calc_profit_ratio(rate) * 100

        # Emergency: always exit on big loss
        if profit_pct <= -2.5:
            return True

        # Block instant exits < 3 min (except stoploss)
        if exit_reason == "exit_signal" and hold_min < 3:
            return False

        return True
