# source: https://raw.githubusercontent.com/mlsys-io/PortfolioBench/fb47ffc7b262e80c8b0b2f9bc0445b210cb475ec/strategy/StochasticCciStrategy.py
from datetime import datetime

import talib.abstract as ta
from freqtrade.strategy import (
    IntParameter,
    IStrategy,
)
from pandas import DataFrame
from technical import qtpylib


class Github_mlsys_io_PortfolioBench__StochasticCciStrategy__20260414_143359(IStrategy):
    """
    Stochastic Oscillator + Commodity Channel Index (CCI) momentum strategy.

    Combines the Stochastic Oscillator (Lane, 1984) with the CCI (Lambert, 1980)
    for dual momentum confirmation. Enters when both indicators signal oversold
    conditions in an uptrend; exits on overbought or trend reversal.
    """

    INTERFACE_VERSION = 3

    timeframe = "1h"

    can_short: bool = False

    minimal_roi = {}

    stoploss = -99

    trailing_stop = False

    process_only_new_candles = True

    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    startup_candle_count: int = 30

    # Strategy parameters
    stoch_k_period = IntParameter(10, 20, default=14, space="buy")
    stoch_d_period = IntParameter(3, 5, default=3, space="buy")
    stoch_slowing = IntParameter(3, 5, default=3, space="buy")
    cci_period = IntParameter(14, 30, default=20, space="buy")
    sma_period = IntParameter(150, 250, default=200, space="buy")

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        stoch = ta.STOCH(
            dataframe,
            fastk_period=self.stoch_k_period.value,
            slowk_period=self.stoch_d_period.value,
            slowd_period=self.stoch_slowing.value,
        )
        dataframe["slowk"] = stoch["slowk"]
        dataframe["slowd"] = stoch["slowd"]

        dataframe["cci"] = ta.CCI(dataframe, timeperiod=self.cci_period.value)

        dataframe["sma200"] = ta.SMA(dataframe, timeperiod=self.sma_period.value)

        dataframe["mean-volume"] = dataframe["volume"].rolling(20).mean()

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                # Stochastic %K crosses above %D in oversold zone
                (qtpylib.crossed_above(dataframe["slowk"], dataframe["slowd"]))
                & (dataframe["slowk"] < 30)
                # CCI confirms oversold bounce
                & (dataframe["cci"] > -100)
                & (dataframe["cci"].shift(1) <= -100)
                # Price above long-term SMA (uptrend filter)
                & (dataframe["close"] > dataframe["sma200"])
                & (dataframe["mean-volume"] > 0.75)
            ),
            "enter_long",
        ] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                # Stochastic overbought
                (
                    (dataframe["slowk"] > 80)
                    & (qtpylib.crossed_below(dataframe["slowk"], dataframe["slowd"]))
                )
                # OR CCI overbought reversal
                | (
                    (dataframe["cci"] < 100)
                    & (dataframe["cci"].shift(1) >= 100)
                )
            ),
            "exit_long",
        ] = 1
        return dataframe

    def confirm_trade_entry(
        self, pair: str, order_type: str, amount: float, rate: float,
        time_in_force: str, current_time: datetime, entry_tag: str | None,
        side: str, **kwargs,
    ) -> bool:
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_close = dataframe.iloc[-1]["close"]
        max_deviation = 0.01
        deviation = abs(rate - last_close) / last_close
        if deviation > max_deviation:
            return False
        return True
