"""MRV04 — IBS (Internal Bar Strength) Mean-Reversion

HYPOTHESIS: Internal Bar Strength = (close - low) / (high - low).
Long when IBS < low_thresh (closed near low = oversold position within bar),
flat (or short) when IBS > high_thresh (closed near high = overbought).

Classic daily mean-reversion edge. Testing on certified BTC/ETH daily bars.

Variants tested:
  V1: Long-flat  thresholds 0.20/0.80 (classic textbook)
  V2: Long-flat  thresholds 0.25/0.75 (slightly wider)
  V3: Long-short thresholds 0.20/0.80 (adds short leg)
  V4: Long-flat  thresholds 0.15/0.85 (tighter = rarer signals)
  Best variant selected by min-asset hold-out Sharpe.

All positions are vol-targeted (20% annualized, 2× leverage cap).
Evaluated on 1d timeframe (IBS is a daily signal by design).
"""

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


# ---------------------------------------------------------------------------
# IBS calculation (causal: uses close, high, low of the same bar i)
# ---------------------------------------------------------------------------
def ibs(df) -> np.ndarray:
    h = df["high"].values.astype(float)
    l = df["low"].values.astype(float)
    c = df["close"].values.astype(float)
    rng = h - l
    # Avoid division by zero (doji bars with zero range)
    result = np.where(rng > 0, (c - l) / rng, 0.5)
    return result


# ---------------------------------------------------------------------------
# Variant builders
# ---------------------------------------------------------------------------
def make_ibs_longflat(low_thresh: float, high_thresh: float):
    """Long when IBS < low_thresh, flat when IBS > high_thresh, hold otherwise."""
    def target_fn(df):
        ibs_val = ibs(df)
        pos = np.full(len(df), np.nan)
        pos[0] = 0.0
        for i in range(1, len(df)):
            if ibs_val[i] < low_thresh:
                pos[i] = 1.0   # go long
            elif ibs_val[i] > high_thresh:
                pos[i] = 0.0   # go flat
            else:
                pos[i] = pos[i - 1]  # hold
        pos = np.nan_to_num(pos, nan=0.0)
        return al.vol_target(pos, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
    return target_fn


def make_ibs_longshort(low_thresh: float, high_thresh: float):
    """Long when IBS < low_thresh, short when IBS > high_thresh, hold otherwise."""
    def target_fn(df):
        ibs_val = ibs(df)
        pos = np.full(len(df), np.nan)
        pos[0] = 0.0
        for i in range(1, len(df)):
            if ibs_val[i] < low_thresh:
                pos[i] = 1.0   # go long
            elif ibs_val[i] > high_thresh:
                pos[i] = -1.0  # go short
            else:
                pos[i] = pos[i - 1]  # hold
        pos = np.nan_to_num(pos, nan=0.0)
        return al.vol_target(pos, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
    return target_fn


# ---------------------------------------------------------------------------
# Vectorized version (faster, equivalent logic using ffill)
# ---------------------------------------------------------------------------
def make_ibs_longflat_vec(low_thresh: float, high_thresh: float):
    """Vectorized long-flat IBS strategy."""
    def target_fn(df):
        ibs_val = ibs(df)
        # Signal: 1=long, 0=flat, NaN=hold (ffill)
        sig = np.where(ibs_val < low_thresh, 1.0,
               np.where(ibs_val > high_thresh, 0.0, np.nan))
        sig[0] = 0.0  # start flat
        pos = sig.copy()
        # forward-fill NaN (hold previous)
        import pandas as pd
        pos = pd.Series(pos).ffill().fillna(0.0).values
        return al.vol_target(pos, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
    return target_fn


def make_ibs_longshort_vec(low_thresh: float, high_thresh: float):
    """Vectorized long-short IBS strategy."""
    def target_fn(df):
        import pandas as pd
        ibs_val = ibs(df)
        sig = np.where(ibs_val < low_thresh, 1.0,
               np.where(ibs_val > high_thresh, -1.0, np.nan))
        sig[0] = 0.0
        pos = pd.Series(sig).ffill().fillna(0.0).values
        return al.vol_target(pos, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
    return target_fn


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

    variants = [
        ("MRV04-V1-LF-0.20/0.80", make_ibs_longflat_vec(0.20, 0.80)),
        ("MRV04-V2-LF-0.25/0.75", make_ibs_longflat_vec(0.25, 0.75)),
        ("MRV04-V3-LS-0.20/0.80", make_ibs_longshort_vec(0.20, 0.80)),
        ("MRV04-V4-LF-0.15/0.85", make_ibs_longflat_vec(0.15, 0.85)),
    ]

    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))
