Files
PythagorasGoal/scripts/live/cc01_regime_watch.py
T
Adriano Dal Pastro de83909db9 un flag sconosciuto non e' l'azione di default: guardia su 18 script, indice USDE orario, pulizia
MISURATO oggi durante la revisione: `trades_db.py --help` non stampava l'uso, cadeva in sync()
e riscriveva meta.ultimo_sync. Nessuno dei 18 script di scripts/live/ usava argparse: un flag
sbagliato era il ramo else. Su journal.py avrebbe scritto pagina e riga di DB, su analista.py
avrebbe speso una chiamata al modello e mandato un Telegram.

- src/live/cli.valida: prima istruzione di ogni __main__, prima di connect()/sync/rete.
  --help -> 0 con l'uso; flag ignoto, valore mancante o posizionale -> 2 con l'elenco dei
  previsti (P4). NIENTE argparse: cambierebbe messaggi, codici d'uscita e --help di script
  che il cron gia' chiama.
- 18 script cablati (i 3 che scrivono + 14 + cc01), flag invariati.
- tests/test_cli_flag.py (30): elenco DERIVATO dalla cartella (P1), valida come prima
  istruzione, uso che documenta i flag, e i flag che il CRON usa davvero restano accettati
  (P15/P16); end-to-end su --help e flag ignoto con trades.db non toccato (M15).
  Verificato a mano: monitor_health --quiet, trades_db --sync --quiet, book_execute dry-run.

Debito §5.15, primo passo: balance_watch (orario) registra `usde_usdc` a ogni campione — None
con la ragione se illeggibile, mai 1,0. Il cablaggio nel bound quando la serie ha storia.

Pulizia dalla revisione: tests/helpers.carica_script al posto della 15a copia del loader
importlib (5 file del libro live); il fill di prova via upsert_fills invece di un INSERT che
lasciava verified NULL; asserzioni non ancorate al padding; movimenti_capitale accetta le
righe gia' lette (una SELECT invece di due ai due lati di una scrittura del cron).

Test 910 verdi (+52). Diario 2026-09-02c.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDJsH3iDSaBns3ccpPBwiu
2026-09-02 15:49:40 +00:00

106 lines
4.0 KiB
Python

"""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()