# source: https://raw.githubusercontent.com/contact142/AttachedAssets/99286cf8e1a0a807781149bc33bd53061e53d0de/user_data/strategies/AgentBase.py
"""
Base agent strategy for XMR/USD.

This class keeps the logic simple and hyperopt-friendly while remaining
safe-by-default in dry-run mode. It exposes tunable parameters for RSI, EMA,
ATR-based exits, and trailing stop behavior. AgentManager can mutate these
fields to explore the search space while keeping the core logic stable.
"""

from __future__ import annotations

from functools import reduce
from typing import Any, Dict, List
import logging

import pandas as pd
from freqtrade.strategy import (
    CategoricalParameter,
    DecimalParameter,
    IntParameter,
)
from freqtrade.strategy.interface import IStrategy


class Github_contact142_AttachedAssets__AgentBase__20251219_044222(IStrategy):
    """
    A lightweight Freqtrade strategy tuned for XMR/USD with conservative defaults.
    """

    minimal_roi = {"0": 0.02, "120": 0.01, "240": 0}
    timeframe = "5m"
    process_only_new_candles = True
    startup_candle_count = 100
    use_exit_signal = True
    exit_profit_only = False
    ignore_buying_expired_candles = True

    rsi_buy = IntParameter(10, 50, default=30, space="buy")
    rsi_sell = IntParameter(50, 90, default=70, space="sell")
    ema_fast = IntParameter(5, 24, default=12, space="buy")
    ema_slow = IntParameter(20, 100, default=26, space="buy")
    atr_period = IntParameter(7, 28, default=14, space="sell")
    atr_multiplier = DecimalParameter(1.0, 4.0, decimals=2, default=2.0, space="sell")

    stoploss_param = DecimalParameter(-0.25, -0.02, default=-0.1, decimals=3, space="sell")
    trailing_stop_param = CategoricalParameter([False, True], default=False, space="sell")
    trailing_stop_positive_param = DecimalParameter(0.01, 0.08, default=0.02, decimals=3, space="sell")
    trailing_stop_positive_offset_param = DecimalParameter(0.01, 0.15, default=0.03, decimals=3, space="sell")
    trailing_only_offset_is_reached_param = CategoricalParameter([True, False], default=True, space="sell")

    plot_config = {
        "main_plot": {
            "ema_fast": {"color": "orange"},
            "ema_slow": {"color": "blue"},
            "bb_upper": {"color": "gray"},
            "bb_lower": {"color": "gray"},
        },
        "subplots": {
            "rsi": {"rsi": {"color": "purple"}},
            "atr": {"atr": {"color": "red"}},
        },
    }

    def __init__(self, config: Dict[str, Any]) -> None:
        super().__init__(config)
        self.custom_params: Dict[str, Any] = config.get("strategy_parameters", {}) if config else {}
        self.logger = logging.getLogger(self.__class__.__name__)

    @property
    def stoploss(self) -> float:  # type: ignore[override]
        return float(self._param_value("stoploss", self.stoploss_param.value))

    @property
    def trailing_stop(self) -> bool:  # type: ignore[override]
        return bool(self._param_value("trailing_stop", self.trailing_stop_param.value))

    @property
    def trailing_stop_positive(self) -> float:  # type: ignore[override]
        return float(
            self._param_value("trailing_stop_positive", self.trailing_stop_positive_param.value)
        )

    @property
    def trailing_stop_positive_offset(self) -> float:  # type: ignore[override]
        return float(
            self._param_value(
                "trailing_stop_positive_offset", self.trailing_stop_positive_offset_param.value
            )
        )

    @property
    def trailing_only_offset_is_reached(self) -> bool:  # type: ignore[override]
        return bool(
            self._param_value(
                "trailing_only_offset_is_reached",
                self.trailing_only_offset_is_reached_param.value,
            )
        )

    def _param_value(self, name: str, fallback: Any) -> Any:
        """
        Resolve parameter value, preferring config-provided overrides, then the
        Parameter default/hyperopt value, and finally a constant fallback.
        """
        if self.custom_params and name in self.custom_params:
            return self.custom_params[name]

        param_attr = getattr(self, f"{name}_param", None)
        if param_attr is not None and hasattr(param_attr, "value"):
            return param_attr.value

        return fallback

    @staticmethod
    def _compute_rsi(series: pd.Series, period: int = 14) -> pd.Series:
        delta = series.diff()
        gain = delta.clip(lower=0)
        loss = -delta.clip(upper=0)
        avg_gain = gain.ewm(alpha=1 / period, min_periods=period, adjust=False).mean()
        avg_loss = loss.ewm(alpha=1 / period, min_periods=period, adjust=False).mean()
        rs = avg_gain / avg_loss.replace(0, 1e-9)
        rsi = 100 - (100 / (1 + rs))
        return rsi

    @staticmethod
    def _compute_atr(dataframe: pd.DataFrame, period: int) -> pd.Series:
        high_low = dataframe["high"] - dataframe["low"]
        high_close = (dataframe["high"] - dataframe["close"].shift()).abs()
        low_close = (dataframe["low"] - dataframe["close"].shift()).abs()
        true_range = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
        return true_range.rolling(window=period, min_periods=period).mean()

    @staticmethod
    def _compute_bbands(series: pd.Series, period: int = 20, deviation: float = 2.0) -> List[pd.Series]:
        mid = series.rolling(window=period, min_periods=period).mean()
        std = series.rolling(window=period, min_periods=period).std(ddof=0)
        upper = mid + deviation * std
        lower = mid - deviation * std
        return [upper, mid, lower]

    def populate_indicators(self, dataframe: pd.DataFrame, metadata: Dict[str, Any]) -> pd.DataFrame:
        df = dataframe.copy()
        ema_fast_length = int(self._param_value("ema_fast", self.ema_fast.value))
        ema_slow_length = int(self._param_value("ema_slow", self.ema_slow.value))
        atr_period = int(self._param_value("atr_period", self.atr_period.value))

        df["ema_fast"] = df["close"].ewm(span=ema_fast_length, adjust=False).mean()
        df["ema_slow"] = df["close"].ewm(span=ema_slow_length, adjust=False).mean()
        df["rsi"] = self._compute_rsi(df["close"])
        df["atr"] = self._compute_atr(df, atr_period)

        upper, mid, lower = self._compute_bbands(df["close"])
        df["bb_upper"] = upper
        df["bb_mid"] = mid
        df["bb_lower"] = lower

        return df

    def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: Dict[str, Any]) -> pd.DataFrame:
        df = dataframe.copy()
        rsi_buy_threshold = int(self._param_value("rsi_buy", self.rsi_buy.value))
        conditions: List[pd.Series] = [
            df["volume"] > 0,
            df["ema_fast"] > df["ema_slow"],
            df["rsi"] < rsi_buy_threshold,
            df["close"] > df["bb_lower"],
        ]
        df.loc[reduce(lambda x, y: x & y, conditions), ["enter_long", "enter_tag"]] = (
            1,
            "ema_rsi_bounce",
        )
        return df

    def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: Dict[str, Any]) -> pd.DataFrame:
        df = dataframe.copy()
        rsi_sell_threshold = int(self._param_value("rsi_sell", self.rsi_sell.value))
        atr_multiplier = float(self._param_value("atr_multiplier", self.atr_multiplier.value))

        exit_conditions: List[pd.Series] = [
            df["rsi"] > rsi_sell_threshold,
            df["close"] < df["ema_slow"] - (df["atr"] * atr_multiplier),
        ]

        df.loc[reduce(lambda x, y: x & y, exit_conditions), ["exit_long", "exit_tag"]] = (
            1,
            "atr_mean_revert",
        )
        return df
