"""cc01_regime_watch — trigger di regime per riaprire il dossier CASH-AND-CARRY (2026-07-24). CONTESTO. CC01 (basis/funding carry) e' un LEAD archiviato: premio REALE ma procyclico (diario 2026-06-26-cash-carry-hl.md), non uno sleeve. La ricerca 2026-07-24 (goal50, R1) ha confermato il ciclo live: funding BTC +70% ann. (gen 2026) -> -3.4% (mag) -> ~0-4% (lug). Regola decisa: il dossier si riapre SOLO quando il funding medio 1-MESE e' sostenutamente ricco — non sugli spike (gen-2026 style, spariti in settimane). Questo watcher legge gli ultimi ~35g di funding orario BTC/ETH dall'API pubblica Hyperliquid (tokenless, stessa fonte certificata di fetch_hl_funding.py) e stampa: - media 30g e 7g annualizzate per BTC/ETH - stato trigger: QUIET (<10% ann.) | WARN (>=10%) | ALERT (>=15% -> riaprire CC01, che resta comunque vincolato a ~$20k+ di capitale e venue con funding eseguibile). Uso: `uv run python scripts/live/cc01_regime_watch.py` (candidato a una riga in cron_daily.sh: e' read-only, nessun ordine, nessuno stato.) """ from __future__ import annotations import datetime as dt import time import numpy as np import requests HL_INFO = "https://api.hyperliquid.xyz/info" HOURS_PER_YEAR = 24 * 365 WARN_ANN = 0.10 # >=10% ann. su media 30g -> WARN ALERT_ANN = 0.15 # >=15% ann. su media 30g -> ALERT: riaprire il dossier CC01 def _post(payload: dict, max_retry: int = 6): delay = 1.0 for _ in range(max_retry): r = requests.post(HL_INFO, json=payload, timeout=30) if r.status_code == 429 or r.status_code >= 500: time.sleep(delay) delay = min(delay * 2, 20) continue r.raise_for_status() return r.json() r.raise_for_status() return r.json() def fetch_recent_funding(coin: str, days: int = 35) -> np.ndarray: """Funding orario degli ultimi `days` giorni (paginato, max 500/req).""" start = int((dt.datetime.now(dt.timezone.utc) - dt.timedelta(days=days)).timestamp() * 1000) rows, seen = [], set() while True: d = _post({"type": "fundingHistory", "coin": coin, "startTime": start}) if not d: break new = [x for x in d if x["time"] not in seen] if not new: break for x in new: seen.add(x["time"]) rows.append((x["time"], float(x["fundingRate"]))) start = new[-1]["time"] + 1 if len(d) < 500: break rows.sort() return np.array([r for _, r in rows], dtype=float) def main() -> None: print("=" * 78) print(" CC01 REGIME WATCH — funding medio 1-mese (Hyperliquid, orario, tokenless)") print("=" * 78) worst = "QUIET" for coin in ("BTC", "ETH"): f = fetch_recent_funding(coin) if len(f) < 24 * 7: print(f" {coin}: dati insufficienti ({len(f)} ore) — check API") continue ann30 = float(f[-24 * 30:].mean()) * HOURS_PER_YEAR ann7 = float(f[-24 * 7:].mean()) * HOURS_PER_YEAR state = ("ALERT" if ann30 >= ALERT_ANN else "WARN" if ann30 >= WARN_ANN else "QUIET") if state == "ALERT" or (state == "WARN" and worst == "QUIET"): worst = state print(f" {coin}: media 30g {ann30:+7.1%} ann. media 7g {ann7:+7.1%} ann. -> {state}") print("-" * 78) if worst == "ALERT": print(" >>> ALERT CC01: funding 30g >= 15% ann. — regime ricco SOSTENUTO.") print(" Riaprire il dossier cash-and-carry (vincoli invariati: ~$20k+,") print(" venue con funding eseguibile, tail risk NON nel dataset).") elif worst == "WARN": print(" >> WARN: funding 30g >= 10% ann. — osservare, non agire.") else: print(" regime QUIET: carry non raccoglibile (coerente con luglio 2026).") USO = """uso: cc01_regime_watch.py (nessun flag) sorveglia il regime di CC01 e stampa lo stato. Dettaglio nel docstring in testa al file.""" if __name__ == "__main__": from src.live.cli import valida valida("cc01_regime_watch.py", USO) main()