# source: https://raw.githubusercontent.com/tuncaykurt/agentlar-workspace/8b85fd67abff2d820558a67f472d6572da105af1/projeler/Freqtrade_Core/user_data/strategies/GridDirectionStrategy.py
"""
Github_tuncaykurt_agentlar_workspace__GridDirectionStrategy__20260605_001508 — Grid Bot Yön Tespiti Optimizasyonu

Grid botun "ne zaman LONG, ne zaman SHORT" kararını optimize eder.
Hyperopt ile en iyi yön tespit parametrelerini bulur.

BUY sinyali = "LONG grid aç"
SELL/EXIT sinyali = "SHORT grid aç veya çık"

Hyperopt:
    freqtrade hyperopt --strategy Github_tuncaykurt_agentlar_workspace__GridDirectionStrategy__20260605_001508 \
        --hyperopt-loss SharpeHyperOptLossDaily \
        --spaces buy sell --epochs 1000 -j 4 \
        --config user_data/config_hyperopt.json \
        --timerange 20251203-20260603

Backtest:
    freqtrade backtesting --strategy Github_tuncaykurt_agentlar_workspace__GridDirectionStrategy__20260605_001508 \
        --config user_data/config_hyperopt.json \
        --timerange 20251203-20260603
"""
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, BooleanParameter
from pandas import DataFrame
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy as np


class Github_tuncaykurt_agentlar_workspace__GridDirectionStrategy__20260605_001508(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = '5m'
    can_short = True

    # Grid bot simülasyonu: yön doğruysa kar, yanlışsa zarar
    minimal_roi = {"180": 0.003, "120": 0.005, "60": 0.01, "0": 0.025}
    stoploss = -0.03
    trailing_stop = True
    trailing_stop_positive = 0.005
    trailing_stop_positive_offset = 0.01
    trailing_only_offset_is_reached = True

    order_types = {
        "entry": "market", "exit": "market",
        "stoploss": "market", "stoploss_on_exchange": False,
    }

    # ═══════════════════════════════════════════════════════════════
    #  HYPEROPT PARAMETRELERI
    # ═══════════════════════════════════════════════════════════════

    # ADX: Trend gücü eşiği
    buy_adx_threshold = IntParameter(15, 35, default=25, space="buy", optimize=True)
    sell_adx_threshold = IntParameter(10, 30, default=20, space="sell", optimize=True)

    # EMA Trend periyodu
    ema_trend_period = IntParameter(100, 300, default=200, space="buy", optimize=True)

    # RSI filtreleri
    buy_rsi_lower = IntParameter(20, 45, default=30, space="buy", optimize=True)
    buy_rsi_upper = IntParameter(50, 75, default=68, space="buy", optimize=True)
    sell_rsi_upper = IntParameter(55, 85, default=70, space="sell", optimize=True)

    # Bollinger Bands
    bb_period = IntParameter(10, 40, default=20, space="buy", optimize=True)
    bb_std = DecimalParameter(1.0, 3.5, default=2.0, decimals=1, space="buy", optimize=True)

    # Supertrend
    st_atr_period = IntParameter(7, 21, default=10, space="buy", optimize=True)
    st_atr_mult = DecimalParameter(1.5, 5.0, default=3.0, decimals=1, space="buy", optimize=True)

    # MACD
    macd_fast = IntParameter(8, 18, default=12, space="buy", optimize=True)
    macd_slow = IntParameter(20, 35, default=26, space="buy", optimize=True)
    macd_signal = IntParameter(5, 15, default=9, space="buy", optimize=True)

    # Onay sistemi
    min_confirmations = IntParameter(2, 6, default=3, space="buy", optimize=True)

    # Volume filtresi
    volume_min_ratio = DecimalParameter(0.5, 3.0, default=1.2, decimals=1, space="buy", optimize=True)

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """Tüm indikatörleri hesapla."""

        # EMA'lar
        for p in [6, 14, 21, 50, 100, 150, 200, 250, 300]:
            dataframe[f'ema_{p}'] = ta.EMA(dataframe, timeperiod=p)

        # RSI
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)

        # ADX + DI
        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
        for period in [10, 14, 20, 25, 30, 40]:
            bb = qtpylib.bollinger_bands(dataframe['close'], window=period, stds=2.0)
            dataframe[f'bb_mid_{period}'] = bb['mid']
            dataframe[f'bb_upper_{period}'] = bb['upper']
            dataframe[f'bb_lower_{period}'] = bb['lower']
            dataframe[f'bb_width_{period}'] = (bb['upper'] - bb['lower']) / bb['mid']

        # MACD
        macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
        dataframe['macd'] = macd['macd']
        dataframe['macd_signal'] = macd['macdsignal']
        dataframe['macd_hist'] = macd['macdhist']

        # Custom MACD (hyperopt deneyecek)
        for fast, slow, sig in [(8, 21, 9), (10, 26, 9), (17, 21, 15), (8, 35, 5), (18, 30, 15)]:
            m = ta.MACD(dataframe, fastperiod=fast, slowperiod=slow, signalperiod=sig)
            dataframe[f'macd_hist_{fast}_{slow}_{sig}'] = m['macdhist']

        # ATR
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)

        # Supertrend (birkaç varyasyon)
        for period in [7, 10, 14, 21]:
            for mult in [1.5, 2.0, 3.0, 4.0, 5.0]:
                st_dir = self._supertrend(dataframe, period, mult)
                dataframe[f'st_dir_{period}_{str(mult).replace(".", "")}'] = st_dir

        # Volume
        dataframe['volume_sma'] = ta.SMA(dataframe['volume'], timeperiod=20)
        dataframe['volume_ratio'] = np.where(
            dataframe['volume_sma'] > 0,
            dataframe['volume'] / dataframe['volume_sma'],
            1.0
        )

        return dataframe

    def _supertrend(self, df: DataFrame, period: int, mult: float) -> list:
        """Supertrend yön hesapla: 1=LONG, -1=SHORT."""
        hl2 = (df['high'] + df['low']) / 2
        atr = ta.ATR(df, timeperiod=period)
        upper_band = hl2 + mult * atr
        lower_band = hl2 - mult * atr

        direction = [1] * len(df)
        for i in range(1, len(df)):
            if df['close'].iloc[i] > upper_band.iloc[i - 1]:
                direction[i] = 1
            elif df['close'].iloc[i] < lower_band.iloc[i - 1]:
                direction[i] = -1
            else:
                direction[i] = direction[i - 1]
        return direction

    def _get_ema_col(self, period: int) -> str:
        """En yakın EMA sütununu bul."""
        available = [6, 14, 21, 50, 100, 150, 200, 250, 300]
        closest = min(available, key=lambda x: abs(x - period))
        return f'ema_{closest}'

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """LONG giriş sinyali = Grid LONG yönde açılmalı."""

        ema_col = self._get_ema_col(self.ema_trend_period.value)
        bb_mid_col = f'bb_mid_{self.bb_period.value}' if f'bb_mid_{self.bb_period.value}' in dataframe.columns else 'bb_mid_20'
        st_col = f'st_dir_{self.st_atr_period.value}_{str(self.st_atr_mult.value).replace(".", "")}'
        if st_col not in dataframe.columns:
            st_col = 'st_dir_10_30'

        # MACD hist — en yakın kombinasyonu bul
        macd_col = f'macd_hist_{self.macd_fast.value}_{self.macd_slow.value}_{self.macd_signal.value}'
        if macd_col not in dataframe.columns:
            macd_col = 'macd_hist'

        # ═══ 7 ONAY OYLAMA SİSTEMİ ═══
        votes = np.zeros(len(dataframe))

        # 1. ADX trend gücü yeterli
        votes += (dataframe['adx'] >= self.buy_adx_threshold.value).astype(int)

        # 2. Fiyat > EMA trend (yükseliş trendi)
        votes += (dataframe['close'] > dataframe[ema_col]).astype(int)

        # 3. EMA6 > EMA14 (kısa vadeli momentum yukarı)
        votes += (dataframe['ema_6'] > dataframe['ema_14']).astype(int)

        # 4. RSI uygun aralıkta
        votes += ((dataframe['rsi'] >= self.buy_rsi_lower.value) &
                  (dataframe['rsi'] <= self.buy_rsi_upper.value)).astype(int)

        # 5. MACD histogram pozitif (momentum yukarı)
        votes += (dataframe[macd_col] > 0).astype(int)

        # 6. Supertrend LONG
        votes += (dataframe[st_col] == 1).astype(int)

        # 7. Fiyat BB mid üstünde
        votes += (dataframe['close'] > dataframe[bb_mid_col]).astype(int)

        # ═══ LONG GİRİŞ ═══
        dataframe.loc[
            (votes >= self.min_confirmations.value) &
            (dataframe['volume_ratio'] >= self.volume_min_ratio.value) &
            (dataframe['volume'] > 0),
            'enter_long'] = 1

        # ═══ SHORT GİRİŞ (LONG'un tersi) ═══
        short_votes = np.zeros(len(dataframe))
        short_votes += (dataframe['adx'] >= self.buy_adx_threshold.value).astype(int)
        short_votes += (dataframe['close'] < dataframe[ema_col]).astype(int)
        short_votes += (dataframe['ema_6'] < dataframe['ema_14']).astype(int)
        short_votes += ((dataframe['rsi'] >= self.buy_rsi_lower.value) &
                        (dataframe['rsi'] <= self.buy_rsi_upper.value)).astype(int)
        short_votes += (dataframe[macd_col] < 0).astype(int)
        short_votes += (dataframe[st_col] == -1).astype(int)
        short_votes += (dataframe['close'] < dataframe[bb_mid_col]).astype(int)

        dataframe.loc[
            (short_votes >= self.min_confirmations.value) &
            (dataframe['volume_ratio'] >= self.volume_min_ratio.value) &
            (dataframe['volume'] > 0),
            'enter_short'] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """Çıkış sinyali = yön terse döndü."""

        ema_col = self._get_ema_col(self.ema_trend_period.value)
        st_col = f'st_dir_{self.st_atr_period.value}_{str(self.st_atr_mult.value).replace(".", "")}'
        if st_col not in dataframe.columns:
            st_col = 'st_dir_10_30'

        # LONG'dan çıkış: trend tersine döndü
        exit_votes = np.zeros(len(dataframe))
        exit_votes += (dataframe['adx'] < self.sell_adx_threshold.value).astype(int)
        exit_votes += (dataframe['close'] < dataframe[ema_col]).astype(int)
        exit_votes += (dataframe['rsi'] >= self.sell_rsi_upper.value).astype(int)
        exit_votes += (dataframe[st_col] == -1).astype(int)

        dataframe.loc[
            (exit_votes >= 2) &
            (dataframe['volume'] > 0),
            'exit_long'] = 1

        # SHORT'tan çıkış: yükseliş başladı
        short_exit_votes = np.zeros(len(dataframe))
        short_exit_votes += (dataframe['adx'] < self.sell_adx_threshold.value).astype(int)
        short_exit_votes += (dataframe['close'] > dataframe[ema_col]).astype(int)
        short_exit_votes += (dataframe['rsi'] <= (100 - self.sell_rsi_upper.value)).astype(int)
        short_exit_votes += (dataframe[st_col] == 1).astype(int)

        dataframe.loc[
            (short_exit_votes >= 2) &
            (dataframe['volume'] > 0),
            'exit_short'] = 1

        return dataframe
