"""RSK02 — TSMOM long-flat with fast kill-switch on sharp short-horizon drawdown.

IDEA:
  Base signal = TSMOM (multi-horizon momentum: 1m, 3m, 6m) long-flat, vol-targeted (TP01-style).
  Kill-switch: if the position is long AND price has dropped >= `dd_thresh` (e.g. -10%) in the
  last `dd_bars` bars, go flat immediately (hold 0) until momentum re-triggers.

  The kill-switch aims to avoid the worst tail events that TSMOM rides through (sharp crashes).
  It should not improve Sharpe much but should cut max drawdown meaningfully.

Small grid: 2 param sets × 2 TFs = 4 total backtests.
  Config A: dd_thresh=-0.10, dd_bars=5  (10% in 5 bars)
  Config B: dd_thresh=-0.08, dd_bars=3  (8% in 3 bars — tighter)
"""

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


def tsmom_direction(df) -> np.ndarray:
    """Multi-horizon TSMOM: long if majority of 1m/3m/6m momentum is positive, else flat.
    Causal: uses close[i] returns through i."""
    c = df["close"].values.astype(float)
    bpd = al.bars_per_day(df)

    horizons_days = [21, 63, 126]  # ~1m, 3m, 6m
    signals = []
    for h in horizons_days:
        win = max(2, int(h * bpd))
        # Return over last `win` bars ending at i (causal)
        ret = np.full(len(c), np.nan)
        ret[win:] = c[win:] / c[:-win] - 1.0
        signals.append(np.sign(ret))

    # Vote: positive direction if at least 2 of 3 horizons are positive
    votes = np.nansum(np.stack(signals, axis=0), axis=0)
    direction = np.where(votes > 0, 1.0, 0.0)  # long-flat only
    # Need all 3 to be non-nan (warmup)
    nan_mask = np.any(np.isnan(np.stack(signals, axis=0)), axis=0)
    direction[nan_mask] = 0.0
    return direction


def rolling_drawdown(c: np.ndarray, win: int) -> np.ndarray:
    """Rolling drawdown from the high of the last `win` bars (including current bar i).
    Value at i = (c[i] - max(c[i-win+1:i+1])) / max(...), causal.
    """
    c = c.astype(float)
    n = len(c)
    dd = np.zeros(n)
    # use pandas rolling max (includes current bar)
    import pandas as pd
    rolling_max = pd.Series(c).rolling(win, min_periods=1).max().values
    dd = c / rolling_max - 1.0
    return dd


def make_target(dd_thresh: float, dd_bars: int):
    """Returns a target_fn(df) -> position array."""
    def target_fn(df):
        c = df["close"].values.astype(float)

        # 1. Base TSMOM direction (long or flat)
        direction = tsmom_direction(df)

        # 2. Kill-switch: compute rolling drawdown over dd_bars bars
        rd = rolling_drawdown(c, dd_bars)

        # 3. Kill: if drawdown within last dd_bars is below threshold, go flat
        # We check the minimum drawdown in the last dd_bars window (most severe recent drop)
        import pandas as pd
        # min of rd over last dd_bars: how far price fell from any peak in window
        # Using rolling min of dd to capture worst recent drawdown
        recent_worst_dd = pd.Series(rd).rolling(dd_bars, min_periods=1).min().values
        kill = recent_worst_dd <= dd_thresh  # True = kill signal active

        # Apply kill: override direction to 0 when kill is active
        direction_with_kill = np.where(kill, 0.0, direction)

        # 4. Vol-target the final direction
        tgt = al.vol_target(direction_with_kill, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
        return tgt

    return target_fn


if __name__ == "__main__":
    configs = [
        {"dd_thresh": -0.10, "dd_bars": 5,  "label": "kill10pct-5bar"},
        {"dd_thresh": -0.08, "dd_bars": 3,  "label": "kill08pct-3bar"},
    ]

    best_rep = None
    best_holdout = -999.0

    for cfg in configs:
        name = f"RSK02-{cfg['label']}"
        target_fn = make_target(cfg["dd_thresh"], cfg["dd_bars"])
        rep = al.study_weights(
            name,
            target_fn,
            tfs=("1d", "12h"),
        )
        print(al.fmt(rep))
        print("JSON:", al.as_json(rep))
        print()

        # Track best by holdout sharpe (min across assets)
        ho = rep["verdict"].get("best_holdout_sharpe", -999.0)
        if ho is not None and ho > best_holdout:
            best_holdout = ho
            best_rep = rep

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