# source: https://raw.githubusercontent.com/remiotore/freqtrade/44beaeb6a420cd8e9f2e4ea93e11d6cfa192ee03/strategies/ElliotV7_392_Optimized.py
from freqtrade.strategy.interface import IStrategy
from typing import Dict, List
from functools import reduce
from pandas import DataFrame

import talib.abstract as ta
import numpy as np
import freqtrade.vendor.qtpylib.indicators as qtpylib
from datetime import datetime, timedelta
from freqtrade.persistence import Trade
from freqtrade.strategy import merge_informative_pair
from freqtrade.strategy import DecimalParameter, IntParameter

# --------------------------------------
# Helper functions
# --------------------------------------

def EWO(dataframe: DataFrame, ema_length: int = 20, ema2_length: int = 200) -> DataFrame:
    """Elliot Wave Oscillator"""
    ema1 = ta.EMA(dataframe, timeperiod=ema_length)
    ema2 = ta.EMA(dataframe, timeperiod=ema2_length)
    return (ema1 - ema2) / dataframe['close'] * 100

# --------------------------------------
# Strategy class
# --------------------------------------

class Github_remiotore_freqtrade__ElliotV7_392_Optimized__20260111_210550(IStrategy):
    """5‑minute trend‑following / pullback hybrid, optimised for reduced drawdown"""

    INTERFACE_VERSION = 3

    timeframe = '5m'
    inf_1h = '1h'

    # --- Performance targets -------------------------------------------------
    minimal_roi = {
        "0": 0.03,   # 3 % immediately
        "30": 0.02,  # after 30 min allow 2 %
        "60": 0.01,  # after 60 min allow 1 %
        "120": 0     # after 120 min free‑ride
    }

    stoploss = -0.20  # emergency SL – custom_stoploss is primary

    trailing_stop = True
    trailing_stop_positive = 0.008   # 0.8 %
    trailing_stop_positive_offset = 0.08  # start trailing after 8 %
    trailing_only_offset_is_reached = True

    # --- Hyperopt parameters -------------------------------------------------
    base_nb_candles_buy = IntParameter(5, 80, default=14, space='buy', optimize=True)
    base_nb_candles_sell = IntParameter(5, 80, default=24, space='sell', optimize=True)
    low_offset = DecimalParameter(0.9, 0.99, default=0.975, space='buy', optimize=True)
    high_offset = DecimalParameter(0.95, 1.1, default=0.991, space='sell', optimize=True)
    high_offset_2 = DecimalParameter(0.99, 1.5, default=0.997, space='sell', optimize=True)

    fast_ewo = 50
    slow_ewo = 200
    ewo_low = DecimalParameter(-20.0, -8.0, default=-19.988, space='buy', optimize=True)
    ewo_high = DecimalParameter(2.0, 12.0, default=2.327, space='buy', optimize=True)
    rsi_buy = IntParameter(30, 70, default=69, space='buy', optimize=True)

    # --- Order execution -----------------------------------------------------
    order_types = {
        'buy': 'limit',
        'sell': 'limit',
        'stoploss': 'limit',
        'stoploss_on_exchange': True
    }
    order_time_in_force = {
        'buy': 'gtc',
        'sell': 'ioc'
    }

    # --- Protections ---------------------------------------------------------
    use_custom_stoploss = True
    process_only_new_candles = True
    startup_candle_count = 60

    plot_config = {
        'main_plot': {
            'ema_fast': {},
            'ema_slow': {},
            'hma_50': {'color': 'orange'},
            'atr': {'color': 'grey'}
        }
    }

    # ---------------------------------------------------------------------
    # Informative pairs / higher timeframe
    # ---------------------------------------------------------------------
    def informative_pairs(self):
        pairs = self.dp.current_whitelist()
        return [(pair, self.inf_1h) for pair in pairs]

    def informative_1h_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        assert self.dp, "DataProvider required"
        df = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=self.inf_1h)

        df['ema_fast'] = ta.EMA(df, timeperiod=20)
        df['ema_slow'] = ta.EMA(df, timeperiod=60)
        df['uptrend'] = ((df['ema_fast'] > df['ema_slow'] * 1.002)).astype('int')
        df['rsi_100'] = ta.RSI(df, timeperiod=100)
        return df

    # ---------------------------------------------------------------------
    # Indicators
    # ---------------------------------------------------------------------
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # merge informative 1h
        inf = self.informative_1h_indicators(dataframe, metadata)
        dataframe = merge_informative_pair(dataframe, inf, self.timeframe, self.inf_1h, ffill=True)

        # Moving averages for adaptive channels
        for val in self.base_nb_candles_buy.range:
            dataframe[f'ma_buy_{val}'] = ta.EMA(dataframe, timeperiod=val)
        for val in self.base_nb_candles_sell.range:
            dataframe[f'ma_sell_{val}'] = ta.EMA(dataframe, timeperiod=val)

        # Volatility – ATR
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
        dataframe['atr_percent'] = dataframe['atr'] / dataframe['close'] * 100

        # Trend metrics
        dataframe['EWO'] = EWO(dataframe, self.fast_ewo, self.slow_ewo)
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['rsi_fast'] = ta.RSI(dataframe, timeperiod=4)
        dataframe['rsi_slow'] = ta.RSI(dataframe, timeperiod=20)

        # Hull and SMA
        dataframe['hma_50'] = qtpylib.hull_moving_average(dataframe['close'], window=50)
        dataframe['sma_9'] = ta.SMA(dataframe, timeperiod=9)

        return dataframe

    # ---------------------------------------------------------------------
    # Buy Logic
    # ---------------------------------------------------------------------
    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        # Common filters
        cond_common = (
            (dataframe['volume'] > 0) &
            (dataframe['uptrend_1h'] > 0) &
            (dataframe['rsi_fast'] < 35) &
            (dataframe['atr_percent'] < 5)
        )

        # Pullback‑in‑uptrend (EWO high)
        conditions.append(
            cond_common &
            (dataframe['EWO'] > self.ewo_high.value) &
            (dataframe['rsi'] < self.rsi_buy.value) &
            (dataframe['close'] < dataframe[f'ma_buy_{self.base_nb_candles_buy.value}'] * self.low_offset.value) &
            (dataframe['close'] < dataframe[f'ma_sell_{self.base_nb_candles_sell.value}'] * self.high_offset.value)
        )

        # Deep pullback (EWO low)
        conditions.append(
            cond_common &
            (dataframe['EWO'] < self.ewo_low.value) &
            (dataframe['close'] < dataframe[f'ma_buy_{self.base_nb_candles_buy.value}'] * self.low_offset.value) &
            (dataframe['close'] < dataframe[f'ma_sell_{self.base_nb_candles_sell.value}'] * self.high_offset.value)
        )

        if conditions:
            dataframe.loc[reduce(lambda a, b: a | b, conditions), 'buy'] = 1
        return dataframe

    # ---------------------------------------------------------------------
    # Sell Logic
    # ---------------------------------------------------------------------
    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        cond_hma_cross = (
            (dataframe['sma_9'] > dataframe['hma_50']) &
            (dataframe['close'] > dataframe[f'ma_sell_{self.base_nb_candles_sell.value}'] * self.high_offset_2.value)
        ) | (
            (dataframe['sma_9'] < dataframe['hma_50']) &
            (dataframe['close'] > dataframe[f'ma_sell_{self.base_nb_candles_sell.value}'] * self.high_offset.value)
        )

        conditions.append(
            cond_hma_cross &
            (dataframe['volume'] > 0) &
            (dataframe['rsi_fast'] > dataframe['rsi_slow'])
        )

        if conditions:
            dataframe.loc[reduce(lambda a, b: a | b, conditions), 'sell'] = 1
        return dataframe

    # ---------------------------------------------------------------------
    # Custom Stoploss – ATR & time based
    # ---------------------------------------------------------------------
    def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
                        current_rate: float, current_profit: float, **kwargs) -> float:
        """Dynamic SL: tighten if position ages or deep in loss."""
        # Hard tighten after 90 min if still red beyond ‑5 %
        if current_profit < -0.05 and (current_time - trade.open_date_utc) > timedelta(minutes=90):
            return -0.015  # cut quickly

        # Universal ATR‑based fallback (approx 1.2 × ATR%)
        try:
            pair_df: DataFrame = self.dp.get_pair_dataframe(pair=pair, timeframe=self.timeframe)
            atr_pct = (ta.ATR(pair_df, timeperiod=14).iloc[-1] / current_rate)
            dynamic_sl = -1.2 * float(atr_pct)
            # Cap to emergency
            return max(dynamic_sl, self.stoploss)
        except Exception:
            return self.stoploss
