30286ea033
Nasce dall'annuncio sulla fine della Proof of Reserves. La notizia in se' vale
poco: si perde un segnale ad alta frequenza e DEBOLE (una PoR auto-pubblicata
prova che gli attivi esistono in un istante, non che coprano le passivita') e si
guadagna un audit annuale indipendente sotto VARA piu' il 90% degli attivi presso
Coinbase come custode. E non l'abbiamo mai sorvegliata: zero occorrenze nel
codice. Rinunciato di proposito a catturare l'ultimo dato — uno snapshot singolo
auto-riportato non regge lo standard di prova e non permetterebbe di stimare `p`.
CONSEGUENZA VERA: la decisione "100% Deribit fino a $20k" ha fra i suoi riapritori
"`p` che diventa stimabile invece che assunto". Dal 1/9 il segnale pubblico passa
da quotidiano ad ANNUALE: da una serie si stima, da un punto all'anno no. Quel
riapritore diventa praticamente irraggiungibile => la decisione e' ora gated SOLO
dal capitale.
TROVATO CERCANDO: esiste un feed RSS degli exchange-update, e conteneva quattro
annunci materiali che nessuno leggeva. Il caso che decide: le specifiche dei perp
USDC sono cambiate il 18/08, ANNUNCIATE IL 14/08. check_specs() e' nato da quella
svista e gira ogni ora, ma rileva la deriva DOPO; il feed l'avrebbe detta quattro
giorni PRIMA. La prima volta e' andata bene solo perche' i cambi erano RIDUZIONI.
Costruito scripts/live/venue_news.py (in cron_daily accanto a fee_watch):
- NON interpreta: dice "e' uscito questo, guardalo". Nessun automatismo su un
testo di marketing (P13). Classifica solo l'urgenza.
- parole-chiave DERIVATE da deribit._CONTRACT e config/live.json, non
ridichiarate (P1): chi aggiunge un asset allarga la sorveglianza da solo, ed
e' il test che lo blinda.
- primo giro semina senza allertare (P9); feed illeggibile -> exit 2, mai
silenzio implicito (P5).
Debito #8 si RESTRINGE, non si chiude: il Rulebook non ha un feed.
Confermato due volte lo zero USDC: l'espansione del 31/07 non contiene l'Italia
ne' alcun paese UE, mentre San Marino e Citta' del Vaticano SONO idonei. Non
asserisco una causa (ci sono anche Canada e Giappone).
DUE CORREZIONI dalla KB "Cross collateral specifications":
(a) Il saldo negativo costa una collateral fee dello 0,05% AL GIORNO = 18,25%/anno,
al secondo — 4,3x la resa USDE. Il 30/08 avevo scritto "lo finanzia a
interesse" SENZA il numero: giusto e vuoto. Col numero, il criterio del
cuscino di regolamento diventa aritmetica (-14 punti). E il ribilanciamento
automatico non salva: scatta a $1M assoluti o al 100% della cross equity,
irraggiungibili a $2k. Non veniamo ribilanciati: sanguiniamo la fee.
(b) L'haircut USDe ufficiale e' 5%, noi abbiamo 0.10 registrato come "verificato
sul venue" il 26/08 senza traccia di come. NON riparato (P12/M28): si tiene
0.10 perche' e' il lato conservativo, non e' sul percorso soldi e non morde
fino al 90% di quota. Divergenza dichiarata in config.
Di lato: BUIDL haircut 2% (sarebbe stato il miglior collaterale del listino, se
si potesse comprare), stETH 7,5% (terza ragione indipendente per lasciarlo stare).
Nessuna fonte documenta un tetto sulle QUANTITA': il ~31,2% resta misurato e non
spiegato — e ora si sa che non e' una svista di lettura.
Suite: 800 passati (5 nuovi su venue_news).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
174 lines
7.0 KiB
Python
174 lines
7.0 KiB
Python
#!/usr/bin/env python
|
|
"""venue_news.py — legge cosa il venue ANNUNCIA. SOLA LETTURA, non blocca mai nulla.
|
|
|
|
PERCHE' ESISTE (2026-08-31). Il debito #8 dice, testuale: *«Resta scoperto il Rulebook vero e
|
|
proprio: la sonda legge se il venue RISPONDE, non cosa il venue ANNUNCIA.»* Questa e' quella
|
|
meta'. In una settimana quattro cambi di policy Deribit materiali sono arrivati **a mano**, perche'
|
|
l'operatore ha incollato dei link — non perche' qualcosa li avesse visti:
|
|
|
|
* tetto sull'USDE (~31,2% dell'equity) * esclusione dell'Italia dai reward USDC
|
|
* permesso mancante su BUIDL * fine della Proof of Reserves quotidiana
|
|
|
|
E il caso che decide la questione: le **specifiche dei perpetual lineari USDC** sono cambiate il
|
|
18/08, annunciate il **14/08**. `check_specs()` rileva la deriva DOPO che e' avvenuta ed e' nato
|
|
proprio da quella svista; il feed l'avrebbe detto **quattro giorni prima**. Rilevare a posteriori e
|
|
sapere in anticipo non sono la stessa cosa: la prima volta e' andata bene solo perche' i cambi
|
|
erano RIDUZIONI (un valore piu' grosso resta conforme). Il giorno che Deribit ALZA un minimo, gli
|
|
ordini vengono rifiutati.
|
|
|
|
COSA E' E COSA NON E'. E' un promemoria che legge un feed RSS pubblico e dice *«e' uscito questo,
|
|
guardalo»*. **Non interpreta e non decide**: nessun automatismo su un testo di marketing (P13 — una
|
|
guardia sui NUMERI non copre il RAGIONAMENTO, e la prosa si legge come opinione di un lettore
|
|
fallibile). L'unica cosa che classifica e' l'URGENZA, e lo fa su parole DERIVATE dal codice
|
|
sorvegliato, non ridichiarate qui (P1): gli strumenti vengono da `deribit._CONTRACT`, le valute di
|
|
collaterale da `config/live.json`. Chi aggiunge uno strumento o una valuta allarga da solo la
|
|
sorveglianza, senza toccare questo file.
|
|
|
|
uv run python scripts/live/venue_news.py # report + allerta sui nuovi
|
|
uv run python scripts/live/venue_news.py --quiet # solo allerta (per il cron)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import html
|
|
import json
|
|
import re
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from src.live.notifier import notify # noqa: E402
|
|
|
|
FEED = "https://insights.deribit.com/exchange-updates/feed/"
|
|
STATE = ROOT / "data" / "live" / "venue_news.jsonl"
|
|
|
|
# Parole che alzano un annuncio a 🚨. Le prime sono FISSE (categorie di rischio che ci toccano
|
|
# per costruzione); le altre si DERIVANO dal codice, sotto.
|
|
_CHIAVI_FISSE = (
|
|
"contract specification", "margin", "collateral", "fee", "delist", "discontinu",
|
|
"reserve", "maintenance", "liquidation", "settlement", "withdrawal", "reward",
|
|
"jurisdiction", "eligib", "insurance fund", "socialized", "adl", "rulebook",
|
|
)
|
|
|
|
|
|
def chiavi() -> list[str]:
|
|
"""Parole-chiave DERIVATE dal codice sorvegliato (P1), piu' quelle fisse."""
|
|
out = set(_CHIAVI_FISSE)
|
|
try:
|
|
from src.live.deribit import _CONTRACT
|
|
for nome in _CONTRACT: # BTC_USDC-PERPETUAL -> "btc", "usdc"
|
|
for pezzo in re.split(r"[_\-]", nome.lower()):
|
|
if len(pezzo) >= 3 and pezzo != "perpetual":
|
|
out.add(pezzo)
|
|
except Exception:
|
|
pass
|
|
try:
|
|
cfg = json.loads((ROOT / "config" / "live.json").read_text())
|
|
out.add(str(cfg.get("usde", {}).get("index_name", "")).split("_")[0].lower())
|
|
except Exception:
|
|
pass
|
|
return sorted(k for k in out if k)
|
|
|
|
|
|
def scarica(url: str = FEED, timeout: float = 20.0) -> str | None:
|
|
"""Feed RSS pubblico (tokenless). None se non leggibile. Mai solleva."""
|
|
try:
|
|
import requests
|
|
r = requests.get(url, timeout=timeout,
|
|
headers={"User-Agent": "PythagorasGoal/venue_news (+read-only)"})
|
|
return r.text if r.status_code == 200 else None
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def voci(xml: str) -> list[dict]:
|
|
"""PURA. Estrae (guid, titolo, data, link) dal RSS. Mai solleva."""
|
|
out = []
|
|
for blocco in re.findall(r"<item>(.*?)</item>", xml or "", re.S):
|
|
def campo(tag):
|
|
m = re.search(rf"<{tag}[^>]*>(?:<!\[CDATA\[)?(.*?)(?:\]\]>)?</{tag}>", blocco, re.S)
|
|
return html.unescape(m.group(1).strip()) if m else ""
|
|
g = campo("guid") or campo("link")
|
|
if g:
|
|
out.append({"guid": g, "titolo": campo("title"),
|
|
"data": campo("pubDate"), "link": campo("link")})
|
|
return out
|
|
|
|
|
|
def rilevante(titolo: str, ks: list[str]) -> list[str]:
|
|
"""PURA. Quali parole-chiave compaiono nel titolo."""
|
|
t = titolo.lower()
|
|
return [k for k in ks if k in t]
|
|
|
|
|
|
def gia_visti() -> set[str]:
|
|
if not STATE.exists():
|
|
return set()
|
|
out = set()
|
|
for riga in STATE.read_text().splitlines():
|
|
try:
|
|
out.add(json.loads(riga)["guid"])
|
|
except Exception:
|
|
pass
|
|
return out
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--quiet", action="store_true")
|
|
a = ap.parse_args()
|
|
|
|
xml = scarica()
|
|
if xml is None:
|
|
if not a.quiet:
|
|
print(" ✗ feed non leggibile — 'non vedo' non e' 'niente di nuovo' (P5)")
|
|
return 2
|
|
|
|
ks, viste, nuove = chiavi(), gia_visti(), []
|
|
primo_giro = not STATE.exists()
|
|
tutte = voci(xml)
|
|
STATE.parent.mkdir(parents=True, exist_ok=True)
|
|
with STATE.open("a") as f:
|
|
for v in tutte:
|
|
if v["guid"] in viste:
|
|
continue
|
|
v["match"] = rilevante(v["titolo"], ks)
|
|
v["visto_il"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
v["seed"] = primo_giro
|
|
f.write(json.dumps(v) + "\n")
|
|
nuove.append(v)
|
|
|
|
# Primo giro: si semina lo storico SENZA allertare (P9: l'allarme massimo non si spende
|
|
# per un arretrato di 10 voci, o non verra' letto il giorno che e' vero).
|
|
if not primo_giro:
|
|
for v in nuove:
|
|
if v["match"]:
|
|
notify("🚨 annuncio Deribit sul NOSTRO percorso",
|
|
{"titolo": v["titolo"], "tocca": ", ".join(v["match"][:6]),
|
|
"data": v["data"], "link": v["link"],
|
|
"azione": "leggerlo: questo sorvegliante NON interpreta"})
|
|
else:
|
|
notify("📌 nuovo annuncio Deribit",
|
|
{"titolo": v["titolo"], "data": v["data"], "link": v["link"]})
|
|
|
|
if not a.quiet:
|
|
print("=" * 78)
|
|
print(f" VENUE NEWS — {len(tutte)} voci nel feed, {len(nuove)} nuove"
|
|
+ (" (PRIMO GIRO: storico seminato, nessun allarme)" if primo_giro else ""))
|
|
print("=" * 78)
|
|
for v in tutte:
|
|
m = rilevante(v["titolo"], ks)
|
|
segno = "🚨" if m else ("·" if v["guid"] in viste else "📌")
|
|
print(f" {segno} {v['data'][:16]:17s} {v['titolo'][:70]}")
|
|
if m:
|
|
print(f" tocca: {', '.join(m[:8])}")
|
|
print(f"\n parole derivate dal codice: {', '.join(ks)}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|