# source: https://raw.githubusercontent.com/dhjakhar/BullFlowReversion/604eb5882f615e9967625fab2cb787fd51b923b7/BullFlowReversion/bullflow_reversion.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__bullflow_reversion__20260102_165738(IStrategy):
    """
    QuantWinner Strategy:
    - Logic: Trend Following Swing Trading in Bull Markets only.
    - Timeframe: 5m (Held for 1-2 days typically).
    - Risk/Reward: Risk 5% to Make 10%.
    """

    # --- SETTINGS ---
    minimal_roi = {
        "0": 0.10,  # 10% Profit Target (Let the trend breathe)
    }

    # Stop Loss 5%. Tight enough to save capital, loose enough to survive noise.
    stoploss = -0.05

    # Trailing Stop: Lock in profits once we get past 4%
    trailing_stop = True
    trailing_stop_positive = 0.02   # Trail distance
    trailing_stop_positive_offset = 0.04 # Activate trail after 4% gain
    trailing_only_offset_is_reached = True

    timeframe = '5m'

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

    # --- INDICATORS ---
    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)

        # 1. MACRO FILTER: BULL MARKET ONLY
        dataframe['ema_50'] = ta.trend.EMAIndicator(close=dataframe['close'], window=50).ema_indicator()
        dataframe['ema_200'] = ta.trend.EMAIndicator(close=dataframe['close'], window=200).ema_indicator()
        dataframe['bull_market'] = dataframe['ema_50'] > dataframe['ema_200']

        # 2. VOLUME
        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 = []

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

        # 1. Z-SCORE REV (Buying the Dip)
        cond_zscore = (
            (dataframe['zscore'] < -self.buy_zscore_threshold.value) &
            (dataframe['zscore'].shift(1) >= -self.buy_zscore_threshold.value) &
            (macro_trend_ok) & (volume_ok)
        )
        conditions.append(cond_zscore)
        dataframe.loc[cond_zscore, 'enter_tag'] = 'zscore_reversion'

        # 2. VWAP BOUNCE (Institutional Value)
        cond_vwap = (
            (dataframe['close'] < dataframe['vwap_lower']) &
            (dataframe['close'] > dataframe['open']) & # Bullish rejection
            (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:
        # NO CUSTOM EXITS.
        # Rely on ROI (10%) and Trailing Stop.
        return dataframe
