# source: https://raw.githubusercontent.com/Minimal88/AstroTrade/98dde53056cf48166d8ae9e8a88f22436ba97f68/freqtrade/user_data/strategies/KamaStrategy.py
# https://jesse.trade/strategies/kama-trendfollowing
import datetime
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
from pandas import DataFrame
import ta

class Github_Minimal88_AstroTrade__KamaStrategy__20250711_051554(IStrategy):
    """
    Github_Minimal88_AstroTrade__KamaStrategy__20250711_051554 implements a Freqtrade trading strategy based on the Kaufman's Adaptive Moving Average (KAMA),
    ADX, Choppiness Index, Bollinger Band width, and ATR indicators.

    Attributes:
        INTERFACE_VERSION (int): Freqtrade strategy interface version.
        minimal_roi (dict): Minimal return on investment configuration.
        stoploss (float): Stoploss value.
        trailing_stop (bool): Enable trailing stop.
        trailing_stop_positive (float): Trailing stop positive threshold.
        trailing_stop_positive_offset (float): Trailing stop positive offset.
        trailing_only_offset_is_reached (bool): Only trail after offset is reached.
        use_custom_stoploss (bool): Use custom stoploss logic.
        use_exit_signal (bool): Use sell signal logic.
        exit_profit_only (bool): Only sell if profitable.
        ignore_buying_expired_candle_after (int): Ignore buying after N candles.
        kama_window (IntParameter): Window size for KAMA calculation.
        adx_threshold (IntParameter): ADX threshold for trend strength.
        chop_threshold (IntParameter): Choppiness Index threshold for trendiness.
        bb_width_threshold (DecimalParameter): Bollinger Band width threshold.

    Methods:
        populate_indicators(df, metadata):
            Adds KAMA, ADX, Choppiness Index, Bollinger Band width, ATR, and long-term KAMA indicators to the dataframe.

        populate_buy_trend(df, metadata):
            Sets buy signal when close is above KAMA and long-term KAMA, ADX and trendiness thresholds are met,
            and Bollinger Band width is below threshold.

        populate_sell_trend(df, metadata):
            Sets sell signal when close is below KAMA and long-term KAMA, ADX and trendiness thresholds are met,
            and Bollinger Band width is below threshold.

        custom_exit(pair, trade, current_time, current_rate, current_profit, **kwargs):
            (Optional) Example placeholder for ATR-based custom sell logic.
    """
    INTERFACE_VERSION = 3
    # Enable shorting
    can_short = True
    minimal_roi = {"0": 0.03}
    stoploss = -0.30
    trailing_stop = True
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.03
    trailing_only_offset_is_reached = True

    use_custom_stoploss = False
    use_exit_signal = True
    exit_profit_only = False
    ignore_buying_expired_candle_after = 0

    # timeframe = '30m'
    # For multi-timeframe, you need to set this and implement populate_indicators accordingly
    # informative_timeframes = {'4h': '4h'}

    kama_window = IntParameter(10, 50, default=30, space='buy')
    adx_threshold = IntParameter(40, 60, default=50, space='buy')
    chop_threshold = IntParameter(40, 60, default=50, space='buy')
    bb_width_threshold = DecimalParameter(0.01, 0.07, default=0.07, space='buy')

    def populate_indicators(self, df: DataFrame, metadata: dict) -> DataFrame:
        """
        Adds all required technical indicators to the dataframe for use in buy/sell logic.

        Indicators added:
            - KAMA (Kaufman's Adaptive Moving Average)
            - ADX (Average Directional Index)
            - Choppiness Index (custom implementation)
            - Bollinger Band width
            - ATR (Average True Range)
            - Long-term KAMA (approximated by rolling mean of KAMA)

        Args:
            df (DataFrame): The input DataFrame with OHLCV data.
            metadata (dict): Additional information (not used here).

        Returns:
            DataFrame: The DataFrame with new indicator columns added.
        """
        # Calculate KAMA (Kaufman's Adaptive Moving Average)
        df['kama'] = ta.momentum.KAMAIndicator(df['close'], window=int(self.kama_window.value)).kama()
        # Calculate ADX (Average Directional Index)
        df['adx'] = ta.trend.ADXIndicator(df['high'], df['low'], df['close'], window=14).adx()
        # Choppiness Index is not in ta, so we implement it
        def choppiness(high, low, close, window=14):
            # Calculate True Range (TR)
            tr = ta.volatility.AverageTrueRange(high, low, close, window=1).average_true_range()
            # Highest high and lowest low over the window
            high_max = high.rolling(window).max()
            low_min = low.rolling(window).min()
            # Sum of TR over the window
            sum_tr = tr.rolling(window).sum()
            # Choppiness Index formula
            chop = 100 * np.log10(sum_tr / (high_max - low_min)) / np.log10(window)
            return chop
        import numpy as np
        # Calculate Choppiness Index
        df['chop'] = choppiness(df['high'], df['low'], df['close'], window=14)
        # Calculate Bollinger Band width
        bb = ta.volatility.BollingerBands(df['close'], window=20, window_dev=2)
        df['bb_width'] = (bb.bollinger_hband() - bb.bollinger_lband()) / df['close']
        # Calculate ATR (Average True Range)
        df['atr'] = ta.volatility.AverageTrueRange(df['high'], df['low'], df['close'], window=14).average_true_range()
        # Calculate long-term KAMA as a rolling mean of KAMA (approximate higher timeframe trend)
        df['long_term_kama'] = df['kama'].rolling(8).mean()  # Approximate 4h trend

        # For tracking last trade index, Freqtrade doesn't support this natively in the dataframe
        # You can use custom logic in custom_buy/sell if needed

        return df

    def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        """
        Populates the 'enter_long' signal in the DataFrame based on custom trading conditions for Long positions.

        This function evaluates a set of technical indicators and assigns a value of 1 to the 'enter_long' column
        for rows where all the following conditions are met:
            - The closing price is above the Kaufman's Adaptive Moving Average (KAMA).
            - The Average Directional Index (ADX) is greater than a specified threshold.
            - The closing price is above a long-term KAMA.
            - The Choppiness Index (CHOP) is below a specified threshold.
            - The Bollinger Band width is below a specified threshold.

        Note:
            - There is no direct implementation for ensuring at least 10 candles have passed since the last trade,
              as this is not natively supported in Freqtrade's strategy framework.

        Args:
            df (DataFrame): The input DataFrame containing price and indicator data.
            metadata (dict): Additional information, not used in this function.

        Returns:
            DataFrame: The DataFrame with the 'enter_long' column updated according to the strategy's buy conditions.
        """
        df.loc[
            (
                (df['close'] > df['kama']) &
                (df['adx'] > self.adx_threshold.value) &
                (df['close'] > df['long_term_kama']) &
                (df['chop'] < self.chop_threshold.value) &
                (df['bb_width'] < float(self.bb_width_threshold.value))
                # No direct way to check "at least 10 candles since last trade" in Freqtrade
            ),
            'enter_long'
        ] = 1
        
        return df

    def populate_short_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        """
        Populates the 'enter_short' signal in the DataFrame based on custom trading conditions for Short positions.

        This function evaluates a set of technical indicators and assigns a value of 1 to the 'enter_short' column
        for rows where all the following conditions are met:
            - The closing price is below the Kaufman's Adaptive Moving Average (KAMA).
            - The Average Directional Index (ADX) is greater than a specified threshold.
            - The closing price is below a long-term KAMA.
            - The Choppiness Index (CHOP) is below a specified threshold.
            - The Bollinger Band width is below a specified threshold.

        Args:
            df (DataFrame): The input DataFrame containing price and indicator data.
            metadata (dict): Additional information, not used in this function.

        Returns:
            DataFrame: The DataFrame with the 'enter_short' column updated according to the strategy's short conditions.
        """
        df.loc[
            (
                (df['close'] < df['kama']) &
                (df['adx'] > self.adx_threshold.value) &
                (df['close'] < df['long_term_kama']) &
                (df['chop'] < self.chop_threshold.value) &
                (df['bb_width'] < float(self.bb_width_threshold.value))
            ),
            'enter_short'
        ] = 1
        return df

    def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        """
        Populates the 'exit_long' and 'exit_short' signals in the DataFrame based on multiple technical indicators.

        This function applies a set of conditions to the input DataFrame `df` to determine when an exit signal should be generated.
        The exit signal is set to 1 for rows where all of the following conditions are met:
            - The closing price is below the KAMA (Kaufman's Adaptive Moving Average).
            - The ADX (Average Directional Index) is above a specified threshold, indicating a strong trend.
            - The closing price is below the long-term KAMA, suggesting a bearish trend.
            - The Choppiness Index is below a specified threshold, indicating the market is trending rather than ranging.
            - The Bollinger Band width is below a specified threshold, indicating low volatility.

        Args:
            df (DataFrame): The input DataFrame containing price and indicator columns.
            metadata (dict): Additional metadata (not used in this function, but required by the interface).

        Returns:
            DataFrame: The input DataFrame with the 'exit_long' and 'exit_short' columns updated where the exit conditions are met.
        """
        # Exit long positions
        df.loc[
            (
                (df['close'] < df['kama']) &
                (df['adx'] > self.adx_threshold.value) &
                (df['close'] < df['long_term_kama']) &
                (df['chop'] < self.chop_threshold.value) &
                (df['bb_width'] < float(self.bb_width_threshold.value))
            ),
            'exit_long'
        ] = 1
        
        # Exit short positions (opposite conditions)
        df.loc[
            (
                (df['close'] > df['kama']) &
                (df['adx'] > self.adx_threshold.value) &
                (df['close'] > df['long_term_kama']) &
                (df['chop'] < self.chop_threshold.value) &
                (df['bb_width'] < float(self.bb_width_threshold.value))
            ),
            'exit_short'
        ] = 1
        
        return df

    # ATR-based stoploss/takeprofit is not natively supported in Freqtrade, but you can use custom_stoploss
    # or custom_exit for advanced logic if needed.

    def custom_exit(self, pair: str, trade, current_time, current_rate, current_profit, **kwargs):
        # Example: ATR-based take profit/stoploss (not exactly like Jesse, but similar)
        # You can access indicators via self.dp.get_analyzed_dataframe(pair, self.timeframe)
        df_tuple = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if df_tuple is None or trade is None:
            return None

        df = df_tuple[0] if isinstance(df_tuple, tuple) else df_tuple
        if df is None or len(df) == 0:
            return None

        # Find the current candle
        last_candle = df.iloc[-1]
        atr = last_candle.get('atr', None)
        if atr is None:
            return None

        # Example logic: Sell if price drops below entry - 2*ATR (trailing stop)
        if current_rate < (trade.open_rate - 2 * atr):
            return 'atr_stoploss'

        # Example logic: Take profit if price exceeds entry + 3*ATR
        if current_rate > (trade.open_rate + 3 * atr):
            return 'atr_takeprofit'
        return None
