# source: https://raw.githubusercontent.com/dhjakhar/BullFlowReversion/80f51750e8817820703d1b9dc874498ee1fce859/BullFlowReversion/version_2/BullFlowReversionV2.py
import numpy as np
import pandas as pd
from freqtrade.strategy import IStrategy, DecimalParameter
from pandas import DataFrame
from functools import reduce
import ta

class Github_dhjakhar_BullFlowReversion__BullFlowReversionV2__20260102_170821(IStrategy):

    minimal_roi = {
        "0": 0.10,
    }

    # ===== FIX 3: REDUCED STOPLOSS =====
    stoploss = -0.035

    trailing_stop = True
    trailing_stop_positive = 0.02
    trailing_stop_positive_offset = 0.04
    trailing_only_offset_is_reached = True

    timeframe = '5m'

    buy_enabled = 1
    buy_zscore_threshold = DecimalParameter(1.5, 3.0, decimals=1, default=2.0, space='buy')

    def _calculate_vwap(self, df):
        if 'date' not in df.columns:
            return df
        df['typical_price'] = (df['high'] + df['low'] + df['close']) / 3
        df['tp_volume'] = df['typical_price'] * df['volume']
        date_key = df['date'].dt.date
        df['cumulative_tp_volume'] = df.groupby(date_key)['tp_volume'].cumsum()
        df['cumulative_volume'] = df.groupby(date_key)['volume'].cumsum()
        df['daily_vwap'] = df['cumulative_tp_volume'] / df['cumulative_volume'].replace(0, np.nan)
        deviation = df['typical_price'] - df['daily_vwap']
        std_series = deviation.groupby(date_key).apply(lambda x: x.expanding().std())
        std_series.index = std_series.index.droplevel(0)
        df['vwap_std'] = std_series
        df['vwap_upper'] = df['daily_vwap'] + (2 * df['vwap_std'])
        df['vwap_lower'] = df['daily_vwap'] - (2 * df['vwap_std'])
        return df

    def _calculate_zscore(self, df):
        period = 20
        std_dev = df['close'].rolling(window=period).std()
        mean = df['close'].rolling(window=period).mean()
        df['zscore'] = (df['close'] - mean) / std_dev
        return df

    def feature_engineering_expand_all(self, dataframe: DataFrame, period: int,
                                        metadata: dict, **kwargs) -> DataFrame:
        dataframe = self._calculate_vwap(dataframe)
        dataframe = self._calculate_zscore(dataframe)

        dataframe['ema_200_htf'] = ta.trend.EMAIndicator(
            close=dataframe['close'],
            window=200 * 12
        ).ema_indicator()

        atr = ta.volatility.AverageTrueRange(
            dataframe['high'],
            dataframe['low'],
            dataframe['close'],
            14
        ).average_true_range()

        dataframe['bull_market'] = (
            (dataframe['close'] > dataframe['ema_200_htf']) &
            (atr > atr.shift(10))
        )

        dataframe['volume_mean'] = dataframe['volume'].rolling(window=20).mean()
        dataframe['high_volume'] = dataframe['volume'] > dataframe['volume_mean']

        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe = self.feature_engineering_expand_all(dataframe, 5, metadata)
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        macro_trend_ok = dataframe['bull_market']
        volume_ok = dataframe['high_volume']

        # ===== FIX 2: Z-SCORE REVERSION CONFIRMATION =====
        cond_zscore = (
            (dataframe['zscore'] < -self.buy_zscore_threshold.value) &
            (dataframe['zscore'] > dataframe['zscore'].shift(1)) &
            macro_trend_ok &
            volume_ok
        )
        conditions.append(cond_zscore)
        dataframe.loc[cond_zscore, 'enter_tag'] = 'zscore_reversion_confirmed'

        cond_vwap = (
            (dataframe['close'] < dataframe['vwap_lower']) &
            (dataframe['close'] > dataframe['open']) &
            macro_trend_ok &
            volume_ok
        )
        conditions.append(cond_vwap)
        dataframe.loc[cond_vwap, 'enter_tag'] = 'vwap_bounce'

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x | y, conditions) & (self.buy_enabled == 1),
                'enter_long'
            ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        return dataframe
