# source: https://raw.githubusercontent.com/jaredrsommer/freqtradestrategies/63cd46ea087ed8ed01f7d54d8cfcf1dbed166b04/GKD_HurstExponent.py
from freqtrade.strategy import IStrategy
from pandas_ta import ema
import pandas as pd
import numpy as np
import talib
import logging
from typing import Dict, List
from pandas import DataFrame

# Set up logging for debugging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

class Github_jaredrsommer_freqtradestrategies__GKD_HurstExponent__20260509_225308(IStrategy):
    # Strategy parameters
    timeframe = "1h"
    minimal_roi = {"0": 0.05, "60": 0.03, "120": 0.01}
    stoploss = -0.10
    trailing_stop = True
    trailing_stop_positive = 0.02
    trailing_stop_positive_offset = 0.03

    # Custom parameters
    hurst_period = 64           # Lookback period for Hurst Exponent
    hurst_smooth_period = 5     # EMA smoothing period for Hurst Exponent
    hurst_threshold = 0.55       # Threshold for trending market
    hurst_exit_threshold = 0.25  # Threshold for exit (random/mean-reverting)
    baseline_period = 20        # Period for baseline EMA
    atr_period = 14            # Period for ATR (Goldie Locks Zone)
    goldie_locks_min = 0.2     # Min multiplier for Goldie Locks Zone
    goldie_locks_max = 1.0     # Max multiplier for Goldie Locks Zone

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Calculate log-returns
        dataframe["log_return"] = np.log(dataframe["close"] / dataframe["close"].shift(1))
        
        # Calculate Hurst Exponent
        dataframe["hurst"] = self.calculate_hurst(dataframe["log_return"], self.hurst_period)
        dataframe['zero'] = 0.5
        
        # Smooth Hurst Exponent
        dataframe["hurst_smooth"] = ema(dataframe["hurst"], length=self.hurst_smooth_period)

        # Baseline (EMA)
        dataframe["baseline"] = ema(dataframe["close"], length=self.baseline_period)
        dataframe["baseline_diff"] = dataframe["baseline"].diff()
        dataframe["baseline_up"] = dataframe["baseline_diff"] > 0
        dataframe["baseline_down"] = dataframe["baseline_diff"] < 0

        # Volatility (ATR for Goldie Locks Zone)
        dataframe["atr"] = talib.ATR(
            dataframe["high"], dataframe["low"], dataframe["close"], timeperiod=self.atr_period
        )
        dataframe["goldie_min"] = dataframe["baseline"] - (dataframe["atr"] * self.goldie_locks_min)
        dataframe["goldie_max"] = dataframe["baseline"] + (dataframe["atr"] * self.goldie_locks_max)

        # Debug logging
        logger.debug(f"Recent hurst values: {dataframe['hurst'].tail(10).to_list()}")
        logger.debug(f"Recent hurst_smooth values: {dataframe['hurst_smooth'].tail(10).to_list()}")

        return dataframe

    def calculate_hurst(self, series: pd.Series, period: int) -> pd.Series:
        # Hurst Exponent using single-window R/S analysis on log-returns
        hurst = pd.Series(np.nan, index=series.index)
        
        for i in range(period, len(series)):
            window = series.iloc[i-period:i].dropna()
            if len(window) < period:
                logger.debug(f"Insufficient data at i={i}, len={len(window)}")
                hurst.iloc[i] = 0.5
                continue
            
            # Mean-adjusted series
            mean = window.mean()
            mean_adj = window - mean
            # Cumulative deviation
            cum_dev = mean_adj.cumsum()
            # Range (R)
            r = cum_dev.max() - cum_dev.min()
            # Standard deviation (S)
            s = window.std()
            
            # R/S calculation
            if s == 0 or r == 0 or np.isnan(s) or np.isnan(r):
                logger.debug(f"Invalid R/S at i={i}: r={r}, s={s}")
                # Fallback: variance-based estimate
                variance = window.var()
                if variance > 0:
                    hurst.iloc[i] = 0.5 + np.log(variance) / (2 * np.log(period))
                    hurst.iloc[i] = np.clip(hurst.iloc[i], 0, 1)
                else:
                    hurst.iloc[i] = 0.5
                continue
            
            rs = r / s
            if rs <= 0:
                logger.debug(f"Invalid rs at i={i}: rs={rs}")
                hurst.iloc[i] = 0.5
                continue
            
            # Approximate H without Anis-Lloyd correction
            h = np.log(rs) / np.log(period)
            hurst.iloc[i] = np.clip(h, 0, 1)
            logger.debug(f"H at i={i}: rs={rs}, h={h}, clipped={hurst.iloc[i]}")
        
        return hurst

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Volatility/Volume Entry Logic (GKD-V)
        dataframe.loc[
            (dataframe["hurst_smooth"] > self.hurst_threshold) &  # Hurst indicates trending market
            (dataframe["baseline_up"]) &                         # Baseline confirms uptrend
            (dataframe["close"] >= dataframe["goldie_min"]) &    # Within Goldie Locks Zone
            (dataframe["close"] <= dataframe["goldie_max"]),
            "enter_long"
        ] = 1

        dataframe.loc[
            (dataframe["hurst_smooth"] > self.hurst_threshold) &  # Hurst indicates trending market
            (dataframe["baseline_down"]) &                       # Baseline confirms downtrend
            (dataframe["close"] >= dataframe["goldie_min"]) &    # Within Goldie Locks Zone
            (dataframe["close"] <= dataframe["goldie_max"]),
            "enter_short"
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Exit Logic: Hurst indicates random/mean-reverting market
        dataframe.loc[
            (dataframe["hurst_smooth"] < self.hurst_exit_threshold),  # Hurst below threshold
            "exit_long"
        ] = 1

        dataframe.loc[
            (dataframe["hurst_smooth"] < self.hurst_exit_threshold),  # Hurst below threshold
            "exit_short"
        ] = 1

        return dataframe