# source: https://raw.githubusercontent.com/shatianming5/Agent_market/f50c7c127ad828b0259cdd7449fc2975cf8c6370/workspace/strategies/auto_v1.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

from datetime import datetime, timedelta
from typing import Optional


class Github_shatianming5_Agent_market__auto_v1__20260408_174318(IStrategy):
    timeframe = "1h"
    can_short = False

    process_only_new_candles = True
    startup_candle_count = 240

    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    minimal_roi = {"0": 0.012, "8": 0.006, "18": 0.0}
    stoploss = -0.04

    def _ema(self, s, span: int):
        return s.ewm(span=span, adjust=False).mean()

    def _rsi(self, close, period: int = 14):
        delta = close.diff()
        up = delta.clip(lower=0.0)
        down = (-delta).clip(lower=0.0)
        roll_up = up.ewm(alpha=1 / period, adjust=False).mean()
        roll_down = down.ewm(alpha=1 / period, adjust=False).mean()
        rs = roll_up / roll_down.replace(0.0, np.nan)
        rsi = 100.0 - (100.0 / (1.0 + rs))
        return rsi.fillna(50.0)

    def _atr(self, 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()))
        return tr.ewm(alpha=1 / period, adjust=False).mean()

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

        dataframe["ema50"] = self._ema(close, 50)
        dataframe["ema200"] = self._ema(close, 200)
        dataframe["ema200_slope_24"] = dataframe["ema200"] / dataframe["ema200"].shift(24) - 1.0

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

        dataframe["rsi14"] = self._rsi(close, 14)
        dataframe["atr14"] = self._atr(dataframe, 14)
        dataframe["atr_pct"] = dataframe["atr14"] / close.replace(0.0, np.nan)

        vol_mean = volume.rolling(20).mean()
        vol_std = volume.rolling(20).std(ddof=0)
        dataframe["vol_ratio"] = (volume / vol_mean.replace(0.0, np.nan)).replace([np.inf, -np.inf], np.nan)
        dataframe["vol_z"] = ((volume - vol_mean) / vol_std.replace(0.0, np.nan)).replace([np.inf, -np.inf], np.nan)

        return dataframe

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

        trend_ok = (df["ema200_slope_24"] > -0.015) & (close > df["ema200"] * 0.93)
        vol_ok = (df["atr_pct"] > 0.002) | (df["bb_width"] > 0.006)
        liquidity_ok = (df["vol_ratio"].fillna(1.0) > 0.75) | (df["vol_z"].fillna(0.0) > -0.3)

        strong_oversold = (close < df["bb_lower"] * 1.01) & (df["rsi14"] < 33)
        mild_pullback = (close < df["bb_mid"] * 0.988) & (df["rsi14"] < 41)

        df["enter_long"] = 0
        df.loc[
            trend_ok
            & vol_ok
            & liquidity_ok
            & (strong_oversold | mild_pullback)
            & (df["volume"] > 0),
            "enter_long",
        ] = 1
        return df

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        df = dataframe
        close = df["close"]

        take_profit = (close >= df["bb_mid"]) | (df["rsi14"] > 56) | (close > df["ema50"] * 1.003)
        risk_off = (close < df["bb_lower"] * 0.985) & (df["rsi14"] < 34)
        trend_break = close < df["ema200"] * 0.915

        df["exit_long"] = 0
        df.loc[(take_profit | risk_off | trend_break) & (df["volume"] > 0), "exit_long"] = 1
        return df

    def custom_exit(
        self,
        pair: str,
        trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ) -> Optional[str]:
        if trade.open_date_utc and current_time - trade.open_date_utc >= timedelta(hours=18):
            return "time_exit"
        if current_profit > 0.0:
            try:
                df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
                last = df.iloc[-1]
                if float(last.get("close", current_rate)) >= float(last.get("bb_mid", current_rate)):
                    return "bb_mid_tp"
            except Exception:
                return None
        return None

    def custom_stoploss(
        self,
        pair: str,
        trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ) -> float:
        try:
            df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            last = df.iloc[-1]
            atr = float(last.get("atr14", np.nan))
            close = float(last.get("close", current_rate))
            if np.isfinite(atr) and close > 0:
                sl = -1.2 * (atr / close)
                return float(np.clip(sl, -0.06, -0.015))
        except Exception:
            pass
        return self.stoploss