"""MRV05 — Williams %R Mean-Reversion

HYPOTHESIS: Buy when %R(14) < -90 (oversold) with trend filter (close > SMA200);
exit (go flat) when %R > -50 (momentum restored). Long-flat only.

Williams %R = (Highest High(n) - Close) / (Highest High(n) - Lowest Low(n)) * -100
Range: -100 (most oversold) to 0 (most overbought).
%R < -80 = oversold zone; %R > -20 = overbought zone.

The exit condition (%R > -50) is causal: we check %R[i] and decide position for bar i+1.
This maps naturally to study_weights (continuous hold logic):
  - position[i] = 1 if %R[i] < -90 AND close[i] > SMA200[i] (buy signal)
  - position[i] = 0 if %R[i] > -50 (exit signal)
  - else hold previous position

Variants (small grid, 4 configs):
  V1: %R entry -90, exit -50, SMA200 trend filter, long-flat
  V2: %R entry -85, exit -50, SMA200 trend filter, long-flat (slightly less oversold entry)
  V3: %R entry -90, exit -50, SMA50 trend filter, long-flat (shorter trend filter)
  V4: %R entry -90, exit -40, SMA200 trend filter, long-flat (later exit)

Best variant selected by min-asset hold-out Sharpe.
All positions are vol-targeted (20% annualized, 2x leverage cap).
"""

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


# ---------------------------------------------------------------------------
# Williams %R calculation (causal: uses data <= bar i)
# ---------------------------------------------------------------------------
def williams_r(df: pd.DataFrame, win: int = 14) -> np.ndarray:
    """Causal Williams %R. Value at i uses data[i-win+1 .. i].
    %R = (HH - Close) / (HH - LL) * -100
    Range: -100 (oversold) to 0 (overbought).
    """
    h = df["high"].values.astype(float)
    l = df["low"].values.astype(float)
    c = df["close"].values.astype(float)
    n = len(c)
    wr = np.full(n, np.nan)
    # Vectorized rolling using pandas
    hh = pd.Series(h).rolling(win, min_periods=win).max().values
    ll = pd.Series(l).rolling(win, min_periods=win).min().values
    rng = hh - ll
    # Avoid division by zero
    valid = rng > 0
    wr[valid] = (hh[valid] - c[valid]) / rng[valid] * -100.0
    return wr


# ---------------------------------------------------------------------------
# Strategy factory
# ---------------------------------------------------------------------------
def make_wrpct_target(wr_entry: float = -90.0, wr_exit: float = -50.0,
                      sma_win: int = 200, wr_win: int = 14):
    """Williams %R long-flat mean-reversion with trend filter.

    Entry: %R[i] < wr_entry AND close[i] > SMA(sma_win)[i] -> go long
    Exit:  %R[i] > wr_exit  -> go flat
    Hold:  otherwise, maintain current position

    Causal: position decided using data <= close[i], held during bar i+1.
    Vol-targeted: 20% annualized, 2x leverage cap.
    """
    def target_fn(df):
        c = df["close"].values.astype(float)
        wr = williams_r(df, wr_win)
        sma_trend = al.sma(c, sma_win)

        # Vectorized state machine using ffill
        # Signal: 1 = enter long, 0 = exit to flat, NaN = hold
        # Priority: exit takes precedence over entry
        sig = np.where(
            wr > wr_exit,                              # exit condition
            0.0,
            np.where(
                (wr < wr_entry) & (c > sma_trend),    # entry condition
                1.0,
                np.nan                                  # hold
            )
        )

        # Start flat
        sig[0] = 0.0

        # Forward-fill NaN (hold previous position)
        pos = pd.Series(sig).ffill().fillna(0.0).values

        # Vol-target
        return al.vol_target(pos, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)

    return target_fn


# ---------------------------------------------------------------------------
# Run all variants and pick best
# ---------------------------------------------------------------------------
if __name__ == "__main__":
    TFS = ("1d",)

    variants = [
        ("MRV05-V1-WR90-exit50-SMA200", make_wrpct_target(-90.0, -50.0, 200, 14)),
        ("MRV05-V2-WR85-exit50-SMA200", make_wrpct_target(-85.0, -50.0, 200, 14)),
        ("MRV05-V3-WR90-exit50-SMA50",  make_wrpct_target(-90.0, -50.0,  50, 14)),
        ("MRV05-V4-WR90-exit40-SMA200", make_wrpct_target(-90.0, -40.0, 200, 14)),
    ]

    results = []
    for name, fn in variants:
        print(f"\nRunning {name} ...")
        rep = al.study_weights(name, fn, tfs=TFS)
        print(al.fmt(rep))
        results.append(rep)

    # Pick best by min_asset_holdout_sharpe
    best = max(results, key=lambda r: r["verdict"].get("best_holdout_sharpe", -99))
    print("\n" + "=" * 60)
    print(f"BEST VARIANT: {best['name']}")
    print(al.fmt(best))
    print("JSON:", al.as_json(best))
