"""SNAPSHOT point-in-time supply stablecoin (DefiLlama) — sblocca il lead STABLE fra 12 mesi. CONTESTO (2026-07-24, ondata on-chain). STABLE-supply-growth (gate risk-on su crescita 30g della supply stablecoin totale, thr 10% ann.) e' stato l'unico candidato dell'ondata sopra quasi tutti i gate (DSR 0.998, marginale ADDS, non-hedge) ma e' declassato a WATCH perche' la storia DefiLlama e' RICOSTRUITA retroattivamente (chain/coin aggiunte nel tempo): il "totale" visto oggi per il 2019-21 non era osservabile allora -> vintage-risk non sanabile. Criterio di rivisita scritto a diario (2026-07-24-onchain-sentiment-wave.md): quando una fonte POINT-IN-TIME della supply accumula >=12 mesi, ritestare il gate thr=10%. Questo script E' quella fonte: cattura oggi cio' che e' osservabile oggi. COSA FA (sola lettura, API pubblica tokenless, nessun ordine): - GET https://stablecoins.llama.fi/stablecoins (lista coin + circolante corrente); - 1 riga JSON per run APPESA a data/external/stable_snapshots/snapshots.jsonl: totale USD-peg, totale all-peg, breakdown top-15 per simbolo, n_coins. ~1 KB/riga -> ~400 KB/anno. Idempotente per giorno: se esiste gia' una riga con la stessa data UTC esce senza scrivere (safe da ri-run in cron). CADENZA RACCOMANDATA (quando/se si decide di cablarla — NON e' in cron adesso): 1 run/giorno dopo mezzanotte UTC (es. 00:15), accanto agli altri snapshot. USO: uv run python scripts/research/r0724_stable_snapshot.py NB ONESTO: questo script MISURA, non decide. Fra >=12 mesi la serie risponde a: il segnale crescita-30g calcolato su dati point-in-time replica quello calcolato sulla storia ricostruita? Se no, il lead STABLE era un artefatto di vintage e muore li'. """ from __future__ import annotations import json import sys import time from datetime import datetime, timezone from pathlib import Path import requests PROJECT_ROOT = Path(__file__).resolve().parents[2] OUT_DIR = PROJECT_ROOT / "data" / "external" / "stable_snapshots" OUT_FILE = OUT_DIR / "snapshots.jsonl" API = "https://stablecoins.llama.fi/stablecoins?includePrices=true" def fetch() -> dict: last = None for _ in range(4): try: r = requests.get(API, timeout=30) j = r.json() if "peggedAssets" in j: return j last = str(j)[:200] except Exception as e: last = str(e) time.sleep(2.0) raise RuntimeError(f"DefiLlama fail: {last}") def circ_usd(asset: dict) -> float: c = asset.get("circulating") or {} return float(sum(v for v in c.values() if isinstance(v, (int, float)))) def main() -> None: today = datetime.now(timezone.utc).strftime("%Y-%m-%d") if OUT_FILE.exists(): for line in OUT_FILE.read_text().splitlines(): try: if json.loads(line).get("date") == today: print(f"SKIP: snapshot per {today} gia' presente") return except json.JSONDecodeError: continue data = fetch() assets = data["peggedAssets"] usd_peg = [a for a in assets if a.get("pegType") == "peggedUSD"] tot_usd = sum(circ_usd(a) for a in usd_peg) tot_all = sum(circ_usd(a) for a in assets) top = sorted(usd_peg, key=circ_usd, reverse=True)[:15] row = { "date": today, "snap_ts": int(time.time() * 1000), "total_usd_peg": round(tot_usd, 2), "total_all_peg": round(tot_all, 2), "n_coins": len(assets), "n_usd_peg": len(usd_peg), "top15": {a.get("symbol", "?"): round(circ_usd(a), 2) for a in top}, "source": "stablecoins.llama.fi/stablecoins", } OUT_DIR.mkdir(parents=True, exist_ok=True) with OUT_FILE.open("a") as f: f.write(json.dumps(row, separators=(",", ":")) + "\n") print(f"OK {today}: totale USD-peg ${tot_usd/1e9:.2f}B ({len(usd_peg)} coin, " f"{len(assets)} totali) -> {OUT_FILE}") if __name__ == "__main__": sys.exit(main())