# source: https://raw.githubusercontent.com/Roshan7869/algotrading/db07ceb71ac165a38fe200a86ae48586ed040f60/user_data/strategies/_archived/hedge_champion_p3f.py
# directory_url: https://github.com/Roshan7869/algotrading/blob/main/user_data/strategies/_archived/
# User: Roshan7869
# Repository: algotrading
# --------------------"""
HEDGE CHAMPION — P3F/P3E Key Level Boost + Tight Trail Hedge
=============================================================
Built on the project's champion strategies (VectorStrategy P3F/P3E):

  - P3E_KEY_LEVEL_BOOST: +934%/84.1%WR/0.93%DD (6yr futures)
  - P3F_KEY_LEVEL_TIGHT_TRAIL: +901%/84.8%WR/0.96%DD
  Source: batch_results_futures_6yr.json

Core Structure:
  - Capital split: 50% long, 50% short on top P2★ pairs
  - Leverage: 10x
  - Stop Loss: -10%
  - Profit Target: +30%
  - Uses P3F's key level boost + tighter trail + squeeze breakout signals

Champion Enhancements Applied:
  a) Key Level Boost: +1 confluence when dist_to_support < 0.5 (longs)
     or dist_to_resistance < 0.5 (shorts) — ChromaDB score 0.612
  b) Tighter trail: trailing_stop_positive_offset 0.03 (was 0.04)
     "Risk to Zero ASAP" concept from ChromaDB
  c) Confluence scoring with minimum 2/5 signals

Risk Mgmt (ChromaDB): P3F proven structure
  - Five signal categories: squeeze_breakout, mean_reversion,
    ema_alignment, expansion, key_level
  - +1 key_level_boost when very close to pivots
  - Minimum 2/5 confluence for entry
  - Tighter trail at +3% activates breakeven-style protection
"""

from datetime import datetime
from typing import Optional
import numpy as np
import pandas as pd
from pandas import DataFrame

from freqtrade.strategy import (IStrategy, Trade, Order,
                                 DecimalParameter, IntParameter, informative)
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.strategy import merge_informative_pair


class Github_Roshan7869_algotrading__hedge_champion_p3f__20260817_183107(IStrategy):
    INTERFACE_VERSION = 3

    timeframe = "1h"
    can_short: bool = True

    # ── Stop & Target ──
    stoploss = -0.10

    # 30% profit target with P3F tighter trail
    minimal_roi = {"0": 0.30}

    # Tighter trail (P3F upgrade: 0.03 vs 0.04 base)
    trailing_stop = True
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.03
    trailing_only_offset_is_reached = True

    process_only_new_candles = True
    startup_candle_count = 200

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

    # ── P3F Hyperopt Parameters ──
    bb_squeeze_threshold = DecimalParameter(0.02, 0.10, default=0.06, decimals=3, space="buy")
    bb_expansion_threshold = DecimalParameter(0.85, 1.20, default=1.00, decimals=2, space="buy")
    rsi_oversold = IntParameter(25, 45, default=40, space="buy")
    rsi_overbought = IntParameter(55, 75, default=60, space="sell")
    volume_factor = DecimalParameter(1.0, 2.5, default=1.5, decimals=1, space="buy")
    ema_fast = IntParameter(8, 21, default=9, space="buy")
    ema_medium = IntParameter(20, 50, default=21, space="buy")
    bb_pctb_low = DecimalParameter(0.20, 0.50, default=0.40, decimals=2, space="buy")
    bb_pctb_high = DecimalParameter(0.50, 0.80, default=0.60, decimals=2, space="sell")
    min_confluence = IntParameter(1, 3, default=2, space="buy")
    key_level_boost_dist = DecimalParameter(0.30, 0.70, default=0.50, decimals=2, space="buy")

    # ── Leverage ──
    def leverage(self, pair, current_time, current_rate,
                 proposed_leverage, max_leverage, entry_tag, side, **kwargs) -> float:
        return min(10.0, max_leverage)

    # ── Indicators (P3F champion spec) ──
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # 1. Bollinger Bands (20, 2)
        bollinger = qtpylib.bollinger_bands(
            qtpylib.typical_price(dataframe), window=20, stds=2
        )
        dataframe["bb_lowerband"] = bollinger["lower"]
        dataframe["bb_middleband"] = bollinger["mid"]
        dataframe["bb_upperband"] = bollinger["upper"]
        dataframe["bb_pctb"] = (
            (dataframe["close"] - dataframe["bb_lowerband"])
            / (dataframe["bb_upperband"] - dataframe["bb_lowerband"])
        ).replace([np.inf, -np.inf], 0.5).fillna(0.5)
        dataframe["bb_width"] = (
            (dataframe["bb_upperband"] - dataframe["bb_lowerband"])
            / dataframe["bb_middleband"]
        ).replace([np.inf, -np.inf], 0).fillna(0)

        # 2. 3SD Bollinger Bands
        bollinger_3sd = qtpylib.bollinger_bands(
            qtpylib.typical_price(dataframe), window=20, stds=3
        )
        dataframe["bb3_upper"] = bollinger_3sd["upper"]
        dataframe["bb3_lower"] = bollinger_3sd["lower"]

        # 3. EMAs
        dataframe["ema_fast"] = ta.EMA(dataframe, timeperiod=self.ema_fast.value)
        dataframe["ema_medium"] = ta.EMA(dataframe, timeperiod=self.ema_medium.value)
        dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200)

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

        # 5. Volume
        dataframe["volume_mean"] = ta.SMA(dataframe["volume"], timeperiod=20)
        dataframe["volume_ratio"] = (
            dataframe["volume"] / dataframe["volume_mean"]
        ).replace([np.inf, -np.inf], 1).fillna(1)

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

        # 7. VWAP
        typical_price = (dataframe["high"] + dataframe["low"] + dataframe["close"]) / 3
        dataframe["vwap"] = (
            (typical_price * dataframe["volume"]).rolling(20).sum()
            / dataframe["volume"].rolling(20).sum()
        ).bfill()

        # 8. Key Level Proximity
        dataframe["pivot_high"] = dataframe["high"].rolling(5, center=True).max()
        dataframe["pivot_low"] = dataframe["low"].rolling(5, center=True).min()
        dataframe["dist_to_resistance"] = (
            (dataframe["pivot_high"] - dataframe["close"]) / dataframe["atr"]
        ).fillna(5)
        dataframe["dist_to_support"] = (
            (dataframe["close"] - dataframe["pivot_low"]) / dataframe["atr"]
        ).fillna(5)

        # 9. Top gainer momentum (for hedging on gainers)
        dataframe["roc_1h"] = dataframe["close"].pct_change(1) * 100
        dataframe["roc_4h"] = dataframe["close"].pct_change(4) * 100
        dataframe["roc_24h"] = dataframe["close"].pct_change(24) * 100

        return dataframe

    # ── Entry ──
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # ═══ LONG: P3F signals + top gainer momentum ═══
        squeeze_breakout_long = (
            (dataframe["bb_width"] < self.bb_squeeze_threshold.value) &
            (dataframe["bb_width"].shift(1) < dataframe["bb_width"]) &
            (dataframe["close"] > dataframe["bb_middleband"]) &
            (dataframe["volume_ratio"] > self.volume_factor.value)
        )

        mean_reversion_long = (
            (dataframe["bb_pctb"] < self.bb_pctb_low.value) &
            (dataframe["close"] > dataframe["bb3_lower"]) &
            (dataframe["rsi"] < self.rsi_oversold.value) &
            (dataframe["close"] > dataframe["vwap"])
        )

        ema_alignment_long = (
            (dataframe["ema_fast"] > dataframe["ema_medium"]) &
            (dataframe["close"] > dataframe["ema_fast"]) &
            (dataframe["ema_medium"] > dataframe["ema_200"]) &
            (dataframe["rsi"] > 40) &
            (dataframe["rsi"] < 65)
        )

        expansion_long = (
            (dataframe["close"] > dataframe["bb3_upper"]) &
            (dataframe["close"].shift(1) <= dataframe["bb3_upper"].shift(1)) &
            (dataframe["volume_ratio"] > self.volume_factor.value) &
            (dataframe["rsi"] > 50)
        )

        key_level_long = (
            (dataframe["dist_to_support"] < 1.0) &
            (dataframe["close"] > dataframe["open"]) &
            (dataframe["volume_ratio"] > 1.2) &
            (dataframe["rsi"] > 35) &
            (dataframe["rsi"] < 65)
        )

        # P3F Key Level Boost: +1 when very close to support
        key_level_boost_long = (
            dataframe["dist_to_support"] < self.key_level_boost_dist.value
        ).astype(int)

        long_signals = [
            squeeze_breakout_long.astype(int),
            mean_reversion_long.astype(int),
            ema_alignment_long.astype(int),
            expansion_long.astype(int),
            key_level_long.astype(int),
        ]
        long_score = sum(long_signals) + key_level_boost_long

        # Additional top-gainer filter for hedge setup
        top_gainer_long = dataframe["roc_4h"] > 2.0

        dataframe.loc[
            (long_score >= self.min_confluence.value) &
            (top_gainer_long) &
            (dataframe["volume"] > 0),
            ["enter_long", "enter_tag"]
        ] = (1, "champion_hedge_long")

        # ═══ SHORT: overextended gainer hedge ═══
        squeeze_breakout_short = (
            (dataframe["bb_width"] < self.bb_squeeze_threshold.value) &
            (dataframe["bb_width"].shift(1) < dataframe["bb_width"]) &
            (dataframe["close"] < dataframe["bb_middleband"]) &
            (dataframe["volume_ratio"] > self.volume_factor.value)
        )

        mean_reversion_short = (
            (dataframe["bb_pctb"] > self.bb_pctb_high.value) &
            (dataframe["close"] < dataframe["bb3_upper"]) &
            (dataframe["rsi"] > self.rsi_overbought.value) &
            (dataframe["close"] < dataframe["vwap"])
        )

        ema_alignment_short = (
            (dataframe["ema_fast"] < dataframe["ema_medium"]) &
            (dataframe["close"] < dataframe["ema_fast"]) &
            (dataframe["ema_medium"] < dataframe["ema_200"]) &
            (dataframe["rsi"] < 60) &
            (dataframe["rsi"] > 35)
        )

        expansion_short = (
            (dataframe["close"] < dataframe["bb3_lower"]) &
            (dataframe["close"].shift(1) >= dataframe["bb3_lower"].shift(1)) &
            (dataframe["volume_ratio"] > self.volume_factor.value) &
            (dataframe["rsi"] < 50)
        )

        key_level_short = (
            (dataframe["dist_to_resistance"] < 1.0) &
            (dataframe["close"] < dataframe["open"]) &
            (dataframe["volume_ratio"] > 1.2) &
            (dataframe["rsi"] < 65) &
            (dataframe["rsi"] > 35)
        )

        key_level_boost_short = (
            dataframe["dist_to_resistance"] < self.key_level_boost_dist.value
        ).astype(int)

        short_signals = [
            squeeze_breakout_short.astype(int),
            mean_reversion_short.astype(int),
            ema_alignment_short.astype(int),
            expansion_short.astype(int),
            key_level_short.astype(int),
        ]
        short_score = sum(short_signals) + key_level_boost_short

        # Overextended top gainer filter for short hedge
        overextended_gainer = (
            (dataframe["roc_24h"] > 10.0) &
            (dataframe["roc_1h"] < 0)
        )

        dataframe.loc[
            (short_score >= self.min_confluence.value) &
            (overextended_gainer) &
            (dataframe["volume"] > 0),
            ["enter_short", "enter_tag"]
        ] = (1, "champion_hedge_short")

        return dataframe

    # ── Exit ──
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                (dataframe["bb_pctb"] > self.bb_pctb_high.value) |
                ((dataframe["rsi"] > self.rsi_overbought.value) &
                 (dataframe["close"] < dataframe["ema_fast"])) |
                (dataframe["bb_width"] > dataframe["bb_width"].rolling(10).mean() * 2.5)
            ) & (dataframe["volume"] > 0),
            ["exit_long", "exit_tag"]
        ] = (1, "champion_exit_long")

        dataframe.loc[
            (
                (dataframe["bb_pctb"] < self.bb_pctb_low.value) |
                ((dataframe["rsi"] < self.rsi_oversold.value) &
                 (dataframe["close"] > dataframe["ema_fast"])) |
                (dataframe["bb_width"] > dataframe["bb_width"].rolling(10).mean() * 2.5)
            ) & (dataframe["volume"] > 0),
            ["exit_short", "exit_tag"]
        ] = (1, "champion_exit_short")

        return dataframe

    # ── Custom Exit: beacon targets ──
    def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
                    current_rate: float, current_profit: float, **kwargs) -> Optional[str]:
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if len(dataframe) < 1:
            return None

        last_candle = dataframe.iloc[-1]
        bb_pctb = last_candle.get("bb_pctb", 0.5)

        if trade.is_short:
            if bb_pctb < 0.15:
                return "beacon_target_short"
        else:
            if bb_pctb > 0.85:
                return "beacon_target_long"

        return None
