"""VOL07 — DVOL spike contrarian long (capitulation timing).

HYPOTHESIS: When DVOL > 90th expanding percentile (fear/capitulation), buy at close,
hold ~1 week (max_bars). The idea: implied vol spikes coincide with panic bottoms,
and the subsequent reversion offers a contrarian long edge.

Signals style (discrete entry/exit), 1d only.
DVOL history starts 2021-03, so the full period is reduced to ~5 years.

Small grid:
  - dvol_pct threshold: 85th or 90th expanding percentile
  - max_bars (hold period): 5 or 7 days
Total: 2 x 2 = 4 configs x 1 TF = 4 backtests.
Best config selected by min(BTC holdout sharpe, ETH holdout sharpe).
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np
import pandas as pd

TFS = ("1d",)

def make_entries(dvol_pct_threshold: float, max_bars: int, cooldown: int = 3):
    """
    Entry: when DVOL crosses above the expanding `dvol_pct_threshold`-th percentile
    (i.e., DVOL[i] > expanding_pct and DVOL[i-1] <= expanding_pct — fresh spike).
    No TP/SL — exit by max_bars only.
    Cooldown: no new entry within `cooldown` bars of a previous entry.
    """
    def entries_fn(df: pd.DataFrame):
        dv = al.dvol(df, "BTC")  # will be overridden per-asset below — but we need asset

        # This placeholder is overridden by the per-asset wrapper in run()
        return _compute_entries(df, dv, dvol_pct_threshold, max_bars, cooldown)

    return entries_fn


def _compute_entries(df: pd.DataFrame, dv: np.ndarray, dvol_pct_threshold: float,
                     max_bars: int, cooldown: int):
    n = len(df)
    entries = [None] * n

    # Expanding percentile of DVOL (causal — uses only data up to i)
    # To avoid bias: require min 60 observations before triggering
    min_obs = 60
    last_entry_bar = -999

    dvol_series = pd.Series(dv)

    for i in range(min_obs, n):
        if np.isnan(dv[i]) or np.isnan(dv[i - 1]):
            continue

        # Expanding pct up to i (inclusive, causal)
        hist = dvol_series.iloc[:i + 1].dropna()
        if len(hist) < min_obs:
            continue
        threshold = float(np.percentile(hist.values, dvol_pct_threshold))

        # Fresh spike: DVOL crosses above threshold
        prev_hist = dvol_series.iloc[:i].dropna()
        prev_threshold = float(np.percentile(prev_hist.values, dvol_pct_threshold)) if len(prev_hist) >= min_obs else np.nan

        if np.isnan(prev_threshold):
            continue

        crossed_up = (dv[i] > threshold) and (dv[i - 1] <= prev_threshold)

        if crossed_up and (i - last_entry_bar >= cooldown):
            entries[i] = {"dir": +1, "tp": None, "sl": None, "max_bars": max_bars}
            last_entry_bar = i

    return entries


def make_entries_per_asset(asset: str, dvol_pct_threshold: float, max_bars: int, cooldown: int = 3):
    """Per-asset wrapper: uses the correct DVOL for each asset."""
    def entries_fn(df: pd.DataFrame):
        dv = al.dvol(df, asset)
        return _compute_entries(df, dv, dvol_pct_threshold, max_bars, cooldown)
    return entries_fn


# Grid
CONFIGS = [
    {"dvol_pct": 85, "max_bars": 5},
    {"dvol_pct": 85, "max_bars": 7},
    {"dvol_pct": 90, "max_bars": 5},
    {"dvol_pct": 90, "max_bars": 7},
]

best_rep = None
best_score = -np.inf

for cfg in CONFIGS:
    name = f"VOL07_p{cfg['dvol_pct']}_h{cfg['max_bars']}"
    print(f"\n--- Config: pct={cfg['dvol_pct']} max_bars={cfg['max_bars']} ---")

    # We need per-asset entries — study_signals calls entries_fn(df) without knowing asset.
    # Workaround: create a closure that wraps per-asset logic by detecting via df length/dates.
    # Better: run each asset separately and build the report manually.

    cells = []
    tf = "1d"
    per_asset = {}
    fee_ok_all = True

    for a in ("BTC", "ETH"):
        df = al.get(a, tf)
        ent_fn = make_entries_per_asset(a, cfg["dvol_pct"], cfg["max_bars"])
        ent = ent_fn(df)
        n_entries = sum(1 for e in ent if e is not None)
        print(f"  {a}: {n_entries} entries")

        base = al.eval_signals(df, ent, fee_rt=2 * al.FEE_SIDE, leverage=1.0, asset=a, tf=tf)
        sweep = {
            f"{2*f*100:.2f}%RT": al.eval_signals(df, ent, fee_rt=2 * f, leverage=1.0)["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"],
            n_trades=base["n_trades"], win_rate=base["win_rate"],
            fee_sweep=sweep, yearly=base["yearly"]
        )

    min_full = min(per_asset[a]["full"]["sharpe"] for a in ("BTC", "ETH"))
    min_hold = min(per_asset[a]["holdout"].get("sharpe", 0.0) for a in ("BTC", "ETH"))
    cell = 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 ("BTC", "ETH")]), 3),
        fee_survives=fee_ok_all
    )
    cells.append(cell)

    # Build a verdict-compatible report
    rep = dict(name=name, kind="signals", cells=cells, verdict=al._verdict(cells))
    print(al.fmt(rep))
    print("JSON:", al.as_json(rep))

    score = min_hold
    if score > best_score:
        best_score = score
        best_rep = rep

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