# source: https://raw.githubusercontent.com/labanux/freq/762aef11fa476888f4a334570763817034f48b0c/user_data/strategies/sekka-rvol.py
# ================================================================
# Github_labanux_freq__sekka_rvol__20260408_083411 v1.0 – RVOL + VWAP Breakout Strategy
# ---------------------------------------------------------------
# Entry when price is above VWAP, breaks above previous candle,
# and relative volume (RVOL) exceeds threshold.
# ================================================================

from freqtrade.strategy import IStrategy, CategoricalParameter
from pandas import DataFrame
import pandas as pd
import numpy as np
import logging
from datetime import datetime
from typing import Optional


class Github_labanux_freq__sekka_rvol__20260408_083411(IStrategy):
    timeframe = "1h"
    informative_timeframes = []
    process_only_new_candles = False
    can_short = False

    use_exit_signal = True
    exit_profit_only = False
    ignore_buying_expiry = True
    position_adjustment_enable = True

    startup_candle_count = 20

    DCA_THRESHOLD = CategoricalParameter([0.03, 0.05, 0.06], default=0.03, space="buy", optimize=False)

    DCA_STEP = 3
    RVOL_THRESHOLD = CategoricalParameter([2.0, 2.5, 3.0, 3.5, 4.0], default=3.0, space="buy", optimize=True)
    RVOL_WINDOW = 20
    VWAP_WINDOW = 14
    LEVERAGE = 1

    minimal_roi = {}
    stoploss = -0.1

    logger = logging.getLogger(__name__)
    _last_dca_stage = None

    ## -- TRAILING SETUP --
    trailing_stop = True
    trailing_stop_positive_offset = 0.01
    trailing_stop_positive = 0.005
    trailing_only_offset_is_reached = True

    # ------------------ Informative Pairs ------------------
    def informative_pairs(self):
        return []

    # ------------------ Plot Config ------------------
    plot_config = {
        "main_plot": {
            "vwap": {"color": "orange"},
        },
        "subplots": {
            "RVOL": {
                "rvol": {"color": "blue"},
            },
        },
    }

    # ------------------ Indicators ------------------
    def hlc3(self, df: DataFrame) -> pd.Series:
        return (df["high"] + df["low"] + df["close"]) / 3.0

    def compute_vwap(self, df: DataFrame, window: int) -> pd.Series:
        hlc3 = self.hlc3(df)
        pv = hlc3 * df["volume"]
        pv_sum = pv.rolling(window, min_periods=1).sum()
        vol_sum = df["volume"].rolling(window, min_periods=1).sum()
        vwap = (pv_sum / vol_sum.replace(0, np.nan)).ffill().fillna(df["close"])
        return vwap

    def populate_indicators(self, df: DataFrame, metadata: dict) -> DataFrame:
        # VWAP
        df["vwap"] = self.compute_vwap(df, self.VWAP_WINDOW)

        # RVOL: current volume / average volume over window
        avg_vol = df["volume"].rolling(self.RVOL_WINDOW, min_periods=1).mean()
        df["rvol"] = (df["volume"] / avg_vol.replace(0, np.nan)).fillna(0)

        # Previous candle info for breakout check
        df["prev_open"] = df["open"].shift(1)
        df["prev_close"] = df["close"].shift(1)
        df["prev_is_red"] = df["prev_close"] < df["prev_open"]

        return df

    def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        df["enter_long"] = 0

        # Price above VWAP
        above_vwap = df["close"] > df["vwap"]

        # Breakout above previous candle:
        # - If prev candle red: close > prev open
        # - If prev candle green: close > prev close
        breakout_level = df["prev_open"].where(df["prev_is_red"], df["prev_close"])
        above_prev = df["close"] > breakout_level

        # RVOL above threshold
        high_rvol = df["rvol"] > self.RVOL_THRESHOLD.value

        df.loc[above_vwap & above_prev & high_rvol, "enter_long"] = 1
        return df

    def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
        df["exit_long"] = 0
        return df

    # ------------------ Custom Stake ------------------
    def custom_stake_amount(self, pair: str, current_time: pd.Timestamp, current_rate: float, **kwargs) -> float:
        balance = self.wallets.get_total_stake_amount()
        free = self.wallets.get_available_stake_amount()
        used = max(balance - free, 0)
        remaining = max(balance - used, 0)

        trade = kwargs.get("trade", None)
        stage = trade.nr_of_successful_entries if trade else 0

        total_steps = self.DCA_STEP

        if stage == 0:
            stake = balance / total_steps
        else:
            remaining_steps = total_steps - stage
            if remaining_steps > 0:
                stake = remaining / remaining_steps
            else:
                stake = 0

        return float(max(min(stake, remaining), 0.0))

    # ------------------ DCA Logic ------------------
    def adjust_trade_position(self, trade, current_time, current_rate, current_profit, **kwargs):
        if self._last_dca_stage is None:
            self._last_dca_stage = {}

        trade_id = getattr(trade, "id", None) or f"{trade.pair}_{getattr(trade, 'open_date', None)}"
        current_stage = trade.nr_of_successful_entries
        recorded = self._last_dca_stage.get(trade_id)

        if recorded is not None and recorded == current_stage:
            return 0

        if current_stage >= (self.DCA_STEP + 1):
            return 0

        avg_rate = trade.open_rate
        drop_ratio = (current_rate / avg_rate) - 1.0
        next_dca_trigger = -self.DCA_THRESHOLD.value

        free_balance = self.wallets.get_available_stake_amount()
        est_stake = self.custom_stake_amount(trade.pair, current_time, current_rate, trade=trade)

        if drop_ratio <= next_dca_trigger and free_balance >= est_stake:
            next_stage = current_stage + 1
            tag = f"DCA_{next_stage}"
            self._last_dca_stage[trade_id] = current_stage
            trade.enter_tag = tag
            return est_stake

        return 0

    # ------------------ Exit Logic ------------------
    def custom_exit(self, pair: str, trade, current_time, current_rate, **kwargs):
        return None

    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                 proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
                 **kwargs) -> float:
        return self.LEVERAGE
