# source: https://raw.githubusercontent.com/timtim-hub/ft5/91699b65ddfb2596a9859320166a218c13add19b/user_data/strategies/BitgetFreqAIStrategy.py
"""
Bitget FreqAI Strategy for Futures Trading
Optimized for 1m-15m trade lengths with 25x leverage
Designed to trade all Bitget futures pairs simultaneously (300+)
"""

import logging
from functools import reduce
from typing import Any

import numpy as np
import talib.abstract as ta
from pandas import DataFrame
from technical import qtpylib

from freqtrade.strategy import IStrategy

logger = logging.getLogger(__name__)


class Github_timtim_hub_ft5__BitgetFreqAIStrategy__20251201_005508(IStrategy):
    """
    Advanced FreqAI strategy for Bitget futures trading.
    
    Features:
    - Optimized for 1m-15m trade lengths
    - 25x leverage support
    - Comprehensive feature engineering for futures markets
    - Adaptive learning with FreqAI
    - Supports long and short positions
    """

    # Strategy interface version
    INTERFACE_VERSION: int = 3

    # Minimal ROI designed for short-term futures trading (1m-15m)
    minimal_roi = {
        "0": 0.02,  # 2% ROI immediately
        "5": 0.015,  # 1.5% ROI after 5 minutes
        "10": 0.01,  # 1% ROI after 10 minutes
        "15": 0.005,  # 0.5% ROI after 15 minutes
        "30": 0.0,  # Exit after 30 minutes regardless
    }

    # Stop loss for futures with leverage (conservative for 25x)
    stoploss = -0.04  # 4% stop loss (with 25x leverage = 100% of margin)

    # Trailing stop for profit protection
    trailing_stop = True
    trailing_stop_positive = 0.01  # Start trailing after 1% profit
    trailing_stop_positive_offset = 0.015  # Trail by 1.5%
    trailing_only_offset_is_reached = True

    # Timeframe for main analysis
    timeframe = "1m"

    # Enable shorting
    can_short = True

    # Process only new candles for performance
    process_only_new_candles = True

    # Startup candle count (needs to be >= largest indicator period)
    startup_candle_count: int = 200

    # Use exit signals
    use_exit_signal = True

    # Exit profit only
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    # Leverage function - set to 25x for all trades
    def leverage(
        self,
        pair: str,
        current_time: Any,
        current_rate: float,
        proposed_leverage: float,
        max_leverage: float,
        entry_tag: str | None,
        side: str,
        **kwargs,
    ) -> float:
        """
        Set leverage to 25x for all futures trades.
        """
        return min(25.0, max_leverage)

    def feature_engineering_expand_all(
        self, dataframe: DataFrame, period: int, metadata: dict, **kwargs
    ) -> DataFrame:
        """
        Automatically expandable features for FreqAI.
        These features will be expanded across timeframes, shifted candles, and correlation pairs.
        All features must be prepended with '%' to be recognized by FreqAI.
        """
        # Momentum indicators
        dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period)
        dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period)
        dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period)
        dataframe["%-mom-period"] = ta.MOM(dataframe, timeperiod=period)

        # Trend indicators
        dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period)
        dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period)
        dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
        dataframe["%-wma-period"] = ta.WMA(dataframe, timeperiod=period)

        # Volatility indicators
        bollinger = qtpylib.bollinger_bands(
            qtpylib.typical_price(dataframe), window=period, stds=2.0
        )
        dataframe["bb_lowerband-period"] = bollinger["lower"]
        dataframe["bb_middleband-period"] = bollinger["mid"]
        dataframe["bb_upperband-period"] = bollinger["upper"]
        dataframe["%-bb_width-period"] = (
            dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"]
        ) / dataframe["bb_middleband-period"]
        dataframe["%-bb_percent-period"] = (
            dataframe["close"] - dataframe["bb_lowerband-period"]
        ) / (dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"])

        # ATR for volatility
        dataframe["%-atr-period"] = ta.ATR(dataframe, timeperiod=period)
        dataframe["%-atr_percent-period"] = (
            dataframe["%-atr-period"] / dataframe["close"]
        ) * 100

        # Volume indicators
        dataframe["%-volume_sma-period"] = ta.SMA(dataframe["volume"], timeperiod=period)
        dataframe["%-volume_ratio-period"] = (
            dataframe["volume"] / dataframe["%-volume_sma-period"]
        )
        dataframe["%-obv-period"] = ta.OBV(dataframe)

        # Price action features
        dataframe["%-high_low_ratio-period"] = (
            dataframe["high"] / dataframe["low"]
        )
        dataframe["%-close_sma_ratio-period"] = (
            dataframe["close"] / dataframe["%-sma-period"]
        )
        dataframe["%-price_change-period"] = (
            dataframe["close"].pct_change(periods=period)
        )

        # Stochastic oscillator
        stoch = ta.STOCH(dataframe)
        dataframe["%-stoch_k-period"] = stoch["slowk"]
        dataframe["%-stoch_d-period"] = stoch["slowd"]

        # MACD
        macd = ta.MACD(dataframe)
        dataframe["%-macd-period"] = macd["macd"]
        dataframe["%-macdsignal-period"] = macd["macdsignal"]
        dataframe["%-macdhist-period"] = macd["macdhist"]

        # Additional momentum features
        dataframe["%-cci-period"] = ta.CCI(dataframe, timeperiod=period)
        dataframe["%-willr-period"] = ta.WILLR(dataframe, timeperiod=period)
        dataframe["%-ultosc-period"] = ta.ULTOSC(dataframe)

        # Price patterns
        dataframe["%-candle_body-period"] = abs(dataframe["close"] - dataframe["open"]) / dataframe["close"]
        dataframe["%-candle_range-period"] = (dataframe["high"] - dataframe["low"]) / dataframe["close"]
        dataframe["%-upper_shadow-period"] = (dataframe["high"] - np.maximum(dataframe["open"], dataframe["close"])) / dataframe["close"]
        dataframe["%-lower_shadow-period"] = (np.minimum(dataframe["open"], dataframe["close"]) - dataframe["low"]) / dataframe["close"]

        # Trend strength
        dataframe["%-aroon_up-period"] = ta.AROON(dataframe, timeperiod=period)["aroonup"]
        dataframe["%-aroon_down-period"] = ta.AROON(dataframe, timeperiod=period)["aroondown"]
        dataframe["%-aroon_osc-period"] = dataframe["%-aroon_up-period"] - dataframe["%-aroon_down-period"]

        return dataframe

    def feature_engineering_expand_basic(
        self, dataframe: DataFrame, metadata: dict, **kwargs
    ) -> DataFrame:
        """
        Basic features that expand across timeframes and shifted candles but not indicator periods.
        """
        # Price momentum
        dataframe["%-pct-change"] = dataframe["close"].pct_change()
        dataframe["%-pct-change-2"] = dataframe["close"].pct_change(periods=2)
        dataframe["%-pct-change-5"] = dataframe["close"].pct_change(periods=5)

        # Volume features
        dataframe["%-raw_volume"] = dataframe["volume"]
        dataframe["%-volume_pct"] = dataframe["volume"].pct_change()

        # Price position in range
        dataframe["%-price_position"] = (
            dataframe["close"] - dataframe["low"]
        ) / (dataframe["high"] - dataframe["low"])

        # Long-term trend
        dataframe["%-ema-200"] = ta.EMA(dataframe, timeperiod=200)
        dataframe["%-sma-200"] = ta.SMA(dataframe, timeperiod=200)

        # Momentum features
        dataframe["%-momentum"] = dataframe["close"] / dataframe["close"].shift(10) - 1
        dataframe["%-rate_of_change"] = dataframe["close"].pct_change(periods=10)

        # Volatility features
        dataframe["%-price_volatility"] = dataframe["close"].pct_change().rolling(10).std()
        dataframe["%-volume_volatility"] = dataframe["volume"].pct_change().rolling(10).std()

        # Market microstructure
        dataframe["%-spread_proxy"] = (dataframe["high"] - dataframe["low"]) / dataframe["close"]
        price_change = abs(dataframe["close"] - dataframe["close"].shift(10))
        price_range = dataframe["high"].rolling(10).max() - dataframe["low"].rolling(10).min()
        dataframe["%-efficiency_ratio"] = np.where(
            price_range > 0,
            price_change / price_range,
            0
        )

        return dataframe

    def feature_engineering_standard(
        self, dataframe: DataFrame, metadata: dict, **kwargs
    ) -> DataFrame:
        """
        Standard features that don't expand automatically.
        Good for time-based features and pair-specific features.
        """
        # Time-based features
        dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
        dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
        dataframe["%-minute_of_hour"] = dataframe["date"].dt.minute

        # Funding rate proxy (for futures)
        # Use price difference between close and typical price as proxy
        dataframe["%-funding_proxy"] = (
            dataframe["close"] - qtpylib.typical_price(dataframe)
        ) / dataframe["close"]

        # Volatility regime (using ATR if available, otherwise use price volatility)
        if "%-atr-period" in dataframe.columns:
            dataframe["%-volatility_regime"] = (
                dataframe["%-atr-period"].rolling(20).mean()
                / dataframe["%-atr-period"].rolling(100).mean()
            )
        else:
            dataframe["%-volatility_regime"] = (
                dataframe["close"].pct_change().rolling(20).std()
                / dataframe["close"].pct_change().rolling(100).std()
            )

        # Futures-specific features
        # Funding rate momentum (price premium/discount)
        dataframe["%-funding_momentum"] = dataframe["%-funding_proxy"].pct_change()
        
        # Price momentum vs volume momentum
        dataframe["%-price_volume_divergence"] = (
            dataframe["close"].pct_change() - dataframe["volume"].pct_change()
        )

        # Trend consistency (how consistent is the trend)
        dataframe["%-trend_consistency"] = (
            (dataframe["close"] > dataframe["close"].shift(1)).rolling(10).sum() / 10
        )

        # Market regime (trending vs ranging)
        dataframe["%-trending_market"] = (
            (dataframe["%-adx-period"] > 25).astype(int) if "%-adx-period" in dataframe.columns
            else (dataframe["close"].pct_change().abs().rolling(20).mean() > 0.01).astype(int)
        )

        return dataframe

    def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
        """
        Define prediction targets for FreqAI.
        Targets must be prepended with '&' to be recognized.
        
        For 1m-15m trade lengths, we predict:
        - Future price movement over next 1-15 candles
        - Direction and magnitude
        """
        # Get label period from config (typically 1-15 candles for short-term trading)
        label_period = self.freqai_info["feature_parameters"]["label_period_candles"]

        # Target 1: Future price return (for regression)
        # Predict the average return over the next N candles
        dataframe["&-s_close"] = (
            dataframe["close"]
            .shift(-label_period)
            .rolling(label_period)
            .mean()
            / dataframe["close"]
            - 1
        )

        # Target 2: Maximum profit potential (for optimization)
        # Predict the maximum profit achievable in the next N candles
        dataframe["&-s_max_profit"] = (
            dataframe["high"]
            .shift(-label_period)
            .rolling(label_period)
            .max()
            / dataframe["close"]
            - 1
        )

        # Target 3: Maximum loss potential (risk assessment)
        dataframe["&-s_max_loss"] = (
            dataframe["low"]
            .shift(-label_period)
            .rolling(label_period)
            .min()
            / dataframe["close"]
            - 1
        )

        # Target 4: Directional accuracy (for classification)
        # Predict if price will go up or down
        future_price = dataframe["close"].shift(-label_period)
        dataframe["&-s_direction"] = np.where(
            future_price > dataframe["close"], 1, -1
        )

        # Target 5: Volatility-adjusted return (Sharpe-like metric)
        future_returns = (future_price / dataframe["close"] - 1)
        future_volatility = dataframe["close"].pct_change().rolling(label_period).std()
        dataframe["&-s_sharpe_proxy"] = np.where(
            future_volatility > 0,
            future_returns / future_volatility,
            0
        )

        # Target 6: Risk-reward ratio
        max_profit = dataframe["high"].shift(-label_period).rolling(label_period).max() / dataframe["close"] - 1
        max_loss = abs(dataframe["low"].shift(-label_period).rolling(label_period).min() / dataframe["close"] - 1)
        dataframe["&-s_risk_reward"] = np.where(
            max_loss > 0,
            max_profit / max_loss,
            0
        )

        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Populate indicators using FreqAI.
        All indicators should be defined in feature_engineering functions.
        """
        # Start FreqAI - this will populate all features and predictions
        dataframe = self.freqai.start(dataframe, metadata, self)

        return dataframe

    def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        """
        Entry logic based on FreqAI predictions.
        """
        # Long entry conditions - improved with multiple targets
        enter_long_conditions = [
            df["do_predict"] == 1,  # Model confidence check
            df["&-s_close"] > 0.005,  # Predicted 0.5%+ gain
            df["&-s_max_profit"] > 0.01,  # Potential for 1%+ profit
            df["&-s_max_loss"] > -0.02,  # Risk limited to 2% loss
        ]
        
        # Add risk-reward ratio condition if available
        if "&-s_risk_reward" in df.columns:
            enter_long_conditions.append(df["&-s_risk_reward"] > 1.5)  # At least 1.5:1 risk-reward
        
        # Add directional accuracy if available
        if "&-s_direction" in df.columns:
            enter_long_conditions.append(df["&-s_direction"] == 1)  # Predicted upward movement
        
        # Add z-score condition if mean/std are available
        if "&-s_close_mean" in df.columns and "&-s_close_std" in df.columns:
            enter_long_conditions.append(
                df["&-s_close"] > df["&-s_close_mean"] + 0.5 * df["&-s_close_std"]
            )

        if enter_long_conditions:
            df.loc[
                reduce(lambda x, y: x & y, enter_long_conditions),
                ["enter_long", "enter_tag"],
            ] = (1, "freqai_long")

        # Short entry conditions - improved with multiple targets
        enter_short_conditions = [
            df["do_predict"] == 1,  # Model confidence check
            df["&-s_close"] < -0.005,  # Predicted 0.5%+ drop
            df["&-s_max_loss"] < -0.01,  # Potential for 1%+ profit (short)
            df["&-s_max_profit"] < 0.02,  # Risk limited to 2% loss
        ]
        
        # Add risk-reward ratio condition if available
        if "&-s_risk_reward" in df.columns:
            enter_short_conditions.append(df["&-s_risk_reward"] > 1.5)  # At least 1.5:1 risk-reward
        
        # Add directional accuracy if available
        if "&-s_direction" in df.columns:
            enter_short_conditions.append(df["&-s_direction"] == -1)  # Predicted downward movement
        
        # Add z-score condition if mean/std are available
        if "&-s_close_mean" in df.columns and "&-s_close_std" in df.columns:
            enter_short_conditions.append(
                df["&-s_close"] < df["&-s_close_mean"] - 0.5 * df["&-s_close_std"]
            )

        if enter_short_conditions:
            df.loc[
                reduce(lambda x, y: x & y, enter_short_conditions),
                ["enter_short", "enter_tag"],
            ] = (1, "freqai_short")

        return df

    def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        """
        Exit logic based on FreqAI predictions.
        """
        # Exit long when prediction turns negative or profit target reached
        exit_long_base = df["do_predict"] == 1
        exit_long_signal = df["&-s_close"] < 0
        
        # Add z-score condition if mean/std are available
        if "&-s_close_mean" in df.columns and "&-s_close_std" in df.columns:
            exit_long_zscore = df["&-s_close"] < df["&-s_close_mean"] - 0.3 * df["&-s_close_std"]
            exit_long_condition = exit_long_base & (exit_long_signal | exit_long_zscore)
        else:
            exit_long_condition = exit_long_base & exit_long_signal

        df.loc[exit_long_condition, "exit_long"] = 1

        # Exit short when prediction turns positive or profit target reached
        exit_short_base = df["do_predict"] == 1
        exit_short_signal = df["&-s_close"] > 0
        
        # Add z-score condition if mean/std are available
        if "&-s_close_mean" in df.columns and "&-s_close_std" in df.columns:
            exit_short_zscore = df["&-s_close"] > df["&-s_close_mean"] + 0.3 * df["&-s_close_std"]
            exit_short_condition = exit_short_base & (exit_short_signal | exit_short_zscore)
        else:
            exit_short_condition = exit_short_base & exit_short_signal

        df.loc[exit_short_condition, "exit_short"] = 1

        return df

    def confirm_trade_entry(
        self,
        pair: str,
        order_type: str,
        amount: float,
        rate: float,
        time_in_force: str,
        current_time: Any,
        entry_tag: str | None,
        side: str,
        **kwargs,
    ) -> bool:
        """
        Additional confirmation before entering a trade.
        """
        # Get the latest dataframe
        df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = df.iloc[-1].squeeze()

        # Reject if price moved too much from prediction
        if side == "long":
            if rate > (last_candle["close"] * (1 + 0.005)):  # 0.5% slippage tolerance
                return False
        else:  # short
            if rate < (last_candle["close"] * (1 - 0.005)):  # 0.5% slippage tolerance
                return False

        return True

