# source: https://raw.githubusercontent.com/aragier/tradingStrategies/ba3f2fd3be7a23d51a2f518cb043f0db05091e41/user_data/strategies/BtcEma9_21Scalp_v2.py
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these imports ---
import numpy as np
import pandas as pd
from datetime import datetime, timedelta, timezone
from pandas import DataFrame
from typing import Optional, Union

from freqtrade.strategy import (
    IStrategy,
    Trade,
    Order,
    PairLocks,
    informative,
    BooleanParameter,
    CategoricalParameter,
    DecimalParameter,
    IntParameter,
    RealParameter,
    timeframe_to_minutes,
    timeframe_to_next_date,
    timeframe_to_prev_date,
    merge_informative_pair,
    stoploss_from_absolute,
    stoploss_from_open,
)

# --------------------------------
import talib.abstract as ta
from technical import qtpylib


class Github_aragier_tradingStrategies__BtcEma9_21Scalp_v2__20260327_161149(IStrategy):
    """
    EMA 9/21 Crossover Scalping Strategy v2 — with multi-timeframe filter,
    MACD-confirmed exits, and volume/RSI improvements.

    Improvements over v1:
    - 1h EMA50 trend direction filter (only trade with the trend)
    - Volume > 1.5x SMA(20) filter on entry
    - Tighter RSI bands: 50-70 for long, 30-50 for short
    - Exit signal requires MACD histogram confirmation
    - RSI extreme exits (>75 long, <25 short)

    Designed for 5m timeframe, BTC futures (long + short).
    """

    INTERFACE_VERSION = 3

    can_short: bool = True

    # --- Minimal ROI ---
    minimal_roi = {
        "30": 0.01,
        "15": 0.02,
        "0": 0.03,
    }

    # --- Stoploss ---
    stoploss = -0.02

    # --- Trailing Stop ---
    trailing_stop = True
    trailing_stop_positive = 0.005
    trailing_stop_positive_offset = 0.01
    trailing_only_offset_is_reached = True

    # --- Timeframe ---
    timeframe = "5m"

    # --- General settings ---
    process_only_new_candles = True
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    # --- Hyperoptable Parameters ---
    # EMA periods
    ema_fast_period = IntParameter(5, 15, default=9, space="buy", optimize=True, load=True)
    ema_slow_period = IntParameter(15, 30, default=21, space="buy", optimize=True, load=True)

    # RSI entry thresholds
    rsi_long_min = IntParameter(45, 55, default=50, space="buy", optimize=True, load=True)
    rsi_long_max = IntParameter(65, 80, default=70, space="buy", optimize=True, load=True)
    rsi_short_min = IntParameter(20, 35, default=30, space="sell", optimize=True, load=True)
    rsi_short_max = IntParameter(45, 55, default=50, space="sell", optimize=True, load=True)

    # RSI exit extremes
    rsi_exit_long = IntParameter(70, 85, default=75, space="sell", optimize=True, load=True)
    rsi_exit_short = IntParameter(15, 30, default=25, space="sell", optimize=True, load=True)

    # ADX threshold
    adx_threshold = IntParameter(20, 35, default=25, space="buy", optimize=True, load=True)

    # Volume multiplier
    volume_factor = DecimalParameter(1.0, 2.5, default=1.5, decimals=1, space="buy", optimize=True, load=True)

    # 1h EMA trend lookback (how many 1h candles back to compare for trend direction)
    ema_1h_lookback = IntParameter(2, 6, default=3, space="buy", optimize=True, load=True)

    # Startup candles: 1h EMA50 needs 50 candles on 1h = 50*12 = 600 on 5m, plus buffer
    startup_candle_count: int = 650

    # --- Order types ---
    order_types = {
        "entry": "limit",
        "exit": "limit",
        "stoploss": "market",
        "stoploss_on_exchange": False,
    }

    order_time_in_force = {"entry": "GTC", "exit": "GTC"}

    # --- Plot config for FreqUI ---
    plot_config = {
        "main_plot": {
            "ema_fast": {"color": "blue"},
            "ema_slow": {"color": "orange"},
        },
        "subplots": {
            "RSI": {
                "rsi": {"color": "red"},
            },
            "ADX": {
                "adx": {"color": "purple"},
            },
            "MACD": {
                "macdhist": {"color": "green", "type": "bar"},
            },
        },
    }

    # --- 1h Informative Timeframe ---
    @informative("1h")
    def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50)
        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # EMA - Fast and Slow
        dataframe["ema_fast"] = ta.EMA(dataframe, timeperiod=self.ema_fast_period.value)
        dataframe["ema_slow"] = ta.EMA(dataframe, timeperiod=self.ema_slow_period.value)

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

        # ADX - trend strength
        dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)

        # MACD - for exit confirmation
        macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
        dataframe["macd"] = macd["macd"]
        dataframe["macdsignal"] = macd["macdsignal"]
        dataframe["macdhist"] = macd["macdhist"]

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

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # --- Long Entry ---
        # EMA9 crosses above EMA21
        # + 1h EMA50 rising (bullish higher-timeframe trend)
        # + RSI in 50-70 range (momentum up, not exhausted)
        # + ADX > threshold (trending)
        # + Volume spike (conviction)
        dataframe.loc[
            (
                (qtpylib.crossed_above(dataframe["ema_fast"], dataframe["ema_slow"]))
                & (dataframe["ema_50_1h"] > dataframe["ema_50_1h"].shift(self.ema_1h_lookback.value))
                & (dataframe["rsi"] > self.rsi_long_min.value)
                & (dataframe["rsi"] < self.rsi_long_max.value)
                & (dataframe["adx"] > self.adx_threshold.value)
                & (dataframe["volume"] > (dataframe["volume_sma"] * self.volume_factor.value))
            ),
            "enter_long",
        ] = 1

        # --- Short Entry ---
        # EMA9 crosses below EMA21
        # + 1h EMA50 falling (bearish higher-timeframe trend)
        # + RSI in 30-50 range (momentum down, not oversold)
        # + ADX > threshold (trending)
        # + Volume spike (conviction)
        dataframe.loc[
            (
                (qtpylib.crossed_below(dataframe["ema_fast"], dataframe["ema_slow"]))
                & (dataframe["ema_50_1h"] < dataframe["ema_50_1h"].shift(self.ema_1h_lookback.value))
                & (dataframe["rsi"] > self.rsi_short_min.value)
                & (dataframe["rsi"] < self.rsi_short_max.value)
                & (dataframe["adx"] > self.adx_threshold.value)
                & (dataframe["volume"] > (dataframe["volume_sma"] * self.volume_factor.value))
            ),
            "enter_short",
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # --- Exit Long ---
        # Option A: Confirmed reversal — EMA crosses back AND MACD histogram negative
        # Option B: RSI extreme — momentum exhaustion take-profit
        dataframe.loc[
            (
                (
                    (qtpylib.crossed_below(dataframe["ema_fast"], dataframe["ema_slow"]))
                    & (dataframe["macdhist"] < 0)
                )
                | (dataframe["rsi"] > self.rsi_exit_long.value)
            )
            & (dataframe["volume"] > 0),
            "exit_long",
        ] = 1

        # --- Exit Short ---
        # Option A: Confirmed reversal — EMA crosses back AND MACD histogram positive
        # Option B: RSI extreme — momentum exhaustion take-profit
        dataframe.loc[
            (
                (
                    (qtpylib.crossed_above(dataframe["ema_fast"], dataframe["ema_slow"]))
                    & (dataframe["macdhist"] > 0)
                )
                | (dataframe["rsi"] < self.rsi_exit_short.value)
            )
            & (dataframe["volume"] > 0),
            "exit_short",
        ] = 1

        return dataframe
