#!/usr/bin/env python3
"""Hyperliquid price-only diagnostics for the GM! Occupancy Trader parameters.

Not a validation of edge: no GM! cell history is used, so every hour is an
entry on both sides. It shows how the τ-scaled exit geometry and post-only
entry behave on Hyperliquid 1h candles at a given fee schedule.

    python3 ops/traders/gm-occupancy/hl_diagnostics.py [--maker 0.00015] [--taker 0.00045] [--rf 0.045]
"""

from __future__ import annotations

import argparse
import json
import math
import statistics
import time
import urllib.request

import gm_occupancy as g

INFO = "https://api.hyperliquid.xyz/info"
COINS = ("BTC", "ETH", "HYPE")


def candles(coin: str, hours: int = 5000) -> list[dict]:
    end = int(time.time() * 1000)
    body = {"type": "candleSnapshot", "req": {"coin": coin, "interval": "1h", "startTime": end - hours * 3_600_000, "endTime": end}}
    req = urllib.request.Request(INFO, json.dumps(body).encode(), {"content-type": "application/json"})
    with urllib.request.urlopen(req, timeout=30) as resp:
        rows = json.load(resp)
    return [{"t": r["t"], "o": float(r["o"]), "h": float(r["h"]), "l": float(r["l"]), "c": float(r["c"])} for r in rows][:-1]


def exit_mix(bars: list[dict], tau_rt: float) -> dict:
    closes = [b["c"] for b in bars]
    counts = {"floor": 0, "move_against": 0, "time": 0}
    nets: list[float] = []
    bound = {"lo": 0, "hi": 0, "inside": 0}
    tbs: list[float] = []
    start = g.WINDOW + g.HOLD_HOURS
    for i in range(start, len(bars) - g.HOLD_HOURS):
        hist = closes[: i + 1]
        lm = g.loss_mass(hist)
        raw = g.LM_C * lm / g.L_MULT if lm else None
        tb = g.tau_b(hist, tau_rt)
        tbs.append(tb)
        if raw is not None:
            bound["lo" if raw < g.CLAMP_LO * tau_rt else "hi" if raw > g.CLAMP_HI * tau_rt else "inside"] += 1
        entry = closes[i]
        for side in ("long", "short"):
            reason, px = None, closes[i + g.HOLD_HOURS]
            for age in range(1, g.HOLD_HOURS + 1):
                b = bars[i + age]
                worst = b["l"] if side == "long" else b["h"]
                reason = g.exit_reason(side, entry, worst, age - 0.5, tb)
                if reason in ("floor", "move_against"):
                    f = g.floor_return(age - 0.5, tb)
                    px = entry * (1 + f) if side == "long" else entry * (1 - f)
                    break
                reason = None
            reason = reason or "time"
            counts[reason] += 1
            sign = 1.0 if side == "long" else -1.0
            nets.append(sign * math.log(px / entry) - tau_rt)
    n = sum(counts.values())
    total = sum(bound.values()) or 1
    return {
        "lots": n,
        "exit_share": {k: round(v / n, 3) for k, v in counts.items()},
        "tau_b_median_bps": round(statistics.median(tbs) * 1e4, 2),
        "initial_floor_median_pct": round(-g.L_MULT * statistics.median(tbs) * 100, 3),
        "clamp": {k: round(v / total, 3) for k, v in bound.items()},
        "mean_net_bps_unconditional": round(statistics.fmean(nets) * 1e4, 2),
    }


def maker_fill(bars: list[dict]) -> dict:
    """Post at the hour's close; filled when the next hour trades through it."""
    fill = {"long": 0, "short": 0}
    n = len(bars) - 1
    for i in range(n):
        c, nxt = bars[i]["c"], bars[i + 1]
        fill["long"] += nxt["l"] < c
        fill["short"] += nxt["h"] > c
    return {k: round(v / n, 3) for k, v in fill.items()}


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--maker", type=float, default=g.FEES["hyperliquid_base"]["maker"])
    ap.add_argument("--taker", type=float, default=g.FEES["hyperliquid_base"]["taker"])
    ap.add_argument("--builder", type=float, default=0.0)
    ap.add_argument("--rf", type=float, default=g.RF_APR, help="declared risk-free rate, a year")
    args = ap.parse_args()
    tau_rt = g.tau(maker=args.maker, taker=args.taker, builder=args.builder)
    rho_h = g.rho(args.rf)
    print(
        f"tau (maker entry + taker exit + 2 builder) = {tau_rt * 1e4:.2f} bps; "
        f"tau_H at r_f {args.rf:.2%} = {g.tau_hold(tau_rt, rho_h) * 1e4:.2f} bps; "
        f"h* = {g.profit_bar(tau_rt, rho_h) * 1e4:.2f} bps"
    )
    for coin in COINS:
        bars = candles(coin)
        span = f"{time.strftime('%Y-%m-%d', time.gmtime(bars[0]['t'] / 1000))}..{time.strftime('%Y-%m-%d', time.gmtime(bars[-1]['t'] / 1000))}"
        rh = g._r_hold([b["c"] for b in bars])
        out = {
            "coin": coin,
            "bars": len(bars),
            "span": span,
            "abs_hold_move_median_bps": round(statistics.median(abs(x) for x in rh) * 1e4, 1),
            "maker_fill_1h": maker_fill(bars),
            **exit_mix(bars, tau_rt),
        }
        print(json.dumps(out))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
