# source: https://raw.githubusercontent.com/Brondinar/trader-bot/e2233bbb50b90778aea0dc4535ee7fad5b79fd1d/strategies/grid_strategy.py
from datetime import datetime

from pandas import DataFrame
from freqtrade.strategy import IStrategy
from freqtrade.persistence import Trade
import talib.abstract as ta


class Github_Brondinar_trader_bot__grid_strategy__20260228_143614(IStrategy):
    """
    Conservative adaptive grid strategy for ETH/USDT.

    - Opens small initial position, DCA buys at -2% grid intervals (up to 5 levels)
    - Sells when price recovers above average entry + 1.5% profit target
    - ATR-based volatility filter pauses trading in extreme conditions
    - Multi-layer safety: stoploss, drawdown breaker, cooldown, daily limit
    """

    INTERFACE_VERSION = 3

    timeframe = "1h"
    stoploss = -0.10
    position_adjustment_enable = True
    max_entry_position_adjustment = 5

    # Disable ROI — we exit via grid profit target in custom_exit
    minimal_roi = {"0": 100}

    can_short = False
    startup_candle_count = 30

    # --- Grid parameters ---
    grid_spacing = 0.02        # 2% between grid levels
    grid_profit_target = 0.015  # 1.5% above average entry to sell

    # --- Grid stake per level ---
    grid_stake = 50.0  # USDT per grid level

    # --- Protections (Freqtrade 2026.1 requires these in strategy, not config) ---
    protections = [
        {
            "method": "MaxDrawdown",
            "trade_limit": 1,
            "lookback_period_candles": 48,
            "max_allowed_drawdown": 0.20,
            "stop_duration_candles": 12,
        },
        {
            "method": "StoplossGuard",
            "trade_limit": 3,
            "lookback_period_candles": 24,
            "stop_duration_candles": 24,
        },
        {
            "method": "CooldownPeriod",
            "stop_duration_candles": 1,
        },
    ]

    # --- Volatility filter ---
    atr_period = 14
    atr_ma_period = 14
    vol_high_threshold = 2.0   # ATR > 2x average = too volatile
    vol_low_threshold = 0.3    # ATR < 0.3x average = too quiet

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["atr"] = ta.ATR(dataframe, timeperiod=self.atr_period)
        dataframe["atr_ma"] = dataframe["atr"].rolling(window=self.atr_ma_period).mean()
        dataframe["atr_ratio"] = dataframe["atr"] / dataframe["atr_ma"]
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (dataframe["volume"] > 0)
            & (dataframe["atr_ratio"] > self.vol_low_threshold)
            & (dataframe["atr_ratio"] < self.vol_high_threshold),
            ["enter_long", "enter_tag"],
        ] = (1, "grid_entry")
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Exit handled by custom_exit, not by signal
        return dataframe

    def adjust_trade_position(
        self,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        min_stake: float | None,
        max_stake: float,
        current_entry_rate: float,
        current_exit_rate: float,
        current_entry_profit: float,
        current_exit_profit: float,
        **kwargs,
    ) -> float | None | tuple[float | None, str | None]:
        # Max 5 additional entries (6 total including initial)
        if trade.nr_of_successful_entries >= 6:
            return None

        # Calculate required drop for next grid level
        # Level 2 = -2%, Level 3 = -4%, etc.
        next_level = trade.nr_of_successful_entries  # 1-based: next is level 2, 3, ...
        required_drop = self.grid_spacing * next_level
        drop_from_entry = (trade.open_rate - current_entry_rate) / trade.open_rate

        if drop_from_entry < required_drop:
            return None

        # Check we have enough stake available
        if self.grid_stake > max_stake:
            return None

        return (self.grid_stake, f"grid_level_{next_level + 1}")

    def custom_exit(
        self,
        pair: str,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ) -> str | None:
        if current_profit >= self.grid_profit_target:
            return "grid_profit_target"
        return None

    def custom_stake_amount(
        self,
        pair: str,
        current_time: datetime,
        current_rate: float,
        proposed_stake: float,
        min_stake: float | None,
        max_stake: float,
        leverage: float,
        entry_tag: str | None,
        side: str,
        **kwargs,
    ) -> float:
        return min(self.grid_stake, max_stake)
