# source: https://raw.githubusercontent.com/learn-dumboo24/adaptive-trend-strategy/7d031b1589e3c32870501c6f01c441d05a3a8071/strategies/HybridGateStrategy.py
"""
Github_learn_dumboo24_adaptive_trend_strategy__HybridGateStrategy__20260512_160735 — freqtrade port of the Claude trading bot's hard gate logic.

Maps the bot's 3-layer gate to native indicators:
  - Multi-Timeframe Alignment  → EMA stack across 15m/1h/4h, RSI agreement
  - Multi-Agent Debate         → composite of RSI + MACD + Bollinger position
  - Volume Confirmation        → volume / 20-period SMA ≥ 1.5

This is a faithful approximation, not exact — the live bot uses TradingView
analysis + Claude synthesis. Backtest result indicates *ballpark* edge of
the same logic on historical OHLCV.

Defaults mirror live bot: SL=3%, TP=5%, gate strict.
"""
from datetime import datetime
from functools import reduce
from typing import Optional

import numpy as np
import pandas as pd
import talib.abstract as ta
from pandas import DataFrame

from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, informative


class Github_learn_dumboo24_adaptive_trend_strategy__HybridGateStrategy__20260512_160735(IStrategy):
    INTERFACE_VERSION = 3

    timeframe = "15m"
    can_short = False

    # Mirror live bot risk settings
    minimal_roi = {"0": 0.05}    # take profit 5%
    stoploss = -0.03             # stop loss 3%

    trailing_stop = False
    process_only_new_candles = True

    use_exit_signal = False
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    startup_candle_count: int = 200

    # ---- TUNABLES (hyperopt-able) ----
    buy_rsi_max     = IntParameter(50, 75, default=70, space="buy")
    buy_rsi_min     = IntParameter(35, 55, default=45, space="buy")
    vol_ratio_min   = DecimalParameter(1.0, 2.5, default=1.5, space="buy", decimals=2)
    mtf_score_min   = IntParameter(1, 4, default=3, space="buy")
    agent_min       = IntParameter(5, 9, default=7, space="buy")
    adx_min         = IntParameter(20, 40, default=25, space="buy")

    @informative("1h")
    def populate_indicators_1h(self, df: DataFrame, metadata: dict) -> DataFrame:
        df["ema_50"]  = ta.EMA(df, timeperiod=50)
        df["ema_200"] = ta.EMA(df, timeperiod=200)
        df["rsi"]     = ta.RSI(df, timeperiod=14)
        return df

    @informative("4h")
    def populate_indicators_4h(self, df: DataFrame, metadata: dict) -> DataFrame:
        df["ema_50"]  = ta.EMA(df, timeperiod=50)
        df["ema_200"] = ta.EMA(df, timeperiod=200)
        df["rsi"]     = ta.RSI(df, timeperiod=14)
        return df

    def populate_indicators(self, df: DataFrame, metadata: dict) -> DataFrame:
        # Base 15m indicators
        df["ema_20"]  = ta.EMA(df, timeperiod=20)
        df["ema_50"]  = ta.EMA(df, timeperiod=50)
        df["ema_200"] = ta.EMA(df, timeperiod=200)
        df["rsi"]     = ta.RSI(df, timeperiod=14)

        macd = ta.MACD(df)
        df["macd"]        = macd["macd"]
        df["macd_signal"] = macd["macdsignal"]
        df["macd_hist"]   = macd["macdhist"]

        bb = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
        df["bb_lower"]  = bb["lowerband"]
        df["bb_middle"] = bb["middleband"]
        df["bb_upper"]  = bb["upperband"]
        df["bb_pos"]    = (df["close"] - df["bb_lower"]) / (df["bb_upper"] - df["bb_lower"])

        df["adx"] = ta.ADX(df, timeperiod=14)

        # Volume confirmation: vol_ratio = current / 20-period mean
        df["vol_sma_20"] = ta.SMA(df["volume"], timeperiod=20)
        df["vol_ratio"]  = df["volume"] / df["vol_sma_20"]

        # ---- MTF score: count bullish timeframes (15m, 1h, 4h) ----
        # 15m bias
        df["bias_15m"]  = ((df["close"] > df["ema_50"]) & (df["ema_50"] > df["ema_200"])).astype(int)
        df["bias_15m"] -= ((df["close"] < df["ema_50"]) & (df["ema_50"] < df["ema_200"])).astype(int)
        # 1h
        df["bias_1h"]   = ((df["close"] > df["ema_50_1h"]) & (df["ema_50_1h"] > df["ema_200_1h"])).astype(int)
        df["bias_1h"]  -= ((df["close"] < df["ema_50_1h"]) & (df["ema_50_1h"] < df["ema_200_1h"])).astype(int)
        # 4h
        df["bias_4h"]   = ((df["close"] > df["ema_50_4h"]) & (df["ema_50_4h"] > df["ema_200_4h"])).astype(int)
        df["bias_4h"]  -= ((df["close"] < df["ema_50_4h"]) & (df["ema_50_4h"] < df["ema_200_4h"])).astype(int)

        df["mtf_score"] = df["bias_15m"] + df["bias_1h"] + df["bias_4h"]

        # ---- Multi-agent composite ----
        # Tech agent: MACD + EMA cross + RSI room
        df["tech_signal"] = (
            (df["macd"] > df["macd_signal"]).astype(int) +
            (df["close"] > df["ema_20"]).astype(int) +
            ((df["rsi"] > 50) & (df["rsi"] < 70)).astype(int)
        )
        # Sentiment agent: RSI direction + BB position momentum
        df["senti_signal"] = (
            (df["rsi"].diff(3) > 0).astype(int) +
            ((df["bb_pos"] > 0.5) & (df["bb_pos"] < 0.95)).astype(int) +
            (df["close"].pct_change(5) > 0).astype(int)
        )
        # Risk agent: ADX trending + not overbought + volume sane
        df["risk_signal"] = (
            (df["adx"] > 20).astype(int) +
            (df["rsi"] < 75).astype(int) +
            (df["vol_ratio"] > 0.7).astype(int)
        )
        df["agent_consensus"] = df["tech_signal"] + df["senti_signal"] + df["risk_signal"]
        # Max possible = 9. ≥6 = bullish consensus.

        return df

    def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        conditions = [
            df["mtf_score"]       >= self.mtf_score_min.value,    # MTF aligned bullish
            df["agent_consensus"] >= self.agent_min.value,        # 3-agent consensus bullish
            df["vol_ratio"]       >= self.vol_ratio_min.value,    # volume confirmation
            df["rsi"]             < self.buy_rsi_max.value,       # not overbought
            df["rsi"]             > self.buy_rsi_min.value,       # momentum present
            df["close"]           > df["ema_50"],                  # short-term trend up
            df["adx"]             > self.adx_min.value,            # only trade trending markets
            df["volume"]          > 0,                             # candle has volume
        ]
        df.loc[reduce(lambda a, b: a & b, conditions), "enter_long"] = 1
        return df

    def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        # Exit when MTF turns bearish OR agents flip to bearish
        df.loc[
            (
                (df["mtf_score"] <= -1) |
                (df["agent_consensus"] <= 3) |
                ((df["rsi"] > 80) & (df["macd"] < df["macd_signal"]))
            ),
            "exit_long"
        ] = 1
        return df
