"""OPT08 — Risk-reversal directional via DVOL-change skew proxy.

HYPOTHESIS: The 25-delta risk reversal sign can be proxied from DVOL changes.
When DVOL rises sharply relative to recent history (puts bid up = skew bullish for
downside fear = bearish tilt) we go short; when DVOL falls (fear subsides / calls
catching up relative = bullish tilt) we go long. We also test the opposite sign to
be honest about direction. We use DVOL z-score over rolling windows as the signal.

CAVEAT: This is a heavy proxy — DVOL is the ATM vol index, not skew. The actual
25d risk reversal is not in the data. Results should be treated as suggestive only.

DVOL history: starts 2021-03, so ~4 years of data. FULL window covers 2021-2026.
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np

# ── Signal construction ──────────────────────────────────────────────────────
# Proxy: if DVOL z-score is high (fear spike) -> bearish; if low (complacency) -> bullish
# This is the "risk-reversal as directional tilt" interpretation:
#   put skew expensive (DVOL spike) = hedgers worried -> fade / go short or stay flat
#   put skew cheap (DVOL low)       = complacency    -> go long
#
# We test 4 configurations:
#   A) zscore_win=20d, signal sign = bearish_on_dvol_spike (negative z -> long)
#   B) zscore_win=60d, signal sign = bearish_on_dvol_spike
#   C) zscore_win=20d, signal sign = bullish_on_dvol_spike (positive z -> long, contrarian)
#   D) zscore_win=60d, signal sign = bullish_on_dvol_spike
#
# After picking best config from 1d, we finalize.

def make_target(df, asset: str, zscore_win_days: int, dvol_spike_bearish: bool,
                vol_target_enabled: bool = True):
    """
    Build a continuous position in [-lev, +lev] based on DVOL z-score.
    dvol_spike_bearish=True:  high DVOL z -> short (fear = downside risk real)
    dvol_spike_bearish=False: high DVOL z -> long  (contrarian, mean-reversion of fear)
    """
    dv = al.dvol(df, asset)  # float array len(df), NaN before 2021-03
    bpd = al.bars_per_day(df)
    win = max(5, zscore_win_days * bpd)

    # z-score of DVOL level over rolling window (causal)
    z = al.zscore(dv, win)

    # Raw direction: clip z to [-2, 2] and normalize to [-1, 1]
    z_clip = np.clip(z, -2.0, 2.0) / 2.0

    if dvol_spike_bearish:
        # high DVOL (z>0) -> bearish (negative position)
        direction = -z_clip
    else:
        # high DVOL (z>0) -> bullish (contrarian: fear is overdone, buy the dip)
        direction = z_clip

    # Zero out where DVOL is NaN (pre-history)
    direction[~np.isfinite(dv)] = 0.0
    direction[~np.isfinite(direction)] = 0.0

    if vol_target_enabled:
        pos = al.vol_target(direction, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
    else:
        pos = np.clip(direction, -1.0, 1.0)

    return pos


# ── Grid: 4 configs ──────────────────────────────────────────────────────────
configs = [
    dict(zscore_win_days=20, dvol_spike_bearish=True,  label="z20-bearish"),
    dict(zscore_win_days=60, dvol_spike_bearish=True,  label="z60-bearish"),
    dict(zscore_win_days=20, dvol_spike_bearish=False, label="z20-bullish"),
    dict(zscore_win_days=60, dvol_spike_bearish=False, label="z60-bullish"),
]

# ── Run on 1d only (DVOL is daily, so sub-daily adds no signal) ─────────────
print("Running OPT08 — Risk-reversal directional (DVOL z-score proxy)")
print("DVOL history starts 2021-03; effective backtest window 2021-2026")
print()

best_rep = None
best_score = -999.0

for cfg in configs:
    lbl = cfg["label"]
    win = cfg["zscore_win_days"]
    bearish = cfg["dvol_spike_bearish"]

    def target_fn(df, _win=win, _bearish=bearish):
        # detect asset from the DVOL data shape
        # We must detect which asset this df belongs to; use a closure trick:
        # try BTC first, if raises try ETH -- but study_weights iterates per asset
        # so we need a per-asset function. We handle this in a wrapper below.
        return make_target(df, "BTC", _win, _bearish)

    # We need per-asset targets, so wrap differently
    def make_target_fn(win_, bearish_):
        def fn(df):
            # Detect asset: try BTC DVOL alignment and check if it matches
            # Actually altlib study_weights passes df already for each asset;
            # we don't know which asset from df alone. Use a heuristic:
            # check price range (BTC >> ETH)
            c = df["close"].values
            med_price = float(np.nanmedian(c))
            asset = "BTC" if med_price > 5000 else "ETH"
            return make_target(df, asset, win_, bearish_)
        return fn

    tf_fn = make_target_fn(win, bearish)
    rep = al.study_weights(f"OPT08-{lbl}", tf_fn, tfs=("1d",))

    best_cell = rep["cells"][0]
    score = best_cell["min_asset_holdout_sharpe"]
    print(f"Config {lbl}: minFull={best_cell['min_asset_full_sharpe']:+.2f}  "
          f"minHold={best_cell['min_asset_holdout_sharpe']:+.2f}  "
          f"feeOK={best_cell['fee_survives']}")

    if score > best_score:
        best_score = score
        best_rep = rep
        best_cfg = cfg

print()
print(f"Best config: {best_cfg['label']}")
print(al.fmt(best_rep))
print("JSON:", al.as_json(best_rep))
