# source: https://raw.githubusercontent.com/remiotore/ccxt-freqtrade/44beaeb6a420cd8e9f2e4ea93e11d6cfa192ee03/strategies/pol313.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 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)
import talib.abstract as ta
import pandas_ta as pta
from technical import qtpylib
from pandas import Series , DataFrame
from collections import namedtuple


def rmi(dataframe:DataFrame , length=20, mom=5):
    df = dataframe.copy()
    df['maxup'] = (df['close'] - df['close'].shift(mom)).clip(lower=0)
    df['maxdown'] = (df['close'].shift(mom) - df['close']).clip(lower=0)
    df.fillna(0, inplace=True)
    df["emaInc"] = ta.EMA(df, price='maxup', timeperiod=length)
    df["emaDec"] = ta.EMA(df, price='maxdown', timeperiod=length)
    df['RMI'] = np.where(df['emaDec'] == 0, 0, 100 - 100 / (1 + df["emaInc"] / df["emaDec"]))
    return df["RMI"]

def zema(dataframe:DataFrame, period, field='close'):
    df = dataframe.copy()
    df['ema1'] = ta.EMA(df[field], timeperiod=period)
    df['ema2'] = ta.EMA(df['ema1'], timeperiod=period)
    df['d'] = df['ema1'] - df['ema2']
    df['zema'] = df['ema1'] + df['d']
    return df['zema']

def pcc(dataframe: DataFrame, period: int = 20, mult: int = 2):
    df = dataframe.copy()
    df['previous_close'] = df['close'].shift()
    df['close_change'] = (df['close'] - df['previous_close']) / df['previous_close'] * 100
    df['high_change'] = (df['high'] - df['close']) / df['close'] * 100
    df['low_change'] = (df['low'] - df['close']) / df['close'] * 100
    df['delta'] = df['high_change'] - df['low_change']
    mid = zema(df, period, 'close_change')
    rangema = zema(df, period, 'delta')
    upper = mid + rangema * mult
    lower = mid - rangema * mult
    return upper, rangema, lower

def ssl_channel_atr(dataframe:DataFrame, length=7):
    df = dataframe.copy()
    df['ATR'] = ta.ATR(df, timeperiod=14)
    df['smaHigh'] = df['high'].rolling(length).mean() + df['ATR']
    df['smaLow'] = df['low'].rolling(length).mean() - df['ATR']
    df['hlv'] = np.where(df['close'] > df['smaHigh'], 1, np.where(df['close'] < df['smaLow'], -1, np.NAN))
    df['hlv'] = df['hlv'].ffill()
    df['ssl_Down'] = np.where(df['hlv'] < 0, df['smaHigh'], df['smaLow'])
    df['ssl_Up'] = np.where(df['hlv'] < 0, df['smaLow'], df['smaHigh'])
    return df['ssl_Down'], df['ssl_Up']

def sroc(dataframe:DataFrame, roclen=21, emalen=13, smooth=21):
    df = dataframe.copy()
    roc = ta.ROC(df, timeperiod=roclen)
    ema = ta.EMA(df, timeperiod=emalen)
    sroc = ta.ROC(ema, timeperiod=smooth)
    return sroc

def cmf(dataframe:DataFrame, length=20, fillna=False) -> Series:
    df = dataframe.copy()
    mfv = ((df['close'] - df['low']) - (df['high'] - df['close'])) / (df['high'] - df['low'])
    mfv = mfv.fillna(0.0)
    mfv *= df['volume']
    cmf = (mfv.rolling(length, min_periods=0).sum() / df['volume'].rolling(length, min_periods=0).sum())
    if fillna:
        cmf = cmf.replace([np.inf, -np.inf], np.nan).fillna(0)
    return Series(cmf, name='cmf')

def ssl_channel(dataframe:DataFrame, length=7):
    df = dataframe.copy()
    df["ATR"] = ta.ATR(df, timeperiod=14)
    df["smaHigh"] = df["high"].rolling(length).mean() + df["ATR"]
    df["smaLow"] = df["low"].rolling(length).mean() - df["ATR"]
    df["hlv"] = np.where(df["close"] > df["smaHigh"], 1, np.where(df["close"] < df["smaLow"], -1, np.NAN))
    df["hlv"] = df["hlv"].ffill()
    df["ssl_Down"] = np.where(df["hlv"] < 0, df["smaHigh"], df["smaLow"])
    df["ssl_Up"] = np.where(df["hlv"] < 0, df["smaLow"], df["smaHigh"])
    return df["ssl_Down"], df["ssl_Up"]

def ewo(dataframe: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["close"] * 100
    return emadif

def wavetrend(dataframe:DataFrame, chlen:int= 10, avg:int= 21, smalen:int= 4) ->Series:
    df = dataframe.copy()
    df['hlc3'] = (df['high'] + df['low'] + df['close']) / 3
    df['esa'] = ta.EMA(df['hlc3'], timeperiod=chlen)
    df['d'] = ta.EMA((df['hlc3'] - df['esa']).abs(), timeperiod=chlen)
    df['ci'] = (df['hlc3'] - df['esa']) / (0.015 * df['d'])
    df['tci'] = ta.EMA(df['ci'], timeperiod=avg)
    df['wt1'] = df['tci']
    df['wt2'] = ta.SMA(df['wt1'], timeperiod=smalen)
    df['wt1-wt2'] = df['wt1'] - df['wt2']
    return df['wt1'], df['wt2']

def t3(dataframe:DataFrame, length:int= 5) -> Series:
    df = dataframe.copy()
    df['xe1'] = ta.EMA(df['close'], timeperiod=length)
    df['xe2'] = ta.EMA(df['xe1'], timeperiod=length)
    df['xe3'] = ta.EMA(df['xe2'], timeperiod=length)
    df['xe4'] = ta.EMA(df['xe3'], timeperiod=length)
    df['xe5'] = ta.EMA(df['xe4'], timeperiod=length)
    df['xe6'] = ta.EMA(df['xe5'], timeperiod=length)
    b = 0.7
    c1 = -b*b*b
    c2 = 3*b*b+3*b*b*b
    c3 = -6*b*b-3*b-3*b*b*b
    c4 = 1+3*b+b*b*b+3*b*b
    df['T3Average'] = c1 * df['xe6'] + c2 * df['xe5'] + c3 * df['xe4'] + c4 * df['xe3']
    return df['T3Average']

def tsi(dataframe: DataFrame, slow_len: int, fast_len: int, fillna=False) -> Series:
    df = dataframe.copy()
    min_periods_slow = 0 if fillna else slow_len
    min_periods_fast = 0 if fillna else fast_len
    close_diff            = df['close'].diff()
    close_diff_abs        = close_diff.abs()
    smooth_close_diff     = close_diff.ewm(span=slow_len, min_periods=min_periods_slow, adjust=False).mean().ewm(span=fast_len, min_periods=min_periods_fast, adjust=False).mean()
    smooth_close_diff_abs = close_diff_abs.ewm(span=slow_len, min_periods=min_periods_slow, adjust=False).mean().ewm(span=fast_len, min_periods=min_periods_fast, adjust=False).mean()
    tsi = smooth_close_diff / smooth_close_diff_abs * 100
    if fillna:
        tsi = tsi.replace([np.inf, -np.inf], np.nan).fillna(0)
    return tsi

def trend(dataframe:DataFrame , length:int = 10 , multiplier:float = 3):
    df = dataframe.copy()
    t = pta.supertrend(high=df['high'] , low=df['low'] , close= df['close'] , length=length , multiplier= multiplier)
    df['trend'] = t[t.columns[0]]
    df['dir'] = t[t.columns[1]]
    df['long'] = t[t.columns[2]]
    df['short'] = t[t.columns[3]]
    return df['trend'] , df['dir'] , df['long'] , df['short']

def heiken(dataframe:DataFrame):
    df = dataframe.copy()
    heikinashi = qtpylib.heikinashi(df)
    df['ha_open'] = heikinashi['open']
    df['ha_close'] = heikinashi['close']  
    return df['ha_open'] , df['ha_close']

def vwma(dataframe: DataFrame, length: int = 10):
    pv = dataframe['close'] * dataframe['volume']
    vwma = Series(ta.SMA(pv, timeperiod=length) / ta.SMA(dataframe['volume'], timeperiod=length))
    return vwma

def williams_r(dataframe: DataFrame, period: int = 14) -> Series:
    highest_high = dataframe["high"].rolling(center=False, window=period).max()
    lowest_low = dataframe["low"].rolling(center=False, window=period).min()
    WR = Series((highest_high - dataframe["close"]) / (highest_high - lowest_low),name=f"{period} Williams %R")
    return WR * -100

def qqe(dataframe:DataFrame , length:int = 20 , smooth:int = 5 , factor:float = 4.7):
    df = dataframe.copy()
    q = pta.qqe(close= df['close'] , length= length , smooth= smooth , factor= factor)
    df['qqe'] = q[q.columns[0]]
    df['qqel'] = q[q.columns[2]]
    df['qqes'] = q[q.columns[3]]
    return df['qqe'] , df['qqel'] , df['qqes']

def range_percent_change(dataframe: DataFrame, method, length: int) -> float:
    if method == 'HL':
        return (dataframe['high'].rolling(length).max() - dataframe['low'].rolling(length).min()) / dataframe[
            'low'].rolling(length).min()
    elif method == 'OC':
        return (dataframe['open'].rolling(length).max() - dataframe['close'].rolling(length).min()) / dataframe[
            'close'].rolling(length).min()
    else:
        raise ValueError(f"Method {method} not defined!")

def moderi(dataframe: DataFrame, len_slow_ma: int = 32) -> Series:
    slow_ma = Series(ta.EMA(vwma(dataframe, length=len_slow_ma), timeperiod=len_slow_ma))
    return slow_ma >= slow_ma.shift(1)  # we just need true & false for ERI trend

def supertrend(dataframe:pd.DataFrame , period:int = 5 , multiplyer:float = 3):
    t = pta.supertrend(high=dataframe['high'] , low=dataframe['low'] , close=dataframe['close'] , length= period , multiplier= multiplyer)
    trend = t[t.columns[0]]
    direction = t[t.columns[1]]
    long = t[t.columns[2]]
    short = t[t.columns[3]]

    return trend , direction , long , short

def ha_typical_price(bars):
    res = (bars['ha_high'] + bars['ha_low'] + bars['ha_close']) / 3.
    return Series(index=bars.index, data=res)

def VWAPB(dataframe, window_size=20, num_of_std=1):
    df = dataframe.copy()
    df['vwap'] = qtpylib.rolling_vwap(df,window=window_size)
    rolling_std = df['vwap'].rolling(window=window_size).std()
    df['vwap_low'] = df['vwap'] - (rolling_std * num_of_std)
    df['vwap_high'] = df['vwap'] + (rolling_std * num_of_std)
    return df['vwap_low'], df['vwap'], df['vwap_high']

def top_percent_change(dataframe: DataFrame, length: int) -> float:
        if length == 0:
            return (dataframe['open'] - dataframe['close']) / dataframe['close']
        else:
            return (dataframe['open'].rolling(length).max() - dataframe['close']) / dataframe['close']

_trend_length = 14
_bb_smooth_length=4

def iff(a,b,c):
    if a:
        return b
    else:
        return c


class Github_remiotore_ccxt_freqtrade__pol313__20260111_210550(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = "5m"
    can_short: bool = True
    max_open_trades = 1

    # minimal_roi = {
    #     "0": 0.04
    # }

    # stoploss = -0.9
    #######################################
    # Buy hyperspace params:
    buy_params = {
        "buy_cmf": -0.162,
        "buy_percent": -0.776,
        "buy_width": 0.034,
    }

    # Sell hyperspace params:
    sell_params = {
        "sell_cmf": 0.044,
        "sell_percent": 1.495,
        "sell_width": 0.016,
    }

    # ROI table:
    minimal_roi = {
        "0": 0.203,
        "17": 0.082,
        "37": 0.032,
        "104": 0
    }

    # Stoploss:
    stoploss = -0.349
    #######################################
    trailing_stop = False
    # trailing_only_offset_is_reached = False
    # trailing_stop_positive = 0.01
    # trailing_stop_positive_offset = 0.0  # Disabled / not configured

    process_only_new_candles = True
    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False
    startup_candle_count: int = 400

    @property
    def plot_config(self):
        return {
            "main_plot": {
                "kc_upper": {"color": "#ff0000"},
                "kc_lower": {"color": "#ffff00"},
            },
            "subplots": {
                "CMF": {
                    "cmf": {"color": "red"},
                }
            }
        }

    buy_percent = DecimalParameter(-2.0 , 0 , default= -1 , decimals= 3 , space='buy' , optimize= True)
    sell_percent = DecimalParameter(0 , 2.0 , default= 1 , decimals= 3 , space='sell' , optimize= True)

    buy_width = DecimalParameter(0.0 , 0.1 , default= 0.05 , decimals= 3 , space='buy' , optimize= True)
    sell_width = DecimalParameter(0.0 , 0.1 , default= 0.05 , decimals= 3 , space='sell' , optimize= True)

    buy_cmf = DecimalParameter(-0.6 , 0 , default= -0.05 , decimals= 3 , space='buy' , optimize= True)
    sell_cmf = DecimalParameter(0 , 0.6 , default= 0.05 , decimals= 3 , space='sell' , optimize= True)

    def populate_indicators(self, df: DataFrame, metadata: dict) -> DataFrame:
        df['tema'] = pta.tema(df['close'] , 6)
        df['ema5'] = pta.ema(df['close'] , 5)
        df['ema10'] = pta.ema(df['close'] , 10)
        df['ema20'] = pta.ema(df['close'] , 20)
        df['ema25'] = pta.ema(df['close'] , 25)
        df['ema30'] = pta.ema(df['close'] , 30)
        df['ema40'] = pta.ema(df['close'] , 40)
        df['ema50'] = pta.ema(df['close'] , 50)
        df['ema50'] = pta.ema(df['close'] , 50)
        df['ema100'] = pta.ema(df['close'] , 100)
        df['ema150'] = pta.ema(df['close'] , 150)
        df['ema200'] = pta.ema(df['close'] , 200)

        df['cti'] = pta.cti(df["close"], length=20)
        df["mfi"] = ta.MFI(df)
        df['roc'] = ta.ROC(df, timeperiod=4)
        df['mp']  = ta.RSI(df['roc'], timeperiod=8)
        df['sar'] = ta.SAR(df)
        df['rocr'] = ta.ROCR(df['close'], timeperiod=168)
        df['mom'] = ta.MOM(df, timeperiod=14)
        df['adx'] = ta.ADX(df, timeperiod=14)
        df['cci'] = ta.CCI(df)

        df["ao"] = qtpylib.awesome_oscillator(df)
        df['cmf'] = cmf(dataframe= df , length= 20)
        df['sroc'] = sroc(dataframe= df , roclen= 21 , emalen= 13 , smooth= 21)
        df['ewo'] = ewo(df , 5 ,35)
        df['tsi'] = tsi(df , slow_len= 26 , fast_len= 6)
        df['rmi'] = rmi(df)
        df['t3'] = t3(dataframe= df , length= 7)
        df['mac'] = self.mac(df, 20, 50)
        df['streak'] = self.ma_streak(df, period=4)

        df["ssl_Down"], df["ssl_Up"] = ssl_channel(dataframe= df , length= 7)
        df['wt1'], df['wt2'] = wavetrend(dataframe= df , chlen=10 , avg= 21 , smalen= 4)
        

        heikinashi = qtpylib.heikinashi(df)
        df['ha_open'] = heikinashi['open']
        df['ha_close'] = heikinashi['close']
        df['ha_high'] = heikinashi['high']
        df['ha_low'] = heikinashi['low']

        t = pta.supertrend(high=df['high'] , low=df['low'] , close= df['close'] , length=24 , multiplier= 2.5)
        df['long'] = t[t.columns[2]]
        df['short'] = t[t.columns[3]]

        streak = abs(int(df['streak'].iloc[-1]))
        streak_back_close = df['close'].shift(streak + 1)
        df['streak_roc'] = 100 * (df['close'] - streak_back_close) / streak_back_close

        pcc = self.pcc(df, period=20, mult=2)
        df['pcc_lower'] = pcc.lowerband
        df['pcc_upper'] = pcc.upperband

        df['r_14'] = williams_r(df, period=14)
        
        stoch_fast = ta.STOCHF(df, 5, 3, 0, 3, 0)
        df['fastd'] = stoch_fast['fastd']
        df['fastk'] = stoch_fast['fastk']

        df['plus_di'] = ta.PLUS_DI(df, timeperiod=25)
        df['minus_di'] = ta.MINUS_DI(df, timeperiod=25)

        bollinger2_40 = qtpylib.bollinger_bands(ha_typical_price(df), window=40, stds=2)
        df['lower40'] = bollinger2_40['lower']
        df['mid40'] = bollinger2_40['mid']
        df['upper40'] = bollinger2_40['upper']
        df['delta'] = ((df['mid40'] - df['lower40']) / (df['mid40'])).abs()   # distance between middle and lower band
        df['tail'] = (df['ha_close'] - df['ha_low']).abs()      # distance between close and low
        df['head'] = (df['ha_high'] - df['ha_close']).abs()     # distance between high and close

        vwap_low, vwap, vwap_high = VWAPB(df, 20, 1)
        df['vwap_upper'] = vwap_high
        df['vwap_mid'] = vwap
        df['vwap_lower'] = vwap_low
        df['vwap_width'] = ( (df['vwap_upper'] - df['vwap_lower']) / df['vwap_mid'] ) * 100

        weighted_bollinger = qtpylib.weighted_bollinger_bands(qtpylib.typical_price(df), window=20, stds=2)
        df["wbb_upper"] = weighted_bollinger["upper"]
        df["wbb_lower"] = weighted_bollinger["lower"]
        df["wbb_mid"] = weighted_bollinger["mid"]
        df["wbb_percent"] = ((df["close"] - df["wbb_lower"]) / (df["wbb_upper"] - df["wbb_lower"]))
        df["wbb_width"] = ((df["wbb_upper"] - df["wbb_lower"]) / df["wbb_mid"])

        keltner = qtpylib.keltner_channel(df , window= 24 , atrs=1.6)
        df["kc_upper"] = keltner["upper"]
        df["kc_lower"] = keltner["lower"]
        df["kc_mid"] = keltner["mid"]
        df["kc_percent"] = ((df["close"] - df["kc_lower"]) / ((df["kc_upper"] - df["kc_lower"])))
        df["kc_width"] = ((df["kc_upper"] - df["kc_lower"]) / df["kc_mid"])

        hilbert = ta.HT_SINE(df)
        df["htsine"] = hilbert["sine"]
        df["htleadsine"] = hilbert["leadsine"]

        stoch = ta.STOCH(df)
        df["slowd"] = stoch["slowd"]
        df["slowk"] = stoch["slowk"]

        stoch_fast = ta.STOCHF(df)
        df["fastd"] = stoch_fast["fastd"]
        df["fastk"] = stoch_fast["fastk"]

        stoch_rsi = ta.STOCHRSI(df)
        df["fastd_rsi"] = stoch_rsi["fastd"]
        df["fastk_rsi"] = stoch_rsi["fastk"]

        df["rsi"] = ta.RSI(df)
        rsi = 0.1 * (df["rsi"] - 50)
        df["fisher_rsi"] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1)
        df["fisher_rsi_norma"] = 50 * (df["fisher_rsi"] + 1)

        
        df["plus_dm"] = ta.PLUS_DM(df)
        df["plus_di"] = ta.PLUS_DI(df)

        df["minus_dm"] = ta.MINUS_DM(df)
        df["minus_di"] = ta.MINUS_DI(df)

        
        df['tpct_0'] = top_percent_change(df , 0)
        df['tpct_3'] = top_percent_change(df , 3)
        df['tpct_9'] = top_percent_change(df , 9)
        return df
    
    def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:

        df.loc[(
                (df['close'] < df['kc_lower']) &
                (df['kc_percent'] > self.buy_percent.value) &
                (df['kc_width'] > self.buy_width.value) &
                (df['cmf'] < self.buy_cmf.value)&
                (df['volume'] > 0)),'enter_long'] = 1

        df.loc[(
                (df['close'] > df['kc_upper']) &
                (df['kc_percent'] > self.sell_percent.value) &
                (df['kc_width'] > self.sell_width.value) &
                (df['cmf'] > self.sell_cmf.value)&
                (df['volume'] > 0)),'enter_short'] = 1

        return df

    def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        df.loc[((df['enter_long'] == 1)) , 'exit_short'] = 1
        df.loc[(df['enter_short'] == 1) , 'exit_long'] = 1
        return df

    def leverage(self,pair: str,current_time: datetime,current_rate: float,proposed_leverage: float,max_leverage: float,entry_tag: str,side: str,**kwargs) -> float:
        return 5.0

    def mac(self, dataframe: DataFrame, fast: int = 20, slow: int = 50) -> Series:
        dataframe = dataframe.copy()
        upper_fast = ta.EMA(dataframe['high'], timeperiod=fast)
        lower_fast = ta.EMA(dataframe['low'], timeperiod=fast)
        upper_slow = ta.EMA(dataframe['high'], timeperiod=slow)
        lower_slow = ta.EMA(dataframe['low'], timeperiod=slow)
        crosses_lf_us = qtpylib.crossed_above(lower_fast, upper_slow) | qtpylib.crossed_below(lower_fast, upper_slow)
        crosses_uf_ls = qtpylib.crossed_above(upper_fast, lower_slow) | qtpylib.crossed_below(upper_fast, lower_slow)
        dir_1 = np.where(crosses_lf_us, 1, np.nan)
        dir_2 = np.where(crosses_uf_ls, -1, np.nan)
        dir = np.where(dir_1 == 1, dir_1, np.nan)
        dir = np.where(dir_2 == -1, dir_2, dir_1)
        res = Series(dir).fillna(method='ffill').to_numpy()
        return res

    def ma_streak(self, dataframe: DataFrame, period: int = 4, source_type='close') -> Series:
        dataframe = dataframe.copy()
        avgval = self.zlema(dataframe[source_type], period)
        arr = np.diff(avgval)
        pos = np.clip(arr, 0, 1).astype(bool).cumsum()
        neg = np.clip(arr, -1, 0).astype(bool).cumsum()
        streak = np.where(arr >= 0, pos - np.maximum.accumulate(np.where(arr <= 0, pos, 0)),-neg + np.maximum.accumulate(np.where(arr >= 0, neg, 0)))
        res = np.concatenate((np.full((dataframe.shape[0] - streak.shape[0]), np.nan), streak))
        return res
    
    def zlema(self, series: Series, period):
        ema1 = ta.EMA(series, period)
        ema2 = ta.EMA(ema1, period)
        d = ema1 - ema2
        zlema = ema1 + d
        return zlema
    
    def pcc(self, dataframe: DataFrame, period: int = 20, mult: int = 2):
        PercentChangeChannel = namedtuple('PercentChangeChannel', ['upperband', 'middleband', 'lowerband'])
        dataframe = dataframe.copy()
        close = dataframe['close']
        previous_close = close.shift()
        low = dataframe['low']
        high = dataframe['high']
        close_change = (close - previous_close) / previous_close * 100
        high_change = (high - close) / close * 100
        low_change = (low - close) / close * 100
        mid = self.zlema(close_change, period)
        rangema = self.zlema(high_change - low_change, period)
        upper = mid + rangema * mult
        lower = mid - rangema * mult
        return PercentChangeChannel(upper, rangema, lower)



