# source: https://raw.githubusercontent.com/remiotore/freqtrade/44beaeb6a420cd8e9f2e4ea93e11d6cfa192ee03/strategies/turbov8_2.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 datetime
from technical.util import resample_to_interval, resampled_merge
from datetime import datetime, timedelta
from freqtrade.persistence import Trade
import technical.indicators as ftt
from technical import qtpylib
import freqtrade.vendor.qtpylib.indicators as qtpylib
import logging
import pandas as pd
import pandas_ta as pta
import datetime
from datetime import datetime, timedelta, timezone
from typing import Optional
import talib.abstract as ta
from technical.util import resample_to_interval, resampled_merge
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, 
                                IStrategy, IntParameter, RealParameter, merge_informative_pair,
                                stoploss_from_open)


buy_params = {
        "base_nb_candles_buy": 12,
        "ewo_high": 3.147,
        "ewo_low": -17.145,
        "low_offset": 0.987,
        "rsi_buy": 57,
    }

sell_params = {
        "base_nb_candles_sell": 22,
        "high_offset": 1.008,
        "high_offset_2": 1.016,
    }

def EWO(dataframe, ema_length=5, ema2_length=3):
    df = dataframe.copy()
    ema1 = ta.EMA(df, timeperiod=ema_length)
    ema2 = ta.EMA(df, timeperiod=ema2_length)
    emadif = (ema1 - ema2) / df['close'] * 100
    return emadif



class Github_remiotore_freqtrade__turbov8_2__20260111_210550(IStrategy):
    INTERFACE_VERSION = 2

    exit_profit_only = True ### No selling at a loss
    use_custom_stoploss = True
    trailing_stop = False # True
    ignore_roi_if_entry_signal = True
    use_exit_signal = True
    startup_candle_count = 400
    run_from_bear = 0

    stoploss = -0.20

    position_adjustment_enable = True
    max_entry_position_adjustment = 3
    max_dca_multiplier = 1.5

    order_time_in_force = {
        'buy': 'gtc',
        'sell': 'gtc'
    }

    minimal_roi = {
        "0": 0.99,
    }

    timeframe = '5m'
    informative_timeframe = '1h'

    @property
    def protections(self):
        prot = []

        prot.append({
            "method": "CooldownPeriod",
            "stop_duration_candles": self.cooldown_lookback.value
        })
        if self.use_stop_protection.value:
            prot.append({
                "method": "StoplossGuard",
                "lookback_period_candles": 24 * 3,
                "trade_limit": 1,
                "stop_duration_candles": self.stop_duration.value,
                "only_per_pair": False
            })

        return prot


    max_epa = CategoricalParameter([0, 1, 2, 3], default=3, space="buy", optimize=True)

    cooldown_lookback = IntParameter(24, 48, default=46, space="protection", optimize=True)
    stop_duration = IntParameter(12, 200, default=5, space="protection", optimize=True)
    use_stop_protection = BooleanParameter(default=True, space="protection", optimize=True)

    base_nb_candles_buy = IntParameter(5, 80, default=buy_params['base_nb_candles_buy'], space='buy', optimize=True)
    base_nb_candles_sell = IntParameter(5, 80, default=sell_params['base_nb_candles_sell'], space='sell', optimize=True)
    low_offset = DecimalParameter(0.9, 0.99, default=buy_params['low_offset'], space='buy', optimize=True)
    high_offset = DecimalParameter(0.95, 1.1, default=sell_params['high_offset'], space='sell', optimize=True)
    high_offset_2 = DecimalParameter(0.99, 1.5, default=sell_params['high_offset_2'], space='sell', optimize=True)   

    fast_ewo = 50
    slow_ewo = 200
    ewo_low = DecimalParameter(-20.0, -8.0, default=buy_params['ewo_low'], space='buy', optimize=True)
    ewo_high = DecimalParameter(2.0, 12.0, default=buy_params['ewo_high'], space='buy', optimize=True)
    rsi_buy = IntParameter(30, 70, default=buy_params['rsi_buy'], space='buy', optimize=True)

    fast_1h = IntParameter(3, 8, default=5, space='buy', optimize=True)
    slow_1h = IntParameter(25, 40, default=30, space='buy', optimize=True)

    dca1 = DecimalParameter(low=0.01, high=0.03, decimals=2, default=0.02, space='buy', optimize=True, load=True)
    dca2 = DecimalParameter(low=0.03, high=0.05, decimals=2, default=0.04, space='buy', optimize=True, load=True)
    dca3 = DecimalParameter(low=0.05, high=0.07, decimals=2, default=0.06, space='buy', optimize=True, load=True)

    tsl_target5 = DecimalParameter(low=0.3, high=0.4, decimals=1, default=0.3, space='sell', optimize=True, load=True)
    ts5 = DecimalParameter(low=0.04, high=0.06, default=0.05, space='sell', optimize=True, load=True)
    tsl_target4 = DecimalParameter(low=0.18, high=0.3, default=0.2, space='sell', optimize=True, load=True)
    ts4 = DecimalParameter(low=0.03, high=0.05, default=0.045, space='sell', optimize=True, load=True)
    tsl_target3 = DecimalParameter(low=0.12, high=0.18, default=0.15, space='sell', optimize=True, load=True)
    ts3 = DecimalParameter(low=0.025, high=0.04, default=0.035, space='sell', optimize=True, load=True)
    tsl_target2 = DecimalParameter(low=0.07, high=0.12, default=0.1, space='sell', optimize=True, load=True)
    ts2 = DecimalParameter(low=0.015, high=0.03, default=0.02, space='sell', optimize=True, load=True)
    tsl_target1 = DecimalParameter(low=0.04, high=0.07, default=0.06, space='sell', optimize=True, load=True)
    ts1 = DecimalParameter(low=0.01, high=0.016, default=0.013, space='sell', optimize=True, load=True)
    tsl_target0 = DecimalParameter(low=0.02, high=0.05, default=0.03, space='sell', optimize=True, load=True)
    ts0 = DecimalParameter(low=0.008, high=0.015, default=0.013, space='sell', optimize=True, load=True)


    def informative_pairs(self):

        pairs = self.dp.current_whitelist()
        pairs += ['BTC/USDT']
        informative_pairs = [(pair, self.informative_timeframe) for pair in pairs]
        return informative_pairs

    def get_informative_indicators(self, metadata: dict):

        dataframe = self.dp.get_pair_dataframe(
            pair=metadata['pair'], timeframe=self.informative_timeframe)

        return dataframe


    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                            proposed_stake: float, min_stake: Optional[float], max_stake: float,
                            leverage: float, entry_tag: Optional[str], side: str,
                            **kwargs) -> float:


        return proposed_stake / self.max_dca_multiplier 

    def adjust_trade_position(self, trade: Trade, current_time: datetime,
                              current_rate: float, current_profit: float,
                              min_stake: Optional[float], max_stake: float,
                              current_entry_rate: float, current_exit_rate: float,
                              current_entry_profit: float, current_exit_profit: float,
                              **kwargs) -> Optional[float]:

        if current_profit > 0.10 and trade.nr_of_successful_exits == 0:

            return -(trade.stake_amount / 2)

        if current_profit > -(self.dca1.value) and trade.nr_of_successful_entries == 1:
            return None

        if current_profit > -(self.dca2.value) and trade.nr_of_successful_entries == 2:
            return None

        if current_profit > -(self.dca3.value) and trade.nr_of_successful_entries == 3:
            return None

        dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
        filled_entries = trade.select_filled_orders(trade.entry_side)
        count_of_entries = trade.nr_of_successful_entries

        try:

            stake_amount = filled_entries[0].cost

            if count_of_entries == 1: 
                stake_amount = stake_amount * 0.166
            elif count_of_entries == 2:
                stake_amount = stake_amount * 0.166
            elif count_of_entries == 3:
                stake_amount = stake_amount * 0.166
            else:
                stake_amount = stake_amount

            return stake_amount
        except Exception as exception:
            return None

        return None

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, **kwargs) -> float:

        for stop5 in self.tsl_target5.range:
            if (current_profit > stop5):
                for stop5a in self.ts5.range:
                    self.dp.send_msg(f'*** FEEDBACK *** Pair {pair} profit {current_profit} - lvl5 {stop5a} stoploss activated')
                    return stop5a 
        for stop4 in self.tsl_target4.range:
            if (current_profit > stop4):
                for stop4a in self.ts4.range:
                    self.dp.send_msg(f'*** FEEDBACK *** Pair {pair} profit {current_profit} - lvl4 {stop4a} stoploss activated')
                    return stop4a 
        for stop3 in self.tsl_target3.range:
            if (current_profit > stop3):
                for stop3a in self.ts3.range:
                    self.dp.send_msg(f'*** FEEDBACK *** Pair {pair} profit {current_profit} - lvl3 {stop3a} stoploss activated')
                    return stop3a 
        for stop2 in self.tsl_target2.range:
            if (current_profit > stop2):
                for stop2a in self.ts2.range:
                    self.dp.send_msg(f'*** FEEDBACK *** Pair {pair} profit {current_profit} - lvl2 {stop2a} stoploss activated')
                    return stop2a 
        for stop1 in self.tsl_target1.range:
            if (current_profit > stop1):
                for stop1a in self.ts1.range:
                    self.dp.send_msg(f'*** FEEDBACK *** Pair {pair} profit {current_profit} - lvl1 {stop1a} stoploss activated')
                    return stop1a 
        for stop0 in self.tsl_target0.range:
            if (current_profit > stop0):
                for stop0a in self.ts0.range:
                    self.dp.send_msg(f'*** FEEDBACK *** Pair {pair} profit {current_profit} - lvl0 {stop0a} stoploss activated')
                    return stop0a 

        return self.stoploss


    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        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)

        if self.dp:
            inf_tf = '1h'
            informative = self.dp.get_pair_dataframe(pair=f"BTC/USDT", timeframe=inf_tf)
            for fast in self.fast_1h.range:
                informative[f'sma_{fast}'] = ta.SMA(informative["close"], timeperiod = fast)

            for slow in self.slow_1h.range:
                informative[f'sma_{slow}'] = ta.SMA(informative["close"], timeperiod = slow)

        
        dataframe['hma_50'] = qtpylib.hull_moving_average(dataframe['close'], window=50)

        dataframe['200_SMA'] = ta.SMA(dataframe["close"], timeperiod = 200)
        dataframe['30_SMA'] = ta.SMA(dataframe["close"], timeperiod = 30)
        dataframe['5_SMA'] = ta.SMA(dataframe["close"], timeperiod = 5)
        dataframe['sma_9'] = ta.SMA(dataframe, timeperiod=9)

        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)

        if (informative[f'sma_{fast}'].iloc[-1] > informative[f'sma_{slow}'].iloc[-1]):
            dataframe['Run_From_Bear'] = 0
            if (self.run_from_bear == 1):
                self.dp.send_msg(f"MARKET STATUS: Bear is gone! Time to wake up from hibernations...", always_send=True)
                self.run_from_bear = 0
        elif (informative[f'sma_{fast}'].iloc[-1] < informative[f'sma_{slow}'].iloc[-1]
            and informative[f'sma_{fast}'].iloc[-1] < informative[f'sma_{fast}'].iloc[-2]).all():
            dataframe['Run_From_Bear'] = 1
            if (self.run_from_bear == 0):
                self.dp.send_msg(f"MARKET STATUS: Bear sighted! Selling off and going into hibernation...", always_send=True)
                self.run_from_bear = 1    
        elif (informative[f'sma_{fast}'].iloc[-1] < informative[f'sma_{slow}'].iloc[-1]
            and informative[f'sma_{fast}'].iloc[-1] > informative[f'sma_{fast}'].iloc[-2]).all():
            dataframe['Run_From_Bear'] = -1
            self.dp.send_msg(f"MARKET STATUS: Bear getting sleepy! w3n m00n?...", always_send=True)
        else:
            dataframe['Run_From_Bear'] = -1
            self.dp.send_msg(f"MARKET STATUS: Bear Lurking! Grab the Lube, This could hurt...", always_send=True)

             
        if (dataframe['30_SMA'].iloc[-1] > dataframe['200_SMA'].iloc[-1] 
            and dataframe['30_SMA'].iloc[-1] > dataframe['30_SMA'].iloc[-2]
            and dataframe['200_SMA'].iloc[-1] > dataframe['200_SMA'].iloc[-2]).all():
            self.max_epa.value = 1
        elif (dataframe['30_SMA'].iloc[-1] > dataframe['200_SMA'].iloc[-1] 
            and dataframe['30_SMA'].iloc[-1] > dataframe['30_SMA'].iloc[-2].all()):
            self.max_epa.value = 1
        elif (dataframe['30_SMA'].iloc[-1] < dataframe['200_SMA'].iloc[-1] 
            and dataframe['30_SMA'].iloc[-1] > dataframe['30_SMA'].iloc[-2].all()):
            self.max_epa.value = 2
        else:
            self.max_epa.value = 2

        return dataframe

    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

        dataframe.loc[
            (
                (dataframe['rsi_fast'] <35) &
                (dataframe['close'] < (dataframe[f'ma_buy_{self.base_nb_candles_buy.value}'] * self.low_offset.value)) &
                (dataframe['EWO'] > self.ewo_high.value) &
                (dataframe['rsi'] < self.rsi_buy.value) &
                (dataframe['Run_From_Bear'] == 0) &
                (dataframe['volume'] > 0)
            ),
            ['enter_long', 'enter_tag']] = (1, 'EWO above high')


        dataframe.loc[
            (
                (dataframe['rsi_fast'] <35)&
                (dataframe['close'] < (dataframe[f'ma_buy_{self.base_nb_candles_buy.value}'] * self.low_offset.value)) &
                (dataframe['EWO'] > self.ewo_high.value) &
                (dataframe['rsi'] < self.rsi_buy.value) &
                (dataframe['close'] < (dataframe[f'ma_sell_{self.base_nb_candles_sell.value}'] * self.high_offset.value)) &
                (dataframe['Run_From_Bear'] == 0) &
                (dataframe['volume'] > 0)
            ),
            ['enter_long', 'enter_tag']] = (1, 'EWO below low')

        return dataframe

    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        dataframe.loc[
            (
                (dataframe['close'] > dataframe['hma_50']) &
                (dataframe['close'] > (dataframe[f'ma_sell_{self.base_nb_candles_sell.value}'] * self.high_offset_2.value)) &
                (dataframe['rsi']>50)&
                (dataframe['volume'] > 0)&
                (dataframe['rsi_fast']>dataframe['rsi_slow'])

            ),
            ['exit_long', 'exit_tag']] = (1, 'Close > Offset Hi')

        dataframe.loc[
            (
                (dataframe['close']<dataframe['hma_50'])&
                (dataframe['close'] > (dataframe[f'ma_sell_{self.base_nb_candles_sell.value}'] * self.high_offset.value)) &
                (dataframe['volume'] > 0)&
                (dataframe['rsi_fast']>dataframe['rsi_slow'])

            ),
            ['exit_long', 'exit_tag']] = (1, 'Close > Offset Lo')

        dataframe.loc[
            (
                (dataframe['Run_From_Bear'] == 1) &
                (dataframe['volume'] > 0)
            ),
            ['exit_long', 'exit_tag']] = (1, 'fucking bearzzz')

        return dataframe