"""VOL02 — IV-RV spread directional strategy.

IDEA: Compare DVOL (Deribit implied vol index) to annualized realized vol (RV).
When DVOL >> RV (vol premium is large / market is stressed), de-risk to flat.
When DVOL <= RV (vol is cheap or normal), stay long (risk-on).

We test both directions:
  - "Stay long when DVOL <= RV" (risk-on when IV cheap)
  - "Stay long when DVOL > RV" (contrarian: buy stress)

Small param grid: spread threshold (0 or +5 vol points above RV) x RV window (21d or 42d).
DVOL history starts 2021-03, so effective backtest starts ~2021-Q1.
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np


def make_target(rv_win_days: int = 21, spread_thresh: float = 0.0, direction: str = "risk_on"):
    """
    direction='risk_on':  long when DVOL - RV_annualized <= spread_thresh (IV cheap/normal)
    direction='stress':   long when DVOL - RV_annualized > spread_thresh  (IV expensive/stressed)
    Both use vol-targeting so position size is volatility-controlled.
    """
    def target_fn(df):
        c = df["close"].values.astype(float)
        bpd = al.bars_per_day(df)
        bpy = bpd * 365.25

        # Realized vol: annualized, causal (uses data up to bar i)
        r = al.simple_returns(c)
        rv_raw = al.realized_vol(r, win=max(2, rv_win_days * bpd), bars_per_year=bpy)
        # Convert to vol points (DVOL is in vol points = percentage, e.g. 65.0 means 65% ann vol)
        rv_vp = rv_raw * 100.0  # e.g. 0.65 -> 65.0

        # DVOL: causal (known at bar open)
        iv_vp = al.dvol(df, df["close"].name if hasattr(df["close"], "name") else "BTC")

        # We need asset name - pass it via closure
        spread = iv_vp - rv_vp  # positive = IV > RV (vol premium)

        if direction == "risk_on":
            # Long when IV-RV <= threshold (IV is cheap/normal relative to RV)
            raw_dir = np.where(spread <= spread_thresh, 1.0, 0.0)
        else:
            # Long when IV-RV > threshold (buy when stressed / high vol premium)
            raw_dir = np.where(spread > spread_thresh, 1.0, 0.0)

        # Mask NaN in DVOL or RV -> flat
        mask_valid = np.isfinite(iv_vp) & np.isfinite(rv_vp)
        raw_dir = np.where(mask_valid, raw_dir, 0.0)

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

    return target_fn


def make_target_with_asset(asset: str, rv_win_days: int = 21, spread_thresh: float = 0.0, direction: str = "risk_on"):
    """Asset-aware version for study_weights (asset is passed per call)."""
    def target_fn(df):
        c = df["close"].values.astype(float)
        bpd = al.bars_per_day(df)
        bpy = bpd * 365.25

        r = al.simple_returns(c)
        rv_raw = al.realized_vol(r, win=max(2, rv_win_days * bpd), bars_per_year=bpy)
        rv_vp = rv_raw * 100.0

        iv_vp = al.dvol(df, asset)
        spread = iv_vp - rv_vp

        if direction == "risk_on":
            raw_dir = np.where(spread <= spread_thresh, 1.0, 0.0)
        else:
            raw_dir = np.where(spread > spread_thresh, 1.0, 0.0)

        mask_valid = np.isfinite(iv_vp) & np.isfinite(rv_vp)
        raw_dir = np.where(mask_valid, raw_dir, 0.0)

        return al.vol_target(raw_dir, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)

    return target_fn


def run_asset_aware(name, asset_configs, tfs=("1d",)):
    """
    Run study_weights with asset-aware DVOL lookup.
    asset_configs: dict of asset -> target_fn
    """
    import altlib as al
    import numpy as np

    cells = []
    for tf in tfs:
        per_asset = {}
        fee_ok_all = True
        for a, tgt_fn in asset_configs.items():
            df = al.get(a, tf)
            tgt = tgt_fn(df)
            base = al.eval_weights(df, tgt, fee_side=al.FEE_SIDE)
            sweep = {f"{2*f*100:.2f}%RT": al.eval_weights(df, tgt, fee_side=f)["full"]["sharpe"]
                     for f in al.FEE_SWEEP}
            fee_ok = sweep.get("0.20%RT", -9) > 0
            fee_ok_all = fee_ok_all and fee_ok
            per_asset[a] = dict(full=base["full"], holdout=base["holdout"],
                                tim=base["time_in_market"], turnover=base["turnover_per_year"],
                                fee_sweep=sweep, yearly=base["yearly"])
        min_full = min(per_asset[a]["full"]["sharpe"] for a in asset_configs)
        min_hold = min(per_asset[a]["holdout"].get("sharpe", 0.0) for a in asset_configs)
        cells.append(dict(tf=tf, per_asset=per_asset,
                          min_asset_full_sharpe=round(min_full, 3),
                          min_asset_holdout_sharpe=round(min_hold, 3),
                          full_sharpe=round(np.mean([per_asset[a]["full"]["sharpe"] for a in asset_configs]), 3),
                          fee_survives=fee_ok_all))
    verdict = al._verdict(cells)
    return dict(name=name, kind="weights", cells=cells, verdict=verdict)


if __name__ == "__main__":
    # Grid: 4 configs, each on 1d only -> 4 cells x 2 assets = 8 backtests (under limit)
    configs = [
        dict(rv_win=21, thresh=0.0,  direction="risk_on"),   # DVOL<=RV -> long
        dict(rv_win=21, thresh=5.0,  direction="risk_on"),   # DVOL<=RV+5 -> long
        dict(rv_win=21, thresh=0.0,  direction="stress"),    # DVOL>RV -> long (opposite)
        dict(rv_win=42, thresh=0.0,  direction="risk_on"),   # longer RV window
    ]

    best_rep = None
    best_min_hold = -999

    for cfg in configs:
        name = f"VOL02-{cfg['direction']}-rv{cfg['rv_win']}-t{cfg['thresh']}"
        asset_cfgs = {
            "BTC": make_target_with_asset("BTC", rv_win_days=cfg["rv_win"],
                                          spread_thresh=cfg["thresh"], direction=cfg["direction"]),
            "ETH": make_target_with_asset("ETH", rv_win_days=cfg["rv_win"],
                                          spread_thresh=cfg["thresh"], direction=cfg["direction"]),
        }
        rep = run_asset_aware(name, asset_cfgs, tfs=("1d",))
        print(al.fmt(rep))
        print()
        mh = rep["verdict"].get("best_holdout_sharpe", -999)
        if best_rep is None or mh > best_min_hold:
            best_rep = rep
            best_min_hold = mh

    # Override name to canonical VOL02
    best_rep["name"] = "VOL02"
    print("\n=== BEST CONFIG ===")
    print(al.fmt(best_rep))
    print("JSON:", al.as_json(best_rep))
