# source: https://raw.githubusercontent.com/shatianming5/Agent_market/144e537e42adea9ac9445c271bd7d6f57afb943c/workspace/strategies/auto_v105.py
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
from pandas import DataFrame
_ROOT = Path(__file__).resolve().parents[2]
if str(_ROOT / "src") not in sys.path:
    sys.path.insert(0, str(_ROOT / "src"))
    sys.path.insert(0, str(_ROOT))
from freqtrade.strategy import IStrategy


class Github_shatianming5_Agent_market__auto_v105__20260429_120239(IStrategy):
    INTERFACE_VERSION = 3

    timeframe = "1h"
    can_short = False
    process_only_new_candles = True
    startup_candle_count: int = 60

    stoploss = -0.025
    minimal_roi = {"0": 0.015, "24": 0.008, "48": 0.0}
    use_exit_signal = True
    ignore_buying_expired_candle_after = 0

    @staticmethod
    def _rsi(close: DataFrame, period: int = 14):
        delta = close.diff()
        up = delta.clip(lower=0.0)
        down = (-delta).clip(lower=0.0)
        alpha = 1.0 / float(period)
        avg_up = up.ewm(alpha=alpha, adjust=False).mean()
        avg_down = down.ewm(alpha=alpha, adjust=False).mean()
        rs = avg_up / (avg_down.replace(0.0, np.nan))
        rsi = 100.0 - (100.0 / (1.0 + rs))
        return rsi

    @staticmethod
    def _atr(dataframe: DataFrame, period: int = 14):
        high = dataframe["high"]
        low = dataframe["low"]
        close = dataframe["close"]
        prev_close = close.shift(1)
        tr = np.maximum(high - low, np.maximum((high - prev_close).abs(), (low - prev_close).abs()))
        alpha = 1.0 / float(period)
        return tr.ewm(alpha=alpha, adjust=False).mean()

    @staticmethod
    def _adx(dataframe: DataFrame, period: int = 14):
        high = dataframe["high"]
        low = dataframe["low"]
        close = dataframe["close"]
        up_move = high.diff()
        down_move = -low.diff()
        plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0.0)
        minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0.0)
        prev_close = close.shift(1)
        tr = np.maximum(high - low, np.maximum((high - prev_close).abs(), (low - prev_close).abs()))
        alpha = 1.0 / float(period)
        atr = DataFrame(tr).ewm(alpha=alpha, adjust=False).mean().iloc[:, 0]
        plus_di = 100.0 * (DataFrame(plus_dm).ewm(alpha=alpha, adjust=False).mean().iloc[:, 0] / atr.replace(0.0, np.nan))
        minus_di = 100.0 * (DataFrame(minus_dm).ewm(alpha=alpha, adjust=False).mean().iloc[:, 0] / atr.replace(0.0, np.nan))
        dx = (100.0 * (plus_di - minus_di).abs() / (plus_di + minus_di).replace(0.0, np.nan))
        adx = dx.ewm(alpha=alpha, adjust=False).mean()
        return adx, plus_di, minus_di

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        if dataframe is None:
            return dataframe
        if dataframe.empty:
            return dataframe

        close = dataframe["close"].astype(float)
        volume = dataframe["volume"].astype(float)

        dataframe["rsi"] = self._rsi(close, 14)
        dataframe["ema9"] = close.ewm(span=9, adjust=False).mean()
        dataframe["ema21"] = close.ewm(span=21, adjust=False).mean()
        dataframe["ema55"] = close.ewm(span=55, adjust=False).mean()

        bb_len = 20
        bb_mid = close.rolling(bb_len).mean()
        bb_std = close.rolling(bb_len).std(ddof=0)
        dataframe["bb_mid"] = bb_mid
        dataframe["bb_lower"] = bb_mid - 2.0 * bb_std
        dataframe["bb_upper"] = bb_mid + 2.0 * bb_std
        dataframe["bb_width"] = ((dataframe["bb_upper"] - dataframe["bb_lower"]) / bb_mid.replace(0.0, np.nan))

        atr = self._atr(dataframe, 14)
        dataframe["atr"] = atr
        dataframe["atrp"] = (atr / close.replace(0.0, np.nan))

        adx, pdi, mdi = self._adx(dataframe, 14)
        dataframe["adx"] = adx
        dataframe["pdi"] = pdi
        dataframe["mdi"] = mdi

        vma = volume.rolling(20).mean()
        dataframe["vol_ratio"] = (volume / vma.replace(0.0, np.nan))

        dataframe["roc24"] = (close / close.shift(24) - 1.0)
        dataframe["roc48"] = (close / close.shift(48) - 1.0)

        zden = bb_std.replace(0.0, np.nan)
        dataframe["z"] = ((close - bb_mid) / zden)

        dataframe = dataframe.replace([np.inf, -np.inf], np.nan).ffill().fillna(0.0)
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        if dataframe is None or dataframe.empty:
            return dataframe

        vol_ok = dataframe["volume"] > 0
        not_panic = dataframe["roc48"] > -0.07
        not_trending_hard = dataframe["adx"] < 30
        vol_sane = dataframe["vol_ratio"] > 0.35
        move_ok = dataframe["atrp"] > 0.003

        oversold_core = (dataframe["rsi"] < 30) & (dataframe["z"] < -1.6)
        band_flush = (dataframe["close"] < dataframe["bb_lower"] * 1.006) & (dataframe["rsi"] < 33)
        reversal_hint = dataframe["rsi"] > dataframe["rsi"].shift(1)

        entry = vol_ok & vol_sane & move_ok & not_panic & not_trending_hard & (oversold_core | band_flush) & (reversal_hint | (dataframe["rsi"] < 24))
        entry = entry & (~entry.shift(1).fillna(False))

        dataframe.loc[entry, "enter_long"] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        if dataframe is None or dataframe.empty:
            return dataframe

        vol_ok = dataframe["volume"] > 0

        tp_band = (dataframe["close"] > dataframe["bb_upper"] * 0.995) | (dataframe["rsi"] > 58)
        recover = (dataframe["close"] > dataframe["ema21"]) & (dataframe["z"] > -0.2) & (dataframe["roc24"] > -0.005)
        risk_off = (dataframe["roc48"] < -0.06) & (dataframe["adx"] > 28) & (dataframe["mdi"] > dataframe["pdi"]) & (dataframe["close"] < dataframe["ema21"])

        exit_sig = vol_ok & (tp_band | recover | risk_off)

        dataframe.loc[exit_sig, "exit_long"] = 1
        return dataframe