# source: https://raw.githubusercontent.com/jcd1991/market-behavior-lab/16a04bd8b1004e90b9b17b556429a2eb730d96da/user_data/strategies/VolumeProfileOHLCVProxy.py
# directory_url: https://github.com/jcd1991/market-behavior-lab/blob/main/user_data/strategies/
# User: jcd1991
# Repository: market-behavior-lab
# --------------------"""Previous-day volume-profile proxy using native 5-minute OHLCV only."""

from __future__ import annotations

import numpy as np
import pandas as pd
from freqtrade.strategy import DecimalParameter, IStrategy
from pandas import DataFrame


class Github_jcd1991_market_behavior_lab__VolumeProfileOHLCVProxy__20260921_130836(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = "5m"
    can_short = True
    process_only_new_candles = True
    startup_candle_count = 600
    stoploss = -0.04
    minimal_roi = {"0": 100.0}

    # This is a normalized candle-volume proxy, not literal trades-per-second.
    buy_vp_tape_speed = DecimalParameter(0.0, 0.0200, default=0.0010, decimals=4, space="buy", optimize=True)
    buy_vp_value_width = DecimalParameter(0.25, 1.50, default=0.75, decimals=2, space="buy", optimize=True)

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        date = pd.to_datetime(dataframe["date"], utc=True)
        typical = (dataframe["high"] + dataframe["low"] + dataframe["close"]) / 3.0
        volume = pd.to_numeric(dataframe["volume"], errors="coerce").fillna(0.0)
        day = date.dt.floor("D")
        weighted = typical * volume
        grouped = pd.DataFrame({"day": day, "high": dataframe["high"], "low": dataframe["low"], "weighted": weighted, "volume": volume}).groupby("day")
        daily = grouped.agg({"high": "max", "low": "min", "weighted": "sum", "volume": "sum"})
        daily["poc"] = daily["weighted"] / daily["volume"].replace(0, np.nan)
        daily["dispersion"] = grouped.apply(lambda frame: np.sqrt(np.average((typical.loc[frame.index] - daily.loc[frame.name, "poc"]) ** 2, weights=np.maximum(volume.loc[frame.index], 1e-12))))
        width = float(self.buy_vp_value_width.value)
        daily["val"] = (daily["poc"] - width * daily["dispersion"]).clip(lower=daily["low"])
        daily["vah"] = (daily["poc"] + width * daily["dispersion"]).clip(upper=daily["high"])
        daily = daily.shift(1)
        lookup = day.map(daily["poc"]).to_numpy()
        dataframe["vp_poc"] = lookup
        dataframe["vp_val"] = day.map(daily["val"]).to_numpy()
        dataframe["vp_vah"] = day.map(daily["vah"]).to_numpy()
        mean_volume = volume.rolling(12, min_periods=6).mean().replace(0, np.nan)
        dataframe["vp_tape_speed"] = ((dataframe["close"] - dataframe["open"]) / dataframe["open"].replace(0, np.nan)) * (volume / mean_volume)
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        speed = float(self.buy_vp_tape_speed.value)
        dataframe["enter_long"] = (dataframe["close"].lt(dataframe["vp_val"]) & dataframe["vp_tape_speed"].ge(speed)).astype(int)
        dataframe["enter_short"] = (dataframe["close"].gt(dataframe["vp_vah"]) & dataframe["vp_tape_speed"].le(-speed)).astype(int)
        dataframe["enter_tag"] = np.where(dataframe["enter_long"], "volume_profile_long", np.where(dataframe["enter_short"], "volume_profile_short", ""))
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["exit_long"] = dataframe["close"].ge(dataframe["vp_poc"]).astype(int)
        dataframe["exit_short"] = dataframe["close"].le(dataframe["vp_poc"]).astype(int)
        dataframe["exit_tag"] = "volume_profile_poc"
        return dataframe
