# source: https://raw.githubusercontent.com/DerSalvador/freqtrade-helm-chart/a669dc11b640b0eb63aa8f8b51e9f181fd7ee43c/chart/deployed_strategies/binance-futures-k8s-namespace/abbas.py
from freqtrade.strategy.interface import IStrategy
from typing import Dict, List
from functools import reduce
from pandas import DataFrame, Series
# --------------------------------
import talib.abstract as ta
import numpy as np
import freqtrade.vendor.qtpylib.indicators as qtpylib
import datetime
from technical.util import resample_to_interval, resampled_merge
from datetime import datetime, timedelta
from freqtrade.persistence import Trade
from freqtrade.strategy import stoploss_from_open, merge_informative_pair, DecimalParameter, IntParameter, CategoricalParameter, BooleanParameter
import technical.indicators as ftt
from freqtrade.exchange import timeframe_to_prev_date
from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal, Real
# Protection hyperspace params:
protection_params = {'cooldown_stop_duration_candles': 1, 'lowprofit2_lookback_period_candles': 107, 'lowprofit2_required_profit': 0.026, 'lowprofit2_stop_duration_candles': 8, 'lowprofit2_trade_limit': 6, 'lowprofit_lookback_period_candles': 34, 'lowprofit_required_profit': 0.027, 'lowprofit_stop_duration_candles': 126, 'lowprofit_trade_limit': 36, 'maxdrawdown_lookback_period_candles': 36, 'maxdrawdown_max_allowed_drawdown': 0.34, 'maxdrawdown_stop_duration_candles': 54, 'maxdrawdown_trade_limit': 17, 'stoplossguard_lookback_period_candles': 3, 'stoplossguard_stop_duration_candles': 8, 'stoplossguard_trade_limit': 11}
# Buy hyperspace params:
entry_params = {'base_nb_candles_entry': 17, 'ewo_high': 2.182, 'ewo_high_2': -3.44, 'ewo_low': -10.26, 'low_offset': 1.066, 'low_offset_2': 0.961, 'rsi_entry': 68, 'min_profit': 1.03}
# Sell hyperspace params:
exit_params = {'base_nb_candles_exit': 9, 'high_offset': 1.01, 'high_offset_2': 1.233, 'high_offset_ema': 0.931}

class Github_DerSalvador_freqtrade_helm_chart__abbas__20260115_122204(IStrategy):
    INTERFACE_VERSION = 3
    cooldown_stop_duration_candles = IntParameter(0, 20, default=protection_params['cooldown_stop_duration_candles'], space='protection', optimize=True)
    maxdrawdown_optimize = True
    maxdrawdown_lookback_period_candles = IntParameter(5, 40, default=protection_params['maxdrawdown_lookback_period_candles'], space='protection', optimize=maxdrawdown_optimize)
    maxdrawdown_trade_limit = IntParameter(1, 20, default=protection_params['maxdrawdown_trade_limit'], space='protection', optimize=maxdrawdown_optimize)
    maxdrawdown_stop_duration_candles = IntParameter(10, 60, default=protection_params['maxdrawdown_stop_duration_candles'], space='protection', optimize=maxdrawdown_optimize)
    maxdrawdown_max_allowed_drawdown = DecimalParameter(0.05, 0.4, default=protection_params['maxdrawdown_max_allowed_drawdown'], space='protection', decimals=2, optimize=maxdrawdown_optimize)
    stoplossguard_optimize = True
    stoplossguard_lookback_period_candles = IntParameter(1, 300, default=protection_params['stoplossguard_lookback_period_candles'], space='protection', optimize=stoplossguard_optimize)
    stoplossguard_trade_limit = IntParameter(1, 20, default=protection_params['stoplossguard_trade_limit'], space='protection', optimize=stoplossguard_optimize)
    stoplossguard_stop_duration_candles = IntParameter(1, 10, default=protection_params['stoplossguard_stop_duration_candles'], space='protection', optimize=stoplossguard_optimize)
    lowprofit_optimize = True
    lowprofit_lookback_period_candles = IntParameter(10, 60, default=protection_params['lowprofit_lookback_period_candles'], space='protection', optimize=lowprofit_optimize)
    lowprofit_trade_limit = IntParameter(1, 50, default=protection_params['lowprofit_trade_limit'], space='protection', optimize=lowprofit_optimize)
    lowprofit_stop_duration_candles = IntParameter(10, 200, default=protection_params['lowprofit_stop_duration_candles'], space='protection', optimize=lowprofit_optimize)
    lowprofit_required_profit = DecimalParameter(0.0, 0.05, default=protection_params['lowprofit_required_profit'], space='protection', decimals=3, optimize=lowprofit_optimize)
    lowprofit2_optimize = True
    lowprofit2_lookback_period_candles = IntParameter(10, 300, default=protection_params['lowprofit2_lookback_period_candles'], space='protection', optimize=lowprofit2_optimize)
    lowprofit2_trade_limit = IntParameter(1, 70, default=protection_params['lowprofit2_trade_limit'], space='protection', optimize=lowprofit2_optimize)
    lowprofit2_stop_duration_candles = IntParameter(1, 40, default=protection_params['lowprofit2_stop_duration_candles'], space='protection', optimize=lowprofit2_optimize)
    lowprofit2_required_profit = DecimalParameter(0, 0.03, default=protection_params['lowprofit2_required_profit'], space='protection', decimals=3, optimize=lowprofit2_optimize)

    @property
    def protections(self):
        prot = []
        prot.append({'method': 'CooldownPeriod', 'stop_duration_candles': self.cooldown_stop_duration_candles.value})
        prot.append({'method': 'MaxDrawdown', 'lookback_period_candles': self.maxdrawdown_lookback_period_candles.value, 'trade_limit': self.maxdrawdown_trade_limit.value, 'stop_duration_candles': self.maxdrawdown_stop_duration_candles.value, 'max_allowed_drawdown': self.maxdrawdown_max_allowed_drawdown.value})
        prot.append({'method': 'StoplossGuard', 'lookback_period_candles': self.stoplossguard_lookback_period_candles.value, 'trade_limit': self.stoplossguard_trade_limit.value, 'stop_duration_candles': self.stoplossguard_stop_duration_candles.value, 'only_per_pair': False})
        prot.append({'method': 'LowProfitPairs', 'lookback_period_candles': self.lowprofit_lookback_period_candles.value, 'trade_limit': self.lowprofit_trade_limit.value, 'stop_duration_candles': self.lowprofit_stop_duration_candles.value, 'required_profit': self.lowprofit_required_profit.value})
        prot.append({'method': 'LowProfitPairs', 'lookback_period_candles': self.lowprofit2_lookback_period_candles.value, 'trade_limit': self.lowprofit2_trade_limit.value, 'stop_duration_candles': self.lowprofit2_stop_duration_candles.value, 'required_profit': self.lowprofit2_required_profit.value})
        return prot

    class HyperOpt:
        # Define a custom stoploss space.

        def stoploss_space():
            return [SKDecimal(-0.08, -0.03, decimals=3, name='stoploss')]
        # Define custom trailing space

        def trailing_space() -> List[Dimension]:
            return [Categorical([True, False], name='trailing_stop'), SKDecimal(0.0001, 0.001, decimals=5, name='trailing_stop_positive'), SKDecimal(0.008, 0.018, decimals=4, name='trailing_stop_positive_offset_p1'), Categorical([True, False], name='trailing_only_offset_is_reached')]
    # ROI table:
    minimal_roi = {'200': 0}
    # Stoploss:
    stoploss = -0.04
    # Trailing stop:
    trailing_stop = True
    trailing_stop_positive = 0.00013
    trailing_stop_positive_offset = 0.00963
    trailing_only_offset_is_reached = True
    # Sell signal
    use_exit_signal = False
    ignore_roi_if_entry_signal = False
    # SMAOffset
    smaoffset_optimize = True
    high_offset_ema = DecimalParameter(0.9, 1.1, default=exit_params['high_offset_ema'], load=True, space='exit', decimals=3, optimize=smaoffset_optimize)
    base_nb_candles_entry = IntParameter(15, 30, default=entry_params['base_nb_candles_entry'], space='entry', optimize=smaoffset_optimize)
    base_nb_candles_exit = IntParameter(5, 30, default=exit_params['base_nb_candles_exit'], space='exit', optimize=smaoffset_optimize)
    low_offset = DecimalParameter(1.0, 1.1, default=entry_params['low_offset'], space='entry', decimals=3, optimize=smaoffset_optimize)
    low_offset_2 = DecimalParameter(0.94, 0.98, default=entry_params['low_offset_2'], space='entry', decimals=3, optimize=smaoffset_optimize)
    high_offset = DecimalParameter(1.0, 1.1, default=exit_params['high_offset'], space='exit', decimals=3, optimize=smaoffset_optimize)
    high_offset_2 = DecimalParameter(1.2, 1.5, default=exit_params['high_offset_2'], space='exit', decimals=3, optimize=smaoffset_optimize)
    # Protection
    fast_ewo = 50
    slow_ewo = 200
    protection_optimize = True
    ewo_low = DecimalParameter(-12.0, -8.0, default=entry_params['ewo_low'], space='entry', decimals=2, optimize=protection_optimize)
    ewo_high = DecimalParameter(1.0, 2.2, default=entry_params['ewo_high'], space='entry', decimals=3, optimize=protection_optimize)
    ewo_high_2 = DecimalParameter(-4.0, -2.0, default=entry_params['ewo_high_2'], space='entry', decimals=2, optimize=protection_optimize)
    rsi_entry = IntParameter(55, 85, default=entry_params['rsi_entry'], space='entry', optimize=protection_optimize)
    min_profit = DecimalParameter(0.7, 1.2, default=entry_params['min_profit'], space='entry', decimals=2, optimize=protection_optimize)
    # Optional order time in force.
    order_time_in_force = {'entry': 'gtc', 'exit': 'ioc'}
    # Optimal timeframe for the strategy
    timeframe = '5m'
    inf_1h = '1h'
    process_only_new_candles = True
    startup_candle_count = 200
    plot_config = {'main_plot': {'bb_upperband28': {'color': '#bc281d', 'type': 'line'}, 'bb_midband28': {'color': 'orange', 'type': 'line'}, 'bb_lowerband28': {'color': '#792bbb', 'type': 'line'}}, 'subplots': {'RSI': {'rsi': {'color': 'yellow'}}}}
    slippage_protection = {'retries': 3, 'max_slippage': -0.002}
    entry_signals = {}

    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:
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        # slippage
        try:
            state = self.slippage_protection['__pair_retries']
        except KeyError:
            state = self.slippage_protection['__pair_retries'] = {}
        candle = dataframe.iloc[-1].squeeze()
        slippage = rate / candle['close'] - 1
        if slippage < self.slippage_protection['max_slippage']:
            pair_retries = state.get(pair, 0)
            if pair_retries < self.slippage_protection['retries']:
                state[pair] = pair_retries + 1
                return False
        state[pair] = 0
        return True

    def informative_pairs(self):
        # get access to all pairs available in whitelist.
        pairs = self.dp.current_whitelist()
        # Assign tf to each pair so they can be downloaded and cached for strategy.
        informative_pairs = [(pair, '1h') for pair in pairs]
        return informative_pairs

    def informative_1h_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        assert self.dp, 'DataProvider is required for multiple timeframes.'
        # Get the informative pair
        informative_1h = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=self.inf_1h)
        informative_1h['hma_50'] = qtpylib.hull_moving_average(informative_1h['close'], window=50)
        informative_1h['ema_100'] = ta.EMA(informative_1h, timeperiod=100)
        informative_1h['ema_12'] = ta.EMA(informative_1h, timeperiod=12)
        informative_1h['ema_20'] = ta.EMA(informative_1h, timeperiod=20)
        informative_1h['ema_26'] = ta.EMA(informative_1h, timeperiod=26)
        informative_1h['ema_50'] = ta.EMA(informative_1h, timeperiod=50)
        informative_1h['ema_200'] = ta.EMA(informative_1h, timeperiod=200)
        informative_1h['sma_200'] = ta.SMA(informative_1h, timeperiod=200)
        informative_1h['sma_200_dec'] = informative_1h['sma_200'] < informative_1h['sma_200'].shift(20)
        informative_1h['sma_9'] = ta.SMA(informative_1h, timeperiod=9)
        # Elliot
        informative_1h['EWO'] = EWO(informative_1h, self.fast_ewo, self.slow_ewo)
        # RSI
        informative_1h['rsi'] = ta.RSI(informative_1h, timeperiod=14)
        informative_1h['rsi_fast'] = ta.RSI(informative_1h, timeperiod=4)
        informative_1h['rsi_slow'] = ta.RSI(informative_1h, timeperiod=20)
        return informative_1h

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Calculate all ma_entry values
        for val in self.base_nb_candles_entry.range:
            dataframe[f'ma_entry_{val}'] = ta.EMA(dataframe, timeperiod=val)
        # Calculate all ma_exit values
        for val in self.base_nb_candles_exit.range:
            dataframe[f'ma_exit_{val}'] = ta.EMA(dataframe, timeperiod=val)
        dataframe['hma_50'] = qtpylib.hull_moving_average(dataframe['close'], window=50)
        dataframe['ema_100'] = ta.EMA(dataframe, timeperiod=100)
        dataframe['ema_12'] = ta.EMA(dataframe, timeperiod=12)
        dataframe['ema_20'] = ta.EMA(dataframe, timeperiod=20)
        dataframe['ema_26'] = ta.EMA(dataframe, timeperiod=26)
        dataframe['ema_50'] = ta.EMA(dataframe, timeperiod=50)
        dataframe['ema_200'] = ta.EMA(dataframe, timeperiod=200)
        dataframe['sma_200'] = ta.SMA(dataframe, timeperiod=200)
        dataframe['sma_200_dec'] = dataframe['sma_200'] < dataframe['sma_200'].shift(20)
        dataframe['sma_9'] = ta.SMA(dataframe, timeperiod=9)
        # Elliot
        dataframe['EWO'] = EWO(dataframe, self.fast_ewo, self.slow_ewo)
        # RSI
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['rsi_fast'] = ta.RSI(dataframe, timeperiod=4)
        dataframe['rsi_slow'] = ta.RSI(dataframe, timeperiod=20)
        # Bollinger bands
        bollinger2 = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2.8)
        dataframe['bb_lowerband28'] = bollinger2['lower']
        dataframe['bb_middleband28'] = bollinger2['mid']
        dataframe['bb_upperband28'] = bollinger2['upper']
        informative_1h = self.informative_1h_indicators(dataframe, metadata)
        dataframe = merge_informative_pair(dataframe, informative_1h, self.timeframe, self.inf_1h, ffill=True)
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[(dataframe['rsi_fast'] < 35) & (dataframe['close'] < dataframe[f'ma_entry_{self.base_nb_candles_entry.value}'] * self.low_offset.value) & (dataframe['EWO'] > self.ewo_high.value) & (dataframe['rsi'] < self.rsi_entry.value) & (dataframe['volume'] > 0) & (dataframe['close'] < dataframe[f'ma_exit_{self.base_nb_candles_exit.value}'] * self.high_offset.value), ['enter_long', 'enter_tag']] = (1, 'ewo1')
        dataframe.loc[(dataframe['rsi_fast'] < 35) & (dataframe['close'] < dataframe[f'ma_entry_{self.base_nb_candles_entry.value}'] * self.low_offset_2.value) & (dataframe['EWO'] > self.ewo_high_2.value) & (dataframe['rsi'] < self.rsi_entry.value) & (dataframe['volume'] > 0) & (dataframe['close'] < dataframe[f'ma_exit_{self.base_nb_candles_exit.value}'] * self.high_offset.value) & (dataframe['rsi'] < 25), ['enter_long', 'enter_tag']] = (1, 'ewo2')
        dataframe.loc[(dataframe['rsi_fast'] < 35) & (dataframe['close'] < dataframe[f'ma_entry_{self.base_nb_candles_entry.value}'] * self.low_offset.value) & (dataframe['EWO'] < self.ewo_low.value) & (dataframe['volume'] > 0) & (dataframe['close'] < dataframe[f'ma_exit_{self.base_nb_candles_exit.value}'] * self.high_offset.value), ['enter_long', 'enter_tag']] = (1, 'ewolow')
        dont_entry_conditions = []
        dont_entry_conditions.append(dataframe['close_1h'].rolling(24).max() < dataframe['close'] * self.min_profit.value)
        if dont_entry_conditions:
            for condition in dont_entry_conditions:
                dataframe.loc[condition, 'enter_long'] = 0
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        return dataframe

def EWO(dataframe, ema_length=5, ema2_length=35):
    df = dataframe.copy()
    ema1 = ta.EMA(df, timeperiod=ema_length)
    ema2 = ta.EMA(df, timeperiod=ema2_length)
    emadif = (ema1 - ema2) / df['low'] * 100
    return emadif