# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
from functools import reduce
import numpy as np
import pandas as pd
from datetime import datetime, timedelta, timezone
from pandas import DataFrame
from typing import Dict, Optional, Union, Tuple

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,
    AnnotationType,
)

import talib.abstract as ta
from technical import qtpylib


class TrendMomentumStrategy(IStrategy):
    """
    EMA Pullback Strategy.

    Philosophy: In an uptrend (price > EMA 200), buy when price pulls back
    to the EMA 50 and bounces. This is much better than chasing momentum.

    Entry:
    - Price > EMA 200 (uptrend confirmed)
    - Price dips below EMA 50 then closes back above it (pullback complete)
    - RSI < 65 (not overextended)

    Exit:
    - Trailing stop does the heavy lifting
    - Signal exit: RSI > 80 + price touches upper Bollinger Band
    """
    INTERFACE_VERSION = 3

    timeframe = "5m"
    can_short: bool = False

    minimal_roi = {}

    stoploss = -0.04

    trailing_stop = True
    trailing_only_offset_is_reached = True
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.02

    process_only_new_candles = True
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    startup_candle_count: int = 300

    # ===================== HYPEROPT PARAMETERS =====================

    buy_rsi_max = IntParameter(40, 70, default=60, space="buy", optimize=True)
    buy_ema_pullback_pct = RealParameter(0.5, 3.0, default=1.5, space="buy", optimize=True)
    sell_rsi_min = IntParameter(65, 85, default=78, space="sell", optimize=True)

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

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # ---- Trend ----
        dataframe['ema20'] = ta.EMA(dataframe, timeperiod=20)
        dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
        dataframe['ema200'] = ta.EMA(dataframe, timeperiod=200)

        # ---- Momentum ----
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)

        # ---- Bollinger Bands ----
        bollinger = qtpylib.bollinger_bands(
            qtpylib.typical_price(dataframe), window=20, stds=2
        )
        dataframe['bb_lowerband'] = bollinger['lower']
        dataframe['bb_middleband'] = bollinger['mid']
        dataframe['bb_upperband'] = bollinger['upper']

        # ---- MACD (for exit only) ----
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']
        dataframe['macdhist'] = macd['macdhist']

        # ---- Volume ----
        dataframe['volume_mean_20'] = dataframe['volume'].rolling(20).mean()
        dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_mean_20']

        # ---- Pullback detection: price was below EMA50, now back above ----
        dataframe['below_ema50'] = dataframe['low'] < dataframe['ema50']
        dataframe['above_ema50'] = dataframe['close'] > dataframe['ema50']

        return dataframe

    # ===================== ENTRY SIGNALS =====================

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                # In uptrend
                (dataframe['close'] > dataframe['ema200']) &
                # Pullback bounce: close above EMA50 and previous candle had low below EMA50
                (dataframe['above_ema50']) &
                (dataframe['below_ema50'].shift(1)) &
                # RSI not overbought
                (dataframe['rsi'] < self.buy_rsi_max.value) &
                # Volume exists
                (dataframe['volume'] > 0)
            ),
            'enter_long'
        ] = 1

        return dataframe

    # ===================== EXIT SIGNALS =====================

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                # RSI overbought
                (dataframe['rsi'] > self.sell_rsi_min.value) &
                # Price at upper Bollinger Band
                (dataframe['close'] > dataframe['bb_upperband']) &
                (dataframe['volume'] > 0)
            ),
            'exit_long'
        ] = 1

        return dataframe
