"""RSK06 — Time-stop momentum
HYPOTHESIS: Enter long on a breakout of the N-bar Donchian high, then EXIT
after exactly M bars (hard time-stop), no trailing. Tests whether momentum
has a fixed horizon with a clean carry/decay structure.

Signal style: al.study_signals (discrete entry/exit, 1d only).

Grid (<=4 param sets, total backtests = 4 * 2 assets = 8 <= 12 max):
  We test (breakout_window, hold_bars) pairs:
    A: (20, 10) — mid-term breakout, short hold
    B: (20, 20) — mid-term breakout, mid hold
    C: (40, 10) — longer breakout, short hold
    D: (40, 20) — longer breakout, mid hold

Entry: close[i] breaks above the prior `bk_win`-bar high (Donchian, causal, shifted).
Fill: close[i] (executable; NOT a high/low extreme, it's the close price).
Exit: close[i + hold_bars] — hard time-stop, no TP/SL.
Direction: long only (momentum = price breaks out above prior range).
No vol-targeting (discrete signal framework does not support it natively).
Fee: 0.10% RT Deribit taker baseline.
"""

import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np

# ---------------------------------------------------------------------------
# Signal builder
# ---------------------------------------------------------------------------
def make_entries(df, bk_win: int, hold_bars: int):
    """Return entries list: signal at i if close[i] > prior bk_win-bar high.
    Uses donchian() which shifts by 1 to prevent look-ahead.
    Entry price = close[i] (not high/low extreme).
    Hard exit after hold_bars bars (max_bars param in harness).
    """
    hi, _lo = al.donchian(df, bk_win)  # hi[i] = max high over [i-bk_win, i-1] — causal
    c = df["close"].values
    n = len(df)
    entries = []
    for i in range(n):
        if np.isnan(hi[i]):
            entries.append(None)
            continue
        # Breakout: current close exceeds the prior-window high
        if c[i] > hi[i]:
            entries.append({"dir": +1, "tp": None, "sl": None, "max_bars": hold_bars})
        else:
            entries.append(None)
    return entries


# ---------------------------------------------------------------------------
# Grid search: pick best config by min-asset hold-out Sharpe
# ---------------------------------------------------------------------------
GRID = [
    (20, 10),
    (20, 20),
    (40, 10),
    (40, 20),
]

best_rep = None
best_score = -999.0
best_label = ""

for bk_win, hold_bars in GRID:
    label = f"RSK06 bk={bk_win} hold={hold_bars}"
    print(f"\n--- Testing {label} ---")

    rep = al.study_signals(
        label,
        lambda df, bw=bk_win, hb=hold_bars: make_entries(df, bw, hb),
        tfs=("1d",),
    )
    print(al.fmt(rep))

    # Score by min-asset hold-out Sharpe (conservative)
    best_cell = max(rep["cells"], key=lambda c: c.get("min_asset_holdout_sharpe", -9))
    score = best_cell.get("min_asset_holdout_sharpe", -9.0)
    if score > best_score:
        best_score = score
        best_rep = rep
        best_label = label

# ---------------------------------------------------------------------------
# Final report on best config
# ---------------------------------------------------------------------------
print("\n" + "=" * 60)
print(f"BEST CONFIG: {best_label}")
print(al.fmt(best_rep))
print("JSON:", al.as_json(best_rep))
