# source: https://raw.githubusercontent.com/Chris6622/class-AdvancedFuturesSwingStrategy-IStrategy-/786cfa2c323feab11c072706b06cb9e4243af820/user_data/strategies/STCStrategy.py
import pandas as pd
from pandas import DataFrame
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter


class Github_Chris6622_class_AdvancedFuturesSwingStrategy_IStrategy___STCStrategy__20260208_084829(IStrategy):
    """
    Strategy using Schaff Trend Cycle (STC) for buy/sell signals, including shorting.
    """

    INTERFACE_VERSION = 3

    can_short: bool = True  # Enable shorting

    minimal_roi = {
        "60": 0.01,
        "30": 0.02,
        "0": 0.04,
    }

    stoploss = -0.10
    timeframe = "15m"
    process_only_new_candles = True
    startup_candle_count: int = 200

    # Hyperoptable parameters for Schaff Trend Cycle (Buy)
    fast_length_buy = IntParameter(10, 50, default=23, space="buy", optimize=True)
    slow_length_buy = IntParameter(20, 100, default=50, space="buy", optimize=True)
    cycle_length_buy = IntParameter(5, 20, default=10, space="buy", optimize=True)
    d1_length_buy = IntParameter(1, 10, default=3, space="buy", optimize=True)
    d2_length_buy = IntParameter(1, 10, default=3, space="buy", optimize=True)
    upper_band_buy = DecimalParameter(50, 100, default=75, space="buy", optimize=True)
    lower_band_buy = DecimalParameter(0, 50, default=25, space="buy", optimize=True)

    # Hyperoptable parameters for Schaff Trend Cycle (Sell)
    fast_length_sell = IntParameter(10, 50, default=23, space="sell", optimize=True)
    slow_length_sell = IntParameter(20, 100, default=50, space="sell", optimize=True)
    cycle_length_sell = IntParameter(5, 20, default=10, space="sell", optimize=True)
    d1_length_sell = IntParameter(1, 10, default=3, space="sell", optimize=True)
    d2_length_sell = IntParameter(1, 10, default=3, space="sell", optimize=True)
    upper_band_sell = DecimalParameter(50, 100, default=75, space="sell", optimize=True)
    lower_band_sell = DecimalParameter(0, 50, default=25, space="sell", optimize=True)

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Adds Schaff Trend Cycle (STC) and other indicators to the dataframe.
        """

        # Ensure the 'close' column is a Pandas Series
        close = dataframe["close"]

        # MACD calculation for Buy using Pandas
        ema_fast_buy = close.ewm(span=self.fast_length_buy.value, adjust=False).mean()
        ema_slow_buy = close.ewm(span=self.slow_length_buy.value, adjust=False).mean()
        dataframe["macd_buy"] = ema_fast_buy - ema_slow_buy

        # First Stochastic calculation for Buy
        high_k_buy = dataframe["macd_buy"].rolling(window=self.cycle_length_buy.value).max()
        low_k_buy = dataframe["macd_buy"].rolling(window=self.cycle_length_buy.value).min()
        dataframe["k_buy"] = 100 * (dataframe["macd_buy"] - low_k_buy) / (high_k_buy - low_k_buy)

        # First %D for Buy
        dataframe["d_buy"] = dataframe["k_buy"].ewm(span=self.d1_length_buy.value, adjust=False).mean()

        # Second stochastic calculation for Buy
        high_d_buy = dataframe["d_buy"].rolling(window=self.cycle_length_buy.value).max()
        low_d_buy = dataframe["d_buy"].rolling(window=self.cycle_length_buy.value).min()
        dataframe["kd_buy"] = 100 * (dataframe["d_buy"] - low_d_buy) / (high_d_buy - low_d_buy)

        # Final STC calculation for Buy
        dataframe["stc_buy"] = dataframe["kd_buy"].ewm(span=self.d2_length_buy.value, adjust=False).mean().clip(
            lower=0, upper=100
        )

        # MACD calculation for Sell using Pandas
        ema_fast_sell = close.ewm(span=self.fast_length_sell.value, adjust=False).mean()
        ema_slow_sell = close.ewm(span=self.slow_length_sell.value, adjust=False).mean()
        dataframe["macd_sell"] = ema_fast_sell - ema_slow_sell

        # First Stochastic calculation for Sell
        high_k_sell = dataframe["macd_sell"].rolling(window=self.cycle_length_sell.value).max()
        low_k_sell = dataframe["macd_sell"].rolling(window=self.cycle_length_sell.value).min()
        dataframe["k_sell"] = 100 * (dataframe["macd_sell"] - low_k_sell) / (high_k_sell - low_k_sell)

        # First %D for Sell
        dataframe["d_sell"] = dataframe["k_sell"].ewm(span=self.d1_length_sell.value, adjust=False).mean()

        # Second stochastic calculation for Sell
        high_d_sell = dataframe["d_sell"].rolling(window=self.cycle_length_sell.value).max()
        low_d_sell = dataframe["d_sell"].rolling(window=self.cycle_length_sell.value).min()
        dataframe["kd_sell"] = 100 * (dataframe["d_sell"] - low_d_sell) / (high_d_sell - low_d_sell)

        # Final STC calculation for Sell
        dataframe["stc_sell"] = dataframe["kd_sell"].ewm(span=self.d2_length_sell.value, adjust=False).mean().clip(
            lower=0, upper=100
        )

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Defines entry signals based on STC for both long and short positions.
        """
        # Long Entry
        dataframe.loc[
            (
                (dataframe["stc_buy"] > self.lower_band_buy.value)
                & (dataframe["stc_buy"].shift(1) <= self.lower_band_buy.value)
                & (dataframe["volume"] > 0)
            ),
            "enter_long",
        ] = 1

        # Short Entry
        dataframe.loc[
            (
                (dataframe["stc_sell"] < self.upper_band_sell.value)
                & (dataframe["stc_sell"].shift(1) >= self.upper_band_sell.value)
                & (dataframe["volume"] > 0)
            ),
            "enter_short",
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Defines exit signals based on STC for both long and short positions.
        """
        # Long Exit
        dataframe.loc[
            (
                (dataframe["stc_buy"] < self.upper_band_buy.value)
                & (dataframe["stc_buy"].shift(1) >= self.upper_band_buy.value)
                & (dataframe["volume"] > 0)
            ),
            "exit_long",
        ] = 1

        # Short Exit
        dataframe.loc[
            (
                (dataframe["stc_sell"] > self.lower_band_sell.value)
                & (dataframe["stc_sell"].shift(1) <= self.lower_band_sell.value)
                & (dataframe["volume"] > 0)
            ),
            "exit_short",
        ] = 1

        return dataframe