"""MRV02 — BB reversion in calm regime (1d, discrete signals).

HYPOTHESIS: Buy lower BB(20,2) ONLY when realized vol is in low expanding-percentile
(calm regime). Exit at mid-BB. The gate is the alpha: filter out high-vol / volatile
periods; only trade the gentle reversions.

Style: al.study_signals (discrete entry/exit, 1d only)
Gate: RV <= expanding percentile of RV (calm = low expanding percentile threshold)
Entry: close <= lower BB(20,2)
TP: mid-BB (dynamic, recomputed each bar in the trade management)
SL: 2 * ATR below entry
Max bars: 20 days
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np
import pandas as pd


def make_entries(df: pd.DataFrame, bb_win: int = 20, bb_k: float = 2.0,
                 rv_win_days: int = 20, rv_pct_thresh: float = 30.0,
                 atr_win: int = 14, max_bars: int = 20):
    """
    Causal entry logic for MRV02.

    Entry conditions at close[i]:
    1. close[i] <= lower_BB(20,2) — price touched/crossed lower band
    2. rv_percentile(i) <= rv_pct_thresh — calm regime (low expanding RV percentile)

    TP: mid_BB at entry time (static target for the trade)
    SL: entry - 2*ATR (static)
    max_bars: 20 days
    """
    c = df["close"].values.astype(float)
    n = len(c)
    bpd = al.bars_per_day(df)
    bpy = bpd * 365.25

    # Bollinger Bands (causal: value at i uses data <= i)
    upper_bb, mid_bb, lower_bb = al.bbands(c, win=bb_win, k=bb_k)

    # Realized vol (annualized), window = rv_win_days bars
    rv_win = max(2, rv_win_days * bpd)
    r = al.simple_returns(c)
    rv = al.realized_vol(r, rv_win, bpy)

    # Expanding percentile of RV (causal: percentile of all RV values seen up to i)
    rv_series = pd.Series(rv)
    rv_pct = rv_series.expanding().rank(pct=True) * 100.0  # 0-100 percentile
    rv_pct = rv_pct.values

    # ATR for SL
    atr_vals = al.atr(df, win=atr_win)

    entries = [None] * n
    warmup = max(bb_win, rv_win, atr_win) + 1

    for i in range(warmup, n):
        # Gate: RV must be in calm regime
        if not np.isfinite(rv_pct[i]) or rv_pct[i] > rv_pct_thresh:
            continue
        # Gate: lower BB must be defined
        if not np.isfinite(lower_bb[i]) or not np.isfinite(mid_bb[i]):
            continue
        # Entry: close touches or crosses lower BB
        if c[i] > lower_bb[i]:
            continue
        # ATR must be defined
        if not np.isfinite(atr_vals[i]) or atr_vals[i] <= 0:
            continue

        tp_price = mid_bb[i]     # exit at mid-band (static target)
        sl_price = c[i] - 2.0 * atr_vals[i]  # SL: 2 ATR below entry

        # Only take trade if TP > entry price (there's room to profit)
        if tp_price <= c[i]:
            continue

        entries[i] = {
            "dir": +1,
            "tp": tp_price,
            "sl": sl_price,
            "max_bars": max_bars,
        }

    return entries


# ----------------------------------------------------------------
# Small parameter grid: bb_win x rv_pct_thresh (4 combos max)
# ----------------------------------------------------------------
GRID = [
    # (bb_win, rv_pct_thresh)
    (20, 30),   # canonical
    (20, 40),   # slightly more permissive gate
    (30, 30),   # wider bands
    (30, 40),   # wider bands + more permissive gate
]

print("MRV02 — BB reversion in calm regime")
print(f"Grid: {GRID}")
print()

best_rep = None
best_score = -999.0

for bb_win, rv_pct_thresh in GRID:
    label = f"MRV02[BB{bb_win},RVp{rv_pct_thresh}]"
    print(f"--- Testing {label} ---")

    def make_fn(bw=bb_win, rp=rv_pct_thresh):
        def entries_fn(df):
            return make_entries(df, bb_win=bw, rv_pct_thresh=rp)
        return entries_fn

    rep = al.study_signals(label, make_fn(), tfs=("1d",))
    print(al.fmt(rep))
    print()

    v = rep["verdict"]
    score = v.get("best_holdout_sharpe", -999.0) or -999.0
    if score > best_score:
        best_score = score
        best_rep = rep
        best_rep["_config"] = dict(bb_win=bb_win, rv_pct_thresh=rv_pct_thresh)

print("\n=== BEST CONFIG ===")
print(al.fmt(best_rep))
print()
print("JSON:", al.as_json(best_rep))
