# source: https://raw.githubusercontent.com/shatianming5/Agent_market/f50c7c127ad828b0259cdd7449fc2975cf8c6370/workspace/strategies/auto_v201.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_v201__20260412_152448(IStrategy):
    INTERFACE_VERSION = 3

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

    use_exit_signal = True
    minimal_roi = {"0": 0.0}
    stoploss = -0.07

    # Prefer letting exits "run" to upper band; avoid tiny-profit exits
    sell_profit_only = True
    sell_profit_offset = 0.003

    rsi_period = 14
    bb_period = 20
    bb_std = 2.0

    rsi_buy = 40.0
    rsi_sell = 72.0

    vol_lookback = 20
    vol_mult = 0.6
    min_bb_width = 0.004

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

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

        # RSI (Wilder-style smoothing)
        delta = close.diff()
        up = delta.clip(lower=0.0)
        down = (-delta).clip(lower=0.0)
        alpha = 1.0 / float(self.rsi_period)
        avg_gain = up.ewm(alpha=alpha, adjust=False, min_periods=self.rsi_period).mean()
        avg_loss = down.ewm(alpha=alpha, adjust=False, min_periods=self.rsi_period).mean()
        rs = avg_gain / (avg_loss + 1e-10)
        dataframe["rsi"] = (100.0 - (100.0 / (1.0 + rs))).astype(float)

        # Bollinger Bands
        mid = close.rolling(self.bb_period, min_periods=self.bb_period).mean()
        std = close.rolling(self.bb_period, min_periods=self.bb_period).std(ddof=0)
        upper = mid + float(self.bb_std) * std
        lower = mid - float(self.bb_std) * std
        dataframe["bb_middle"] = mid
        dataframe["bb_upper"] = upper
        dataframe["bb_lower"] = lower
        denom = (upper - lower).replace(0.0, np.nan)
        dataframe["bb_width"] = ((upper - lower) / (mid + 1e-10)).astype(float)
        dataframe["percent_b"] = ((close - lower) / denom).astype(float)

        # Volume baseline + mild trend context
        dataframe["vol_sma"] = dataframe["volume"].rolling(self.vol_lookback, min_periods=self.vol_lookback).mean()
        dataframe["sma50"] = close.rolling(50, min_periods=50).mean()

        cols = ["rsi", "bb_middle", "bb_upper", "bb_lower", "bb_width", "percent_b", "vol_sma", "sma50"]
        for c in cols:
            if c in dataframe.columns:
                dataframe[c] = dataframe[c].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) & (dataframe["volume"] > (dataframe["vol_sma"] * float(self.vol_mult)))
        vol_ok = vol_ok | ((dataframe["volume"] > 0) & (dataframe["vol_sma"] == 0.0))

        # Main mean-reversion trigger: near/under lower band + oversold RSI
        near_lower = dataframe["close"] <= (dataframe["bb_lower"] * 1.003)
        oversold = dataframe["rsi"] < float(self.rsi_buy)

        # Ensure some movement potential; keep loose to avoid 0 trades
        vol_regime = dataframe["bb_width"] > float(self.min_bb_width)

        # Very mild downtrend guard (avoid extreme freefall), still permissive in bearish chop
        trend_guard = (dataframe["sma50"] == 0.0) | (dataframe["close"] > (dataframe["sma50"] * 0.90))

        cond = vol_ok & near_lower & oversold & vol_regime & trend_guard
        dataframe.loc[cond, ["enter_long", "enter_tag"]] = (1, "rsi_bb_revert_v201")
        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

        # Let winners run: take profit at (near) upper band; fallback exit if mean reversion stalls
        hit_upper = dataframe["close"] >= (dataframe["bb_upper"] * 0.997)
        rsi_hot = dataframe["rsi"] > float(self.rsi_sell)
        stalled_revert = (dataframe["close"] > dataframe["bb_middle"]) & (dataframe["rsi"] > 58.0)

        cond = vol_ok & (hit_upper | rsi_hot | stalled_revert)
        dataframe.loc[cond, "exit_long"] = 1
        return dataframe