# source: https://raw.githubusercontent.com/vaskosmihaylov/nfi-custom-strategies/3e3c9f0b59c5a387dfafb0f8205dad722827d0fe/user_data/strategies/AdvancedFuturesSwingStrategy/AdvancedFuturesSwingStrategy.py
"""
Advanced Multi-Timeframe Futures Swing Strategy for Freqtrade
=============================================================
This strategy incorporates:
- Higher Timeframe (HTF) trend analysis
- Distribution & Accumulation zones
- Support & Resistance detection
- Long wick analysis (rejection patterns)
- Order flow confirmation
- Smart Money Concepts (SMC)
"""

from freqtrade.strategy import IStrategy, DecimalParameter, IntParameter, merge_informative_pair
from pandas import DataFrame
import pandas as pd
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from functools import reduce
import numpy as np
from datetime import datetime


class Github_vaskosmihaylov_nfi_custom_strategies__AdvancedFuturesSwingStrategy__20260609_062716(IStrategy):
    """
    Advanced Multi-Timeframe Futures Swing Trading Strategy
    with Order Flow Analysis
    """
    
    INTERFACE_VERSION = 3
    
    # Base timeframe for entry/exit
    timeframe = '15m'
    
    # Can short
    can_short = True
    
    # ROI table
    minimal_roi = {
        "0": 0.20,
        "720": 0.15,
        "1440": 0.10,
        "2880": 0.06
    }
    
    # Stoploss
    stoploss = -0.06
    
    # Trailing stop
    trailing_stop = True
    trailing_stop_positive = 0.015
    trailing_stop_positive_offset = 0.03
    trailing_only_offset_is_reached = True
    
    # Leverage
    leverage_num = 3
    
    # ===== HYPEROPT PARAMETERS =====
    # These will be optimized quarterly and auto-injected
    
    # Trend Parameters
    htf_trend_ema = IntParameter(50, 200, default=100, space='buy', optimize=True)
    accumulation_period = IntParameter(20, 50, default=30, space='buy', optimize=True)
    
    # Wick Analysis
    wick_threshold = DecimalParameter(0.3, 0.7, default=0.5, space='buy', decimals=2, optimize=True)
    upper_wick_min = DecimalParameter(0.4, 0.8, default=0.6, space='buy', decimals=2, optimize=True)
    lower_wick_min = DecimalParameter(0.4, 0.8, default=0.6, space='buy', decimals=2, optimize=True)
    
    # Volume Parameters
    volume_surge = DecimalParameter(1.5, 3.0, default=2.0, space='buy', decimals=1, optimize=True)
    volume_confirmation = DecimalParameter(1.2, 2.5, default=1.5, space='buy', decimals=1, optimize=True)
    
    # Support/Resistance
    sr_lookback = IntParameter(50, 100, default=75, space='buy', optimize=True)
    sr_threshold = DecimalParameter(0.002, 0.01, default=0.005, space='buy', decimals=3, optimize=True)
    
    # RSI Parameters
    rsi_buy_min = IntParameter(30, 45, default=40, space='buy', optimize=True)
    rsi_buy_max = IntParameter(50, 65, default=65, space='buy', optimize=True)
    rsi_sell_min = IntParameter(60, 75, default=65, space='sell', optimize=True)
    rsi_overbought = IntParameter(65, 80, default=70, space='sell', optimize=True)
    
    # ADX Parameters
    adx_trend_min = IntParameter(15, 30, default=20, space='buy', optimize=True)
    adx_strong_trend = IntParameter(25, 40, default=25, space='buy', optimize=True)
    adx_weak_trend = IntParameter(10, 20, default=15, space='sell', optimize=True)
    
    # EMA Parameters
    ema_fast = IntParameter(8, 25, default=20, space='buy', optimize=True)
    ema_slow = IntParameter(40, 100, default=50, space='buy', optimize=True)
    
    # Order Flow
    delta_threshold = DecimalParameter(0, 1000, default=100, space='buy', decimals=0, optimize=True)
    cumulative_delta_period = IntParameter(10, 30, default=20, space='buy', optimize=True)
    
    # Accumulation/Distribution
    volatility_threshold = DecimalParameter(0.5, 0.9, default=0.7, space='buy', decimals=1, optimize=True)
    volume_acc_multiplier = DecimalParameter(1.1, 1.5, default=1.2, space='buy', decimals=1, optimize=True)
    
    # Exit Parameters
    exit_volume_low = DecimalParameter(0.6, 0.9, default=0.8, space='sell', decimals=1, optimize=True)
    exit_rsi_high = IntParameter(55, 70, default=60, space='sell', optimize=True)
    
    startup_candle_count: int = 400
    
    use_exit_signal = True
    exit_profit_only = False
    
    def informative_pairs(self):
        """
        Define additional informative pairs for HTF analysis
        """
        pairs = self.dp.current_whitelist()
        informative_pairs = []
        
        # Add higher timeframes for each pair
        for pair in pairs:
            informative_pairs.append((pair, '1h'))   # Higher timeframe 1
            informative_pairs.append((pair, '4h'))   # Higher timeframe 2
            informative_pairs.append((pair, '1d'))   # Higher timeframe 3
            
        return informative_pairs
    
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Generate indicators including HTF analysis
        """
        
        # ===== BASE TIMEFRAME INDICATORS =====
        
        # Price Action
        dataframe['hl_avg'] = (dataframe['high'] + dataframe['low']) / 2
        dataframe['body'] = abs(dataframe['close'] - dataframe['open'])
        dataframe['range'] = dataframe['high'] - dataframe['low']
        
        # Upper and Lower Wicks
        dataframe['upper_wick'] = dataframe['high'] - dataframe[['open', 'close']].max(axis=1)
        dataframe['lower_wick'] = dataframe[['open', 'close']].min(axis=1) - dataframe['low']
        
        # Wick to body ratio (rejection signals)
        dataframe['upper_wick_ratio'] = dataframe['upper_wick'] / (dataframe['range'] + 1e-10)
        dataframe['lower_wick_ratio'] = dataframe['lower_wick'] / (dataframe['range'] + 1e-10)
        
        # Long wick detection
        dataframe['long_upper_wick'] = (dataframe['upper_wick_ratio'] > self.upper_wick_min.value) & \
                                       (dataframe['upper_wick'] > dataframe['body'] * 2)
        dataframe['long_lower_wick'] = (dataframe['lower_wick_ratio'] > self.lower_wick_min.value) & \
                                       (dataframe['lower_wick'] > dataframe['body'] * 2)
        
        # EMAs
        dataframe['ema_fast'] = ta.EMA(dataframe, timeperiod=self.ema_fast.value)
        dataframe['ema_slow'] = ta.EMA(dataframe, timeperiod=self.ema_slow.value)
        dataframe['ema_100'] = ta.EMA(dataframe, timeperiod=100)
        dataframe['ema_200'] = ta.EMA(dataframe, timeperiod=200)
        
        # RSI
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        
        # Volume Analysis
        dataframe['volume_sma'] = dataframe['volume'].rolling(window=20).mean()
        dataframe['volume_ratio'] = dataframe['volume'] / (dataframe['volume_sma'] + 1e-10)
        dataframe['high_volume'] = dataframe['volume_ratio'] > self.volume_surge.value
        
        # Order Flow Approximation (using volume delta)
        dataframe['buy_volume'] = dataframe['volume'] * ((dataframe['close'] - dataframe['low']) / 
                                                         (dataframe['range'] + 1e-10))
        dataframe['sell_volume'] = dataframe['volume'] * ((dataframe['high'] - dataframe['close']) / 
                                                          (dataframe['range'] + 1e-10))
        dataframe['volume_delta'] = dataframe['buy_volume'] - dataframe['sell_volume']
        dataframe['cumulative_delta'] = dataframe['volume_delta'].rolling(window=self.cumulative_delta_period.value).sum()
        
        # Volume Profile (simplified - volume at price levels)
        dataframe['vwap'] = (dataframe['volume'] * dataframe['hl_avg']).rolling(window=20).sum() / \
                           (dataframe['volume'].rolling(window=20).sum() + 1e-10)
        
        # ===== SUPPORT & RESISTANCE DETECTION =====
        dataframe = self.detect_support_resistance(dataframe)
        
        # ===== ACCUMULATION & DISTRIBUTION DETECTION =====
        dataframe = self.detect_accumulation_distribution(dataframe)
        
        # ADX - Trend Strength
        dataframe['adx'] = ta.ADX(dataframe, timeperiod=14)
        dataframe['plus_di'] = ta.PLUS_DI(dataframe, timeperiod=14)
        dataframe['minus_di'] = ta.MINUS_DI(dataframe, timeperiod=14)
        
        # Bollinger Bands
        bollinger = qtpylib.bollinger_bands(dataframe['close'], window=20, stds=2)
        dataframe['bb_lower'] = bollinger['lower']
        dataframe['bb_middle'] = bollinger['mid']
        dataframe['bb_upper'] = bollinger['upper']
        
        # ATR
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
        
        # ===== HIGHER TIMEFRAME ANALYSIS =====
        
        # 1H Timeframe
        informative_1h = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1h')
        informative_1h = self.populate_htf_indicators(informative_1h, '1h')
        dataframe = merge_informative_pair(dataframe, informative_1h, self.timeframe, '1h', ffill=True)
        
        # 4H Timeframe
        informative_4h = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='4h')
        informative_4h = self.populate_htf_indicators(informative_4h, '4h')
        dataframe = merge_informative_pair(dataframe, informative_4h, self.timeframe, '4h', ffill=True)
        
        # Daily Timeframe
        informative_1d = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d')
        informative_1d = self.populate_htf_indicators(informative_1d, '1d')
        dataframe = merge_informative_pair(dataframe, informative_1d, self.timeframe, '1d', ffill=True)
        
        # ===== HTF TREND CONFLUENCE =====
        dataframe['htf_bullish'] = (
            (dataframe['trend_1h'] == 'bullish') &
            (dataframe['trend_4h'] == 'bullish') &
            (dataframe['trend_1d'] == 'bullish')
        )
        
        dataframe['htf_bearish'] = (
            (dataframe['trend_1h'] == 'bearish') &
            (dataframe['trend_4h'] == 'bearish') &
            (dataframe['trend_1d'] == 'bearish')
        )
        
        return dataframe
    
    def populate_htf_indicators(self, dataframe: DataFrame, timeframe: str) -> DataFrame:
        """
        Populate indicators for higher timeframes
        """
        # EMAs for trend
        dataframe['ema_fast'] = ta.EMA(dataframe, timeperiod=20)
        dataframe['ema_slow'] = ta.EMA(dataframe, timeperiod=50)
        dataframe['ema_trend'] = ta.EMA(dataframe, timeperiod=self.htf_trend_ema.value)
        
        # Trend determination
        dataframe['trend'] = 'neutral'
        dataframe.loc[
            (dataframe['close'] > dataframe['ema_trend']) &
            (dataframe['ema_fast'] > dataframe['ema_slow']) &
            (dataframe['ema_slow'].shift(1) < dataframe['ema_slow']),
            'trend'
        ] = 'bullish'
        
        dataframe.loc[
            (dataframe['close'] < dataframe['ema_trend']) &
            (dataframe['ema_fast'] < dataframe['ema_slow']) &
            (dataframe['ema_slow'].shift(1) > dataframe['ema_slow']),
            'trend'
        ] = 'bearish'
        
        # ADX for trend strength
        dataframe['adx'] = ta.ADX(dataframe, timeperiod=14)
        
        # Support and Resistance on HTF
        dataframe['htf_resistance'] = dataframe['high'].rolling(window=20).max()
        dataframe['htf_support'] = dataframe['low'].rolling(window=20).min()
        
        # Volume analysis
        dataframe['volume_sma'] = dataframe['volume'].rolling(window=20).mean()
        
        return dataframe
    
    def detect_support_resistance(self, dataframe: DataFrame) -> DataFrame:
        """
        Detect key support and resistance levels
        """
        lookback = self.sr_lookback.value
        threshold = self.sr_threshold.value
        
        # Rolling pivot points
        dataframe['pivot_high'] = dataframe['high'].rolling(window=lookback, center=True).max()
        dataframe['pivot_low'] = dataframe['low'].rolling(window=lookback, center=True).min()
        
        # Dynamic support/resistance
        dataframe['resistance_1'] = dataframe['high'].rolling(window=20).max()
        dataframe['resistance_2'] = dataframe['high'].rolling(window=50).max()
        dataframe['support_1'] = dataframe['low'].rolling(window=20).min()
        dataframe['support_2'] = dataframe['low'].rolling(window=50).min()
        
        # Near support/resistance
        dataframe['near_resistance'] = (
            ((dataframe['close'] >= dataframe['resistance_1'] * (1 - threshold)) &
             (dataframe['close'] <= dataframe['resistance_1'] * (1 + threshold))) |
            ((dataframe['close'] >= dataframe['resistance_2'] * (1 - threshold)) &
             (dataframe['close'] <= dataframe['resistance_2'] * (1 + threshold)))
        )
        
        dataframe['near_support'] = (
            ((dataframe['close'] >= dataframe['support_1'] * (1 - threshold)) &
             (dataframe['close'] <= dataframe['support_1'] * (1 + threshold))) |
            ((dataframe['close'] >= dataframe['support_2'] * (1 - threshold)) &
             (dataframe['close'] <= dataframe['support_2'] * (1 + threshold)))
        )
        
        # Order flow confirmation at support/resistance
        dataframe['support_confirmed'] = (
            dataframe['near_support'] &
            (dataframe['cumulative_delta'] > 0) &  # Positive order flow
            dataframe['long_lower_wick']  # Rejection wick
        )
        
        dataframe['resistance_confirmed'] = (
            dataframe['near_resistance'] &
            (dataframe['cumulative_delta'] < 0) &  # Negative order flow
            dataframe['long_upper_wick']  # Rejection wick
        )
        
        return dataframe
    
    def detect_accumulation_distribution(self, dataframe: DataFrame) -> DataFrame:
        """
        Detect accumulation and distribution zones (Smart Money Concepts)
        """
        period = self.accumulation_period.value
        
        # Accumulation/Distribution Line (A/D Line)
        dataframe['ad_line'] = ta.AD(dataframe)
        dataframe['ad_line_sma'] = dataframe['ad_line'].rolling(window=period).mean()
        
        # Price range compression (accumulation characteristic)
        dataframe['price_std'] = dataframe['close'].rolling(window=period).std()
        dataframe['price_std_ratio'] = dataframe['price_std'] / dataframe['price_std'].rolling(window=50).mean()
        
        # Volume characteristics
        dataframe['avg_volume'] = dataframe['volume'].rolling(window=period).mean()
        dataframe['volume_std'] = dataframe['volume'].rolling(window=period).std()
        
        # ACCUMULATION ZONE detection
        # - Sideways price action (low volatility)
        # - Higher volume than average
        # - Positive cumulative delta
        # - Price compression near support
        dataframe['accumulation_zone'] = (
            (dataframe['price_std_ratio'] < self.volatility_threshold.value) &  # Low volatility
            (dataframe['volume'] > dataframe['avg_volume'] * self.volume_acc_multiplier.value) &  # Higher volume
            (dataframe['cumulative_delta'] > 0) &  # Buying pressure
            (dataframe['close'] < dataframe['ema_slow']) &  # Below mid-term MA
            (dataframe['rsi'] < 50)  # Not overbought
        )
        
        # DISTRIBUTION ZONE detection
        # - Sideways price action at tops
        # - Higher volume
        # - Negative cumulative delta
        # - Price compression near resistance
        dataframe['distribution_zone'] = (
            (dataframe['price_std_ratio'] < self.volatility_threshold.value) &  # Low volatility
            (dataframe['volume'] > dataframe['avg_volume'] * self.volume_acc_multiplier.value) &  # Higher volume
            (dataframe['cumulative_delta'] < 0) &  # Selling pressure
            (dataframe['close'] > dataframe['ema_slow']) &  # Above mid-term MA
            (dataframe['rsi'] > 50)  # Not oversold
        )
        
        # Wyckoff Spring/Upthrust detection
        dataframe['spring'] = (  # Fake breakdown (bullish)
            (dataframe['low'] < dataframe['support_1']) &
            (dataframe['close'] > dataframe['support_1']) &
            dataframe['long_lower_wick'] &
            (dataframe['volume'] > dataframe['volume_sma'] * 1.5)
        )
        
        dataframe['upthrust'] = (  # Fake breakout (bearish)
            (dataframe['high'] > dataframe['resistance_1']) &
            (dataframe['close'] < dataframe['resistance_1']) &
            dataframe['long_upper_wick'] &
            (dataframe['volume'] > dataframe['volume_sma'] * 1.5)
        )
        
        return dataframe
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        LONG Entry with HTF trend confirmation and order flow
        """
        conditions_long = []
        
        # === LONG CONDITION 1: HTF Bullish + Accumulation Breakout ===
        conditions_long.append(
            dataframe['htf_bullish'] &  # All HTFs bullish
            (dataframe['accumulation_zone'].shift(1) == True) &  # Previous accumulation
            (dataframe['close'] > dataframe['ema_fast']) &  # Breakout above fast EMA
            (dataframe['cumulative_delta'] > self.delta_threshold.value) &  # Positive order flow
            (dataframe['volume_ratio'] > self.volume_confirmation.value) &  # Volume surge
            (dataframe['rsi'] > self.rsi_buy_min.value) & (dataframe['rsi'] < self.rsi_buy_max.value) &  # RSI in range
            (dataframe['adx'] > self.adx_trend_min.value)  # Trending
        )
        
        # === LONG CONDITION 2: Spring (Wyckoff) + Order Flow Confirmation ===
        conditions_long.append(
            dataframe['htf_bullish'] &
            (dataframe['spring'] == True) &  # Spring pattern
            (dataframe['cumulative_delta'] > dataframe['cumulative_delta'].shift(1)) &  # Increasing buying
            (dataframe['close'] > dataframe['open']) &  # Bullish candle
            (dataframe['adx'] > self.adx_weak_trend.value)
        )
        
        # === LONG CONDITION 3: Support Bounce + Order Flow ===
        conditions_long.append(
            (dataframe['trend_4h'] == 'bullish') &  # 4H trend bullish
            dataframe['support_confirmed'] &  # Support with order flow confirmation
            (dataframe['long_lower_wick'] == True) &  # Rejection wick
            (dataframe['close'] > dataframe['vwap']) &  # Above VWAP
            (dataframe['rsi'] < 50) &  # Not overbought
            (dataframe['volume_ratio'] > 1.3)
        )
        
        # === LONG CONDITION 4: Pullback Entry in Strong Trend ===
        conditions_long.append(
            dataframe['htf_bullish'] &
            (dataframe['close'] > dataframe['ema_200']) &  # Above 200 EMA
            (dataframe['close'] < dataframe['bb_middle']) &  # Pullback
            (dataframe['rsi'] < self.rsi_buy_min.value) &  # Oversold on pullback
            qtpylib.crossed_above(dataframe['close'], dataframe['ema_fast']) &  # Bounce
            (dataframe['cumulative_delta'] > 0) &  # Buying coming in
            (dataframe['adx'] > self.adx_strong_trend.value)  # Strong trend
        )
        
        if conditions_long:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions_long),
                'enter_long'
            ] = 1
        
        return dataframe
    
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        LONG Exit signals
        """
        conditions_exit_long = []
        
        # === EXIT 1: Distribution Zone + Order Flow Reversal ===
        conditions_exit_long.append(
            (dataframe['distribution_zone'] == True) &
            (dataframe['cumulative_delta'] < 0) &  # Selling pressure
            (dataframe['rsi'] > self.rsi_sell_min.value)
        )
        
        # === EXIT 2: Resistance Rejection ===
        conditions_exit_long.append(
            dataframe['resistance_confirmed'] &
            (dataframe['long_upper_wick'] == True) &
            (dataframe['volume_ratio'] > 1.5)
        )
        
        # === EXIT 3: HTF Trend Reversal ===
        conditions_exit_long.append(
            (dataframe['trend_4h'] == 'bearish') &
            (dataframe['close'] < dataframe['ema_fast']) &
            (dataframe['cumulative_delta'] < 0)
        )
        
        # === EXIT 4: Upthrust (False Breakout) ===
        conditions_exit_long.append(
            (dataframe['upthrust'] == True) &
            (dataframe['rsi'] > self.exit_rsi_high.value)
        )
        
        # === EXIT 5: Momentum Loss ===
        conditions_exit_long.append(
            (dataframe['adx'] < self.adx_weak_trend.value) &  # Weak trend
            (dataframe['rsi'] > self.exit_rsi_high.value) &
            (dataframe['volume_ratio'] < self.exit_volume_low.value)  # Low volume
        )
        
        if conditions_exit_long:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions_exit_long),
                'exit_long'
            ] = 1
        
        return dataframe
    
    def leverage(self, pair: str, current_time, current_rate: float,
                 proposed_leverage: float, max_leverage: float, entry_tag: str,
                 side: str, **kwargs) -> float:
        """
        Dynamic leverage based on market conditions
        """
        return self.leverage_num
    
    def custom_stoploss(self, pair: str, trade: 'Trade', current_time,
                       current_rate: float, current_profit: float, **kwargs) -> float:
        """
        Custom stoploss with ATR-based dynamic adjustment
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        
        # ATR-based stop
        atr = last_candle['atr']
        current_price = last_candle['close']
        atr_stop = (atr * 2) / current_price  # 2x ATR stop
        
        # Use the more conservative stop
        return min(self.stoploss, -atr_stop)