# source: https://raw.githubusercontent.com/HarleyBear70/okx-trading-bot/8a50f1549ed1f8ed0e2b5cad5b79714019e5da1e/freqtrade/user_data/strategies/research_strategies.py
# directory_url: https://github.com/HarleyBear70/okx-trading-bot/blob/main/freqtrade/user_data/strategies/
# User: HarleyBear70
# Repository: okx-trading-bot
# --------------------"""
Research batch strategies: the common bot types sold by 3Commas / Bitsgap / HaasOnline,
rebuilt simply so they can be backtested on the same footing as Jeff's own bots.

Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453     - 3Commas-style DCA: start a deal on an RSI dip, buy more ("safety orders")
             every 2.5% lower (each 1.5x bigger, up to 5), sell everything at +2% on the
             average price. Safety net stop at -30% (most DCA bots run with no stop).
Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453 - buy sharp drops (6%+ in 4 hours with RSI under 25), take +4%, give up
             after a day, stop at -6%.
"""
from pandas import DataFrame

from freqtrade.strategy import IStrategy


def rsi(close, n=14):
    d = close.diff()
    up = d.clip(lower=0).ewm(alpha=1 / n, adjust=False).mean()
    dn = (-d.clip(upper=0)).ewm(alpha=1 / n, adjust=False).mean()
    return 100 - 100 / (1 + up / dn)


class Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = "1h"
    can_short = False
    startup_candle_count = 50
    minimal_roi = {"0": 0.02}
    stoploss = -0.30
    use_exit_signal = False
    position_adjustment_enable = True
    max_entry_position_adjustment = 5

    STEP = 0.025      # next safety order every 2.5% below the first buy
    SCALE = 1.5       # each safety order 1.5x the one before
    UNITS = sum(1.5 ** k for k in range(6))   # base + 5 safety orders, in base-order units

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["rsi"] = rsi(dataframe["close"])
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[dataframe["rsi"] < 30, "enter_long"] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["exit_long"] = 0
        return dataframe

    def custom_stake_amount(self, pair, current_time, current_rate, proposed_stake, min_stake,
                            max_stake, leverage, entry_tag, side, **kwargs) -> float:
        # keep enough money for every safety order of every open deal
        total = self.wallets.get_total_stake_amount()
        slots = self.config.get("max_open_trades", 3)
        return min(total / slots / self.UNITS, max_stake)

    def adjust_trade_position(self, trade, current_time, current_rate, current_profit,
                              min_stake, max_stake, **kwargs):
        filled = trade.select_filled_orders(trade.entry_side)
        count = trade.nr_of_successful_entries
        if not filled or count > self.max_entry_position_adjustment:
            return None
        first = filled[0]
        if current_rate > first.safe_price * (1 - self.STEP * count):
            return None
        stake = first.safe_filled * first.safe_price * (self.SCALE ** count)
        if min_stake and stake < min_stake:
            return None
        return min(stake, max_stake)


class Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = "1h"
    can_short = False
    startup_candle_count = 50
    minimal_roi = {"0": 0.04, "720": 0.01, "1440": 0}
    stoploss = -0.06
    use_exit_signal = False

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["rsi"] = rsi(dataframe["close"])
        dataframe["drop4"] = dataframe["close"] / dataframe["close"].shift(4) - 1
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[(dataframe["drop4"] <= -0.06) & (dataframe["rsi"] < 25), "enter_long"] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["exit_long"] = 0
        return dataframe


def ut_bot(df, key=4.0, period=52, use_ha=True):
    """UT Bot Alerts (QuantNomad, TradingView). Returns (buy, sell) boolean lists.
    ATR from the real candles; signal source = Heikin Ashi close if use_ha."""
    o, h, l, c = (df[k].tolist() for k in ("open", "high", "low", "close"))
    n = len(c)
    tr = [h[0] - l[0]] + [max(h[i] - l[i], abs(h[i] - c[i - 1]), abs(l[i] - c[i - 1])) for i in range(1, n)]
    atr = [None] * n
    if n >= period:
        atr[period - 1] = sum(tr[:period]) / period
        for i in range(period, n):
            atr[i] = (atr[i - 1] * (period - 1) + tr[i]) / period
    src = [(o[i] + h[i] + l[i] + c[i]) / 4 for i in range(n)] if use_ha else c
    stop = [0.0] * n
    buy, sell = [False] * n, [False] * n
    for i in range(1, n):
        if atr[i] is None:
            continue
        loss = key * atr[i]
        p = stop[i - 1]
        if src[i] > p and src[i - 1] > p:
            stop[i] = max(p, src[i] - loss)
        elif src[i] < p and src[i - 1] < p:
            stop[i] = min(p, src[i] + loss)
        elif src[i] > p:
            stop[i] = src[i] - loss
        else:
            stop[i] = src[i] + loss
        if atr[i - 1] is None:
            continue
        buy[i] = src[i] > stop[i] and src[i - 1] <= stop[i - 1]
        sell[i] = src[i] < stop[i] and src[i - 1] >= stop[i - 1]
    return buy, sell


class Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453(IStrategy):
    """UT Bot Alerts, Jeff's settings: Heikin Ashi signals, ATR period 52, key value 4.
    Buys on the Buy signal, sells on the Sell signal, at REAL prices (next candle open)."""
    INTERFACE_VERSION = 3
    timeframe = "4h"
    can_short = False
    startup_candle_count = 200
    minimal_roi = {"0": 100}
    stoploss = -0.25          # safety net only; UT Bot itself has no stop besides the Sell signal
    use_exit_signal = True
    key, period, use_ha, daily_filter = 4.0, 52, True, False

    def informative_pairs(self):
        return [(p, "1d") for p in self.dp.current_whitelist()] if self.daily_filter else []

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        buy, sell = ut_bot(dataframe, self.key, self.period, self.use_ha)
        dataframe["ut_buy"], dataframe["ut_sell"] = buy, sell
        dataframe["day_up"] = True
        if self.daily_filter:
            d = self.dp.get_pair_dataframe(pair=metadata["pair"], timeframe="1d").copy()
            d["up"] = d["close"] > d["close"].ewm(span=50, adjust=False).mean()
            up = dict(zip(d["date"].dt.floor("D") + __import__("pandas").Timedelta(days=1), d["up"]))
            dataframe["day_up"] = [bool(up.get(t.floor("D"), False)) for t in dataframe["date"]]
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[dataframe["ut_buy"] & dataframe["day_up"], "enter_long"] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[dataframe["ut_sell"], "exit_long"] = 1
        return dataframe


class Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453Regular(Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453):
    """Same, but signals from regular candles (for comparison)."""
    use_ha = False


class Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_0344531h(Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453):
    timeframe = "1h"


class Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453Trend(Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453):
    """Jeff's UT Bot settings, but only buys when the daily trend is up."""
    daily_filter = True


def _wma(s, n):
    import numpy as np
    w = np.arange(1, n + 1)
    return s.rolling(n).apply(lambda x: (x * w).sum() / w.sum(), raw=True)


def hma(s, n):
    return _wma(2 * _wma(s, max(n // 2, 1)) - _wma(s, n), max(int(n ** 0.5), 1))


class Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453(IStrategy):
    """SSL Hybrid (Mihkel00) baseline signal: HMA(60) baseline with a band of 0.2 x EMA(60) of true range.
    Baseline turns blue when price closes above the band (buy), red when it closes below (sell).
    The ATR period 28 / EMA smoothing settings only change the ATR bands drawn on the chart."""
    INTERFACE_VERSION = 3
    timeframe = "4h"
    can_short = False
    startup_candle_count = 200
    minimal_roi = {"0": 100}
    stoploss = -0.25
    use_exit_signal = True
    length, mult, daily_filter = 60, 0.2, False

    def informative_pairs(self):
        return [(p, "1d") for p in self.dp.current_whitelist()] if self.daily_filter else []

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        import pandas as pd
        c = dataframe["close"]
        tr = pd.concat([dataframe["high"] - dataframe["low"], (dataframe["high"] - c.shift()).abs(),
                        (dataframe["low"] - c.shift()).abs()], axis=1).max(axis=1)
        base = hma(c, self.length)
        rng = tr.ewm(span=self.length, adjust=False).mean()
        dataframe["blue"] = c > base + rng * self.mult
        dataframe["red"] = c < base - rng * self.mult
        dataframe["day_up"] = True
        if self.daily_filter:
            d = self.dp.get_pair_dataframe(pair=metadata["pair"], timeframe="1d").copy()
            d["up"] = d["close"] > d["close"].ewm(span=50, adjust=False).mean()
            up = dict(zip(d["date"].dt.floor("D") + pd.Timedelta(days=1), d["up"]))
            dataframe["day_up"] = [bool(up.get(t.floor("D"), False)) for t in dataframe["date"]]
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        turned_blue = dataframe["blue"] & ~dataframe["blue"].shift(1, fill_value=False)
        dataframe.loc[turned_blue & dataframe["day_up"], "enter_long"] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[dataframe["red"], "exit_long"] = 1
        return dataframe


class Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_0344531h(Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453):
    timeframe = "1h"


class Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453Trend(Github_HarleyBear70_okx_trading_bot__research_strategies__20261016_034453):
    daily_filter = True
