"""Filtro edge minimo (`min_tp_frac`): MR01/DIP01 NON devono emettere segnali il cui TP
(la media) cade entro `min_tp_frac` dall'entry — sarebbero perdenti garantiti netto fee.
Proprietà testate su dati reali BTC 1h:
  1. monotonia: alzando min_tp_frac il numero di segnali non aumenta;
  2. ogni segnale superstite ha gap TP > min_tp_frac;
  3. con min_tp_frac=0 il comportamento è invariato (default off = backtest validato intatto).
"""
import numpy as np
import pytest
from src.data.downloader import load_data
from scripts.strategies.MR01_bollinger_fade import BollingerFade
import importlib

_dip_mod = importlib.import_module("scripts.strategies.DIP01_dip_buy")
DipCls = next(v for k, v in vars(_dip_mod).items()
              if isinstance(v, type) and k.lower().startswith("dip"))


@pytest.fixture(scope="module")
def btc():
    df = load_data("BTC", "1h")
    return df, df.index  # ts non usato dalle fade, basta un placeholder


def _gaps(signals, df):
    c = df["close"].values
    return [abs(s.metadata["tp"] - c[s.idx]) / c[s.idx] for s in signals]


def test_mr01_filter_monotone_and_gap(btc):
    df, ts = btc
    s = BollingerFade()
    base = dict(bb_window=50, k=2.5, sl_atr=2.0, max_bars=24)
    n0 = len(s.generate_signals(df, ts, **base, min_tp_frac=0.0))
    for f in (0.0010, 0.0015, 0.0020, 0.005):
        sig = s.generate_signals(df, ts, **base, min_tp_frac=f)
        assert len(sig) <= n0                               # monotonia
        gaps = _gaps(sig, df)
        assert all(g > f for g in gaps)                     # nessun superstite sotto soglia


def test_mr01_default_off_unchanged(btc):
    df, ts = btc
    s = BollingerFade()
    base = dict(bb_window=50, k=2.5, sl_atr=2.0, max_bars=24)
    a = s.generate_signals(df, ts, **base)                  # default (no kw)
    b = s.generate_signals(df, ts, **base, min_tp_frac=0.0)
    assert len(a) == len(b)


def test_dip01_filter_gap(btc):
    df, ts = btc
    s = DipCls()
    base = dict(n=50, z_in=2.0, sl_atr=2.5, max_bars=24)
    n0 = len(s.generate_signals(df, ts, **base, min_tp_frac=0.0))
    sig = s.generate_signals(df, ts, **base, min_tp_frac=0.0020)
    assert len(sig) <= n0
    assert all(g > 0.0020 for g in _gaps(sig, df))


def _load(mod_name):
    import importlib
    m = importlib.import_module(mod_name)
    return next(v() for k, v in vars(m).items()
                if isinstance(v, type) and getattr(v, "__module__", "") == m.__name__
                and hasattr(v, "generate_signals"))


def test_mr02_mr07_filter_gap(btc):
    """Anche MR02 (midpoint canale) e MR07 (ATR-scaled) onorano min_tp_frac."""
    df, ts = btc
    for mod, base in (
        ("scripts.strategies.MR02_donchian_fade", dict(n=20, sl_atr=2.0, max_bars=24)),
        ("scripts.strategies.MR07_return_reversal",
         dict(n=50, k=3.5, tp_atr=2.0, sl_atr=1.5, max_bars=24)),
    ):
        s = _load(mod)
        n0 = len(s.generate_signals(df, ts, **base, min_tp_frac=0.0))
        sig = s.generate_signals(df, ts, **base, min_tp_frac=0.0015)
        assert len(sig) <= n0
        assert all(g > 0.0015 for g in _gaps(sig, df))
