# source: https://raw.githubusercontent.com/Danson77/Freqtrade_AIO_UI/f0bf29851d0577f244c17c0aa7d593ba3a7f3551/user_data/strategies/DT%20Strategies/Cenderawasih_30m.py
# directory_url: https://github.com/Danson77/Freqtrade_AIO_UI/blob/main/user_data/strategies/DT Strategies/
# User: Danson77
# Repository: Freqtrade_AIO_UI
# --------------------import logging
import math
from functools import reduce

import talib.abstract as ta
from pandas import DataFrame, Series

from freqtrade.strategy import IStrategy, informative, DecimalParameter, IntParameter
from freqtrade.exchange import timeframe_to_minutes

logger = logging.getLogger(__name__)


def tv_wma(series: Series, length: int = 9) -> Series:
    length = int(length)
    if length < 1:
        return series.copy()

    weights = list(range(1, length + 1))
    weight_sum = sum(weights)

    return series.rolling(length).apply(
        lambda x: (x * weights).sum() / weight_sum,
        raw=True
    )


def tv_hma(dataframe: DataFrame, length: int = 9, field: str = "close") -> Series:
    length = int(length)
    if length < 1:
        return dataframe[field].copy()

    half_length = max(1, length // 2)
    sqrt_length = max(1, int(math.sqrt(length)))

    wma_half = tv_wma(dataframe[field], half_length)
    wma_full = tv_wma(dataframe[field], length)
    hull_input = 2 * wma_half - wma_full

    return tv_wma(hull_input, sqrt_length)


class Github_Danson77_Freqtrade_AIO_UI__Cenderawasih_30m__20260520_191933(IStrategy):
    INTERFACE_VERSION = 3

    minimal_roi = {
        "0": 100
    }

    buy_params = {
        "buy_length_hma": 130,
        "buy_offset_hma": 0.83,
        "buy_length_hma2": 63,
        "buy_offset_hma2": 0.85,
        "buy_length_hma3": 30,
        "buy_offset_hma3": 0.84,
        "buy_length_hma4": 39,
        "buy_offset_hma4": 0.9,
        "buy_rsi_1": 42,
        "buy_rsi_2": 48,
        "buy_min_red_2h": 6,
        "buy_min_red_2h_2": 18,
        "buy_min_red_2h_3": 17,
        "buy_min_red_2h_4": 11,
        "buy_max_red_2h": -4,
    }

    sell_params = {
        "sell_length_ema": 5,
        "sell_offset_ema": 1.0,
        "sell_length_ema2": 102,
        "sell_offset_ema2": 0.87,
        "sell_length_ema3": 71,
        "sell_offset_ema3": 0.89,
        "sell_length_ema4": 133,
        "sell_offset_ema4": 1.17,
        "sell_long_green3": 6,
    }

    buy_rsi_1 = IntParameter(20, 50, default=42, optimize=False)
    buy_rsi_2 = IntParameter(20, 50, default=48, optimize=False)

    buy_min_red_2h = IntParameter(1, 30, default=6, optimize=False)
    buy_min_red_2h_2 = IntParameter(1, 30, default=18, optimize=False)
    buy_min_red_2h_3 = IntParameter(1, 30, default=17, optimize=False)
    buy_min_red_2h_4 = IntParameter(1, 30, default=11, optimize=False)
    buy_max_red_2h = IntParameter(-20, 20, default=-4, optimize=False)

    optimize_buy_hma = False
    buy_length_hma = IntParameter(5, 150, default=130, optimize=optimize_buy_hma)
    buy_offset_hma = DecimalParameter(0.8, 1.0, default=0.83, decimals=2, optimize=optimize_buy_hma)

    optimize_buy_hma2 = False
    buy_length_hma2 = IntParameter(5, 150, default=63, optimize=optimize_buy_hma2)
    buy_offset_hma2 = DecimalParameter(0.8, 1.0, default=0.85, decimals=2, optimize=optimize_buy_hma2)

    optimize_buy_hma3 = False
    buy_length_hma3 = IntParameter(5, 150, default=30, optimize=optimize_buy_hma3)
    buy_offset_hma3 = DecimalParameter(0.8, 1.0, default=0.84, decimals=2, optimize=optimize_buy_hma3)

    optimize_buy_hma4 = False
    buy_length_hma4 = IntParameter(5, 150, default=39, optimize=optimize_buy_hma4)
    buy_offset_hma4 = DecimalParameter(0.8, 1.0, default=0.90, decimals=2, optimize=optimize_buy_hma4)

    optimize_sell_ema = False
    sell_length_ema = IntParameter(5, 150, default=5, optimize=optimize_sell_ema)
    sell_offset_ema = DecimalParameter(1.0, 1.2, default=1.00, decimals=2, optimize=optimize_sell_ema)

    optimize_sell_ema2 = False
    sell_length_ema2 = IntParameter(5, 150, default=102, optimize=optimize_sell_ema2)
    sell_offset_ema2 = DecimalParameter(0.8, 1.0, default=0.87, decimals=2, optimize=optimize_sell_ema2)

    optimize_sell_ema3 = False
    sell_length_ema3 = IntParameter(5, 150, default=71, optimize=optimize_sell_ema3)
    sell_offset_ema3 = DecimalParameter(0.8, 1.0, default=0.89, decimals=2, optimize=optimize_sell_ema3)

    optimize_sell_ema4 = False
    sell_length_ema4 = IntParameter(5, 150, default=133, optimize=optimize_sell_ema4)
    sell_offset_ema4 = DecimalParameter(1.0, 1.2, default=1.17, decimals=2, optimize=optimize_sell_ema4)

    sell_long_green3 = IntParameter(5, 40, default=6, optimize=False)

    stoploss = -0.99

    trailing_stop = True
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.15
    trailing_only_offset_is_reached = True

    use_exit_signal = True
    exit_profit_only = False
    exit_profit_offset = 0.01
    ignore_roi_if_entry_signal = False

    timeframe = "30m"
    process_only_new_candles = True
    startup_candle_count = 300

    timeframe_minutes = timeframe_to_minutes(timeframe)
    timeframe_minutes_string = f"{timeframe_minutes}m" if timeframe_minutes < 60 else f"{timeframe_minutes // 60}h"

    @informative("1d")
    def populate_indicators_1d(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["age_filter_ok"] = (
            dataframe["volume"].rolling(window=30, min_periods=30).min() > 0
        )

        drop_columns = ["open", "high", "low", "close", "volume"]
        dataframe.drop(columns=dataframe.columns.intersection(drop_columns), inplace=True)
        return dataframe

    @informative("2h")
    def populate_indicators_2h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["pct_change"] = dataframe["close"].pct_change()

        drop_columns = ["open", "high", "low", "close", "volume"]
        dataframe.drop(columns=dataframe.columns.intersection(drop_columns), inplace=True)
        return dataframe

    @informative(timeframe, "BTC/{stake}", "{base}_{column}_{timeframe}")
    def populate_indicators_btc_30m(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["rsi"] = ta.RSI(dataframe["close"], timeperiod=14)

        drop_columns = ["open", "high", "low", "close", "volume"]
        dataframe.drop(columns=dataframe.columns.intersection(drop_columns), inplace=True)
        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["rsi"] = ta.RSI(dataframe["close"], timeperiod=14)
        dataframe["pct_change"] = dataframe["close"].pct_change()
        dataframe["live_data_ok"] = (
            dataframe["volume"].rolling(window=72, min_periods=72).min() > 0
        )

        if not self.optimize_buy_hma:
            dataframe["hma_offset_buy"] = tv_hma(dataframe, int(self.buy_length_hma.value)) * self.buy_offset_hma.value

        if not self.optimize_buy_hma2:
            dataframe["hma_offset_buy2"] = tv_hma(dataframe, int(self.buy_length_hma2.value)) * self.buy_offset_hma2.value

        if not self.optimize_buy_hma3:
            dataframe["hma_offset_buy3"] = tv_hma(dataframe, int(self.buy_length_hma3.value)) * self.buy_offset_hma3.value

        if not self.optimize_buy_hma4:
            dataframe["hma_offset_buy4"] = tv_hma(dataframe, int(self.buy_length_hma4.value)) * self.buy_offset_hma4.value

        if not self.optimize_sell_ema:
            dataframe["ema_offset_sell"] = ta.EMA(
                dataframe["close"], timeperiod=int(self.sell_length_ema.value)
            ) * self.sell_offset_ema.value

        if not self.optimize_sell_ema2:
            dataframe["ema_offset_sell2"] = ta.EMA(
                dataframe["close"], timeperiod=int(self.sell_length_ema2.value)
            ) * self.sell_offset_ema2.value

        if not self.optimize_sell_ema3:
            dataframe["ema_offset_sell3"] = ta.EMA(
                dataframe["close"], timeperiod=int(self.sell_length_ema3.value)
            ) * self.sell_offset_ema3.value

        if not self.optimize_sell_ema4:
            dataframe["ema_offset_sell4"] = ta.EMA(
                dataframe["close"], timeperiod=int(self.sell_length_ema4.value)
            ) * self.sell_offset_ema4.value

        return dataframe

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

        if self.optimize_buy_hma:
            dataframe["hma_offset_buy"] = tv_hma(dataframe, int(self.buy_length_hma.value)) * self.buy_offset_hma.value

        if self.optimize_buy_hma2:
            dataframe["hma_offset_buy2"] = tv_hma(dataframe, int(self.buy_length_hma2.value)) * self.buy_offset_hma2.value

        if self.optimize_buy_hma3:
            dataframe["hma_offset_buy3"] = tv_hma(dataframe, int(self.buy_length_hma3.value)) * self.buy_offset_hma3.value

        if self.optimize_buy_hma4:
            dataframe["hma_offset_buy4"] = tv_hma(dataframe, int(self.buy_length_hma4.value)) * self.buy_offset_hma4.value

        dataframe["enter_tag"] = ""
        dataframe["enter_long"] = 0

        btc_rsi_col = f"btc_rsi_{self.timeframe_minutes_string}"

        add_check = (
            dataframe["live_data_ok"]
            & dataframe["age_filter_ok_1d"].fillna(False)
            & (dataframe["close"] < dataframe["open"])
            & (dataframe["volume"] > 0)
        )

        buy_offset_hma = (
            (
                (dataframe["close"] < dataframe["hma_offset_buy"])
                & (dataframe[btc_rsi_col] < 30)
                & (dataframe["rsi"] < self.buy_rsi_1.value)
                & (dataframe["pct_change_2h"] > (-0.01 * self.buy_min_red_2h.value))
                & (dataframe["pct_change_2h"] < (-0.01 * self.buy_max_red_2h.value))
            )
            |
            (
                (dataframe["close"] < dataframe["hma_offset_buy2"])
                & (dataframe[btc_rsi_col] >= 30)
                & (dataframe[btc_rsi_col] < 50)
                & (dataframe["rsi"] < self.buy_rsi_2.value)
                & (dataframe["pct_change_2h"] > (-0.01 * self.buy_min_red_2h_2.value))
            )
            |
            (
                (dataframe["close"] < dataframe["hma_offset_buy3"])
                & (dataframe[btc_rsi_col] >= 50)
                & (dataframe[btc_rsi_col] < 70)
                & (dataframe["pct_change_2h"] > (-0.01 * self.buy_min_red_2h_3.value))
            )
            |
            (
                (dataframe["close"] < dataframe["hma_offset_buy4"])
                & (dataframe[btc_rsi_col] >= 70)
                & (dataframe["pct_change_2h"] > (-0.01 * self.buy_min_red_2h_4.value))
            )
        )

        conditions.append(buy_offset_hma)
        dataframe["enter_tag"] = dataframe["enter_tag"].mask(
            buy_offset_hma,
            dataframe["enter_tag"] + "hma "
        )

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions) & add_check,
                "enter_long"
            ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        if self.optimize_sell_ema:
            dataframe["ema_offset_sell"] = ta.EMA(
                dataframe["close"], timeperiod=int(self.sell_length_ema.value)
            ) * self.sell_offset_ema.value

        if self.optimize_sell_ema2:
            dataframe["ema_offset_sell2"] = ta.EMA(
                dataframe["close"], timeperiod=int(self.sell_length_ema2.value)
            ) * self.sell_offset_ema2.value

        if self.optimize_sell_ema3:
            dataframe["ema_offset_sell3"] = ta.EMA(
                dataframe["close"], timeperiod=int(self.sell_length_ema3.value)
            ) * self.sell_offset_ema3.value

        if self.optimize_sell_ema4:
            dataframe["ema_offset_sell4"] = ta.EMA(
                dataframe["close"], timeperiod=int(self.sell_length_ema4.value)
            ) * self.sell_offset_ema4.value

        dataframe["exit_tag"] = ""
        dataframe["exit_long"] = 0
        conditions = []

        sell_ema_1 = dataframe["close"] > dataframe["ema_offset_sell"]
        conditions.append(sell_ema_1)
        dataframe["exit_tag"] = dataframe["exit_tag"].mask(
            sell_ema_1,
            dataframe["exit_tag"] + "EMA_up "
        )

        sell_ema_2 = dataframe["close"] < dataframe["ema_offset_sell2"]
        conditions.append(sell_ema_2)
        dataframe["exit_tag"] = dataframe["exit_tag"].mask(
            sell_ema_2,
            dataframe["exit_tag"] + "EMA_down "
        )

        sell_ema_3 = (
            (dataframe["close"] < dataframe["ema_offset_sell3"]).astype(int).rolling(2).min() > 0
        )
        conditions.append(sell_ema_3)
        dataframe["exit_tag"] = dataframe["exit_tag"].mask(
            sell_ema_3,
            dataframe["exit_tag"] + "EMA_down_2 "
        )

        sell_ema_4 = (
            (dataframe["close"] > dataframe["ema_offset_sell4"]).astype(int).rolling(2).min() > 0
        )
        conditions.append(sell_ema_4)
        dataframe["exit_tag"] = dataframe["exit_tag"].mask(
            sell_ema_4,
            dataframe["exit_tag"] + "EMA_up_2 "
        )

        sell_long_green3 = (
            dataframe["pct_change"].rolling(3).sum() > (0.01 * self.sell_long_green3.value)
        )
        conditions.append(sell_long_green3)
        dataframe["exit_tag"] = dataframe["exit_tag"].mask(
            sell_long_green3,
            dataframe["exit_tag"] + "green_3 "
        )

        add_check = dataframe["volume"] > 0

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions) & add_check,
                "exit_long"
            ] = 1

        return dataframe