# source: https://raw.githubusercontent.com/sharkmeatx712-dev/bot-stuff/866315617afaabf435127573bef9a043560ebca6/strategies/sharkmeat_ote.py
import logging
from datetime import datetime
from typing import Dict, List, Optional, Tuple
import talib.abstract as ta
from freqtrade.strategy import IStrategy, merge_informative_pair
from freqtrade.persistence import Trade
from pandas import DataFrame
import numpy as np

logger = logging.getLogger(__name__)

class Github_sharkmeatx712_dev_bot_stuff__sharkmeat_ote__20260207_023043(IStrategy):
    INTERFACE_VERSION = 3
    
    # --- [ TIMEFRAMES ] ---
    timeframe = '5m'
    inf_1h = '1h'
    startup_candle_count: int = 200
    process_only_new_candles = True

    # --- [ RISK MANAGEMENT ] ---
    can_short = True
    stoploss = -0.10 
    minimal_roi = {"0": 100.0} 
    use_custom_stoploss = True

    # --- [ GLOBAL MASTER FILTER ] ---
    manual_btc_high = 71700.0
    manual_btc_low = 69680.0

    # --- [ STRATEGY PARAMETERS ] ---
    ote_zone_low = 0.58
    ote_zone_high = 0.79

    # State tracking
    last_log_time: Dict[str, datetime] = {}

    def informative_pairs(self) -> List[Tuple[str, str]]:
        return [("BTC/USDT:USDT", self.inf_1h)]

    def leverage(self, *args, **kwargs) -> float:
        return 5.0

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Standard Triggers
        dataframe['engulfing'] = ta.CDLENGULFING(dataframe)
        dataframe['fvg_bullish'] = (dataframe['low'] > dataframe['high'].shift(2)) & \
                                   (dataframe['close'].shift(1) > dataframe['open'].shift(1))
        dataframe['fvg_bearish'] = (dataframe['high'] < dataframe['low'].shift(2)) & \
                                   (dataframe['close'].shift(1) < dataframe['open'].shift(1))

        # Range Analysis (20-candle structure)
        dataframe['range_high'] = dataframe['high'].rolling(window=20).max().shift(1)
        dataframe['range_low'] = dataframe['low'].rolling(window=20).min().shift(1)
        dataframe['range_width'] = dataframe['range_high'] - dataframe['range_low']

        # Wyckoff "W" (Spring) Logic
        # Type 1: Deep Shakeout (Dipped > 2% of range width below floor)
        dataframe['spring_type1'] = (dataframe['low'] < (dataframe['range_low'] - (dataframe['range_width'] * 0.02))) & \
                                     (dataframe['close'] > dataframe['range_low'])
        
        # Type 2: Secondary Test (Reclaim + Higher Low pullback)
        dataframe['spring_type2'] = (dataframe['low'] > dataframe['low'].shift(1)) & \
                                     (dataframe['low'].shift(1) < dataframe['range_low']) & \
                                     (dataframe['close'] > dataframe['range_low'])

        # Type 3: Minor Spring (Shallow peek < 1% range width)
        dataframe['spring_type3'] = (dataframe['low'] < dataframe['range_low']) & \
                                     (dataframe['low'] > (dataframe['range_low'] - (dataframe['range_width'] * 0.01))) & \
                                     (dataframe['close'] > dataframe['range_low'])

        # Wyckoff "M" (Upthrust) Logic
        # Type 1: Violent Upthrust
        dataframe['upthrust_type1'] = (dataframe['high'] > (dataframe['range_high'] + (dataframe['range_width'] * 0.02))) & \
                                       (dataframe['close'] < dataframe['range_high'])
        
        # Type 2: Secondary Test (Lower High after sweep)
        dataframe['upthrust_type2'] = (dataframe['high'] < dataframe['high'].shift(1)) & \
                                       (dataframe['high'].shift(1) > dataframe['range_high']) & \
                                       (dataframe['close'] < dataframe['range_high'])

        # Type 3: Minor Upthrust
        dataframe['upthrust_type3'] = (dataframe['high'] > dataframe['range_high']) & \
                                       (dataframe['high'] < (dataframe['range_high'] + (dataframe['range_width'] * 0.01))) & \
                                       (dataframe['close'] < dataframe['range_high'])

        # BTC Master Filter
        btc_inf = self.dp.get_pair_dataframe(pair="BTC/USDT:USDT", timeframe=self.inf_1h)
        if btc_inf is not None and not btc_inf.empty:
            btc_inf['btc_range'] = self.manual_btc_high - self.manual_btc_low
            btc_inf['btc_in_long_zone'] = (btc_inf['close'] >= (self.manual_btc_high - (btc_inf['btc_range'] * self.ote_zone_high))) & \
                                          (btc_inf['close'] <= (self.manual_btc_high - (btc_inf['btc_range'] * self.ote_zone_low)))
            btc_inf['btc_in_short_zone'] = (btc_inf['close'] <= (self.manual_btc_low + (btc_inf['btc_range'] * self.ote_zone_high))) & \
                                           (btc_inf['close'] >= (self.manual_btc_low + (btc_inf['btc_range'] * self.ote_zone_low)))
            dataframe = merge_informative_pair(dataframe, btc_inf, self.timeframe, self.inf_1h, ffill=True)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        btc_long = dataframe[f'btc_in_long_zone_{self.inf_1h}']
        btc_short = dataframe[f'btc_in_short_zone_{self.inf_1h}']

        # --- [ LONG TAGGING & ENTRIES ] ---
        dataframe.loc[btc_long & dataframe['spring_type1'], 'enter_tag'] = 'W_Type1_Shakeout'
        dataframe.loc[btc_long & dataframe['spring_type2'], 'enter_tag'] = 'W_Type2_Test'
        dataframe.loc[btc_long & dataframe['spring_type3'], 'enter_tag'] = 'W_Type3_Minor'
        dataframe.loc[btc_long & (dataframe['fvg_bullish']), 'enter_tag'] = 'FVG_Long'
        dataframe.loc[btc_long & (dataframe['engulfing'] == 100), 'enter_tag'] = 'Engulfing_Long'

        dataframe.loc[btc_long & (
            dataframe['spring_type1'] | dataframe['spring_type2'] | dataframe['spring_type3'] |
            dataframe['fvg_bullish'] | (dataframe['engulfing'] == 100)
        ), 'enter_long'] = 1

        # --- [ SHORT TAGGING & ENTRIES ] ---
        dataframe.loc[btc_short & dataframe['upthrust_type1'], 'enter_tag'] = 'M_Type1_Shakeout'
        dataframe.loc[btc_short & dataframe['upthrust_type2'], 'enter_tag'] = 'M_Type2_Test'
        dataframe.loc[btc_short & dataframe['upthrust_type3'], 'enter_tag'] = 'M_Type3_Minor'
        dataframe.loc[btc_short & (dataframe['fvg_bearish']), 'enter_tag'] = 'FVG_Short'
        dataframe.loc[btc_short & (dataframe['engulfing'] == -100), 'enter_tag'] = 'Engulfing_Short'

        dataframe.loc[btc_short & (
            dataframe['upthrust_type1'] | dataframe['upthrust_type2'] | dataframe['upthrust_type3'] |
            dataframe['fvg_bearish'] | (dataframe['engulfing'] == -100)
        ), 'enter_short'] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[:, 'exit_long'] = 0
        dataframe.loc[:, 'exit_short'] = 0
        return dataframe

    def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
                        current_rate: float, current_profit: float, **kwargs) -> float:
        if current_profit >= 0.09:
            trade.set_custom_data('exit_tag', 'Trailing_Sniper')
            return -0.01
        if current_profit >= 0.06:
            trade.set_custom_data('exit_tag', 'Profit_Lock_2.8')
            return (current_rate - (trade.open_rate * 1.028)) / current_rate
        
        trade.set_custom_data('exit_tag', 'Initial_Stop')
        return self.stoploss