#29 B2: lo stato si salvava PRIMA dell'invio e il ritorno di notify() non si leggeva — un invio fallito restava "gia' detto" per l'episodio intero. Schema di cuscino_watch.py: nuove_condizioni()/rec["allertati"] contano una condizione come "gia' detta" solo se l'invio precedente e' RIUSCITO; un record vecchio senza `allertati` vale come "tutto allertato" (nessun rispam retroattivo). Lo stato si scrive una volta sola, a fine giro. #35 B3/B4: la quota non e' piu' una decisione dell'operatore rinviata a lunedi' — dal 10/09 (issue #7) e' DERIVATA dal cuscino USDC (usde._nota_cuscino) — quindi tolti DECISIONE_QUOTA_DAL, quota_da_decidere() e la notify "la decisione di QUOTA e' dovuta". Il tetto di allerta IDONEO legge c["quota_max_frac"] invece del 50% cablato. La riga "margine" non dice piu' "conto SEGREGATO" (il modello e' Cross X:SM dal 31/08): stampa il totale USDC + USDE scontato del cross, coi numeri che la funzione ha gia'. Le due modifiche toccano lo stesso blocco (allarmi + STATE write + stampa) e non si possono separare per hunk senza rischio: un solo commit per il file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+51
-50
@@ -58,12 +58,6 @@ EPS_REWARD = 1e-3 # USDE: sotto e' rumore di lettura; il reward atteso a $500
|
||||
FILL_TS = "2026-08-26T13:04:32Z" # conversione eseguita: 500 USDE @ 1.0003
|
||||
VERDETTO_DOPO = "2026-08-29T12:30:00Z" # ultima finestra reward del 29/08 conclusa
|
||||
|
||||
# La QUOTA e' dell'operatore, e il 2026-08-29 e' stata RINVIATA a lunedi' 31/08 per decidere su
|
||||
# piu' di una finestra invece che su una (il 27/08 aveva pagato ZERO, il 28/08 +0.052 USDE: fra
|
||||
# le due letture ci sono 3,80% e 1,90% annuo, cioe' tutta la differenza che conta). N9: una
|
||||
# decisione rinviata senza un promemoria e' rinviata per sempre — da lunedi' il watch lo dice
|
||||
# ogni giorno finche' la riga non viene tolta.
|
||||
DECISIONE_QUOTA_DAL = "2026-08-31" # lunedi': si decide la quota
|
||||
EPS_GIORNI = 0.5 # sotto mezza giornata l'APR non si annualizza
|
||||
|
||||
# --- lettura dei trade spot: i DUE limiti del canale, misurati il 2026-09-10 -----------------
|
||||
@@ -162,13 +156,6 @@ def rendimento(records: list[dict]) -> dict:
|
||||
nota=f"{len(pagate)}/{len(usabili)} finestre hanno pagato")
|
||||
|
||||
|
||||
def quota_da_decidere(now: datetime, verdetto_corrente: str) -> bool:
|
||||
"""PURA. La decisione di quota e' dovuta? Solo se il conto e' IDONEO e il rinvio e' scaduto."""
|
||||
if verdetto_corrente != "IDONEO":
|
||||
return False
|
||||
return now.date().isoformat() >= DECISIONE_QUOTA_DAL
|
||||
|
||||
|
||||
def condizioni(rec: dict | None, c: dict) -> set[str]:
|
||||
"""PURA. Condizioni di allerta attive su un record."""
|
||||
if not rec:
|
||||
@@ -188,6 +175,20 @@ def condizioni(rec: dict | None, c: dict) -> set[str]:
|
||||
return att
|
||||
|
||||
|
||||
def nuove_condizioni(att_now: set[str], att_prev: set[str], prev: dict | None) -> set[str]:
|
||||
"""PURA (issue #29 B2). Le condizioni da notificare in questo giro.
|
||||
|
||||
Fino a qui lo stato si salvava PRIMA dell'invio e il ritorno di notify() non si leggeva: un
|
||||
invio fallito restava "gia' detto" per l'episodio intero (l'ora dopo att_prev diceva ancora
|
||||
che la condizione c'era, quindi non era piu' "nuova"). Schema di cuscino_watch.py: una
|
||||
condizione conta come "gia' detta" solo se era attiva l'ultima volta E il suo invio e'
|
||||
RIUSCITO — cioe' compare in `allertati` del record precedente. Un record vecchio senza
|
||||
`allertati` (scritto prima di questo fix) vale come "tutto allertato": nessun rispam
|
||||
retroattivo delle condizioni gia' viste."""
|
||||
confermate_prev = set((prev or {}).get("allertati", att_prev))
|
||||
return att_now - (att_prev & confermate_prev)
|
||||
|
||||
|
||||
def _safe_client():
|
||||
try:
|
||||
from src.live.deribit import DeribitRead
|
||||
@@ -284,54 +285,58 @@ def main() -> int:
|
||||
v, v_motivo = verdetto(records + [rec], now)
|
||||
rec["verdetto"], rec["verdetto_motivo"] = v, v_motivo
|
||||
|
||||
STATE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with STATE.open("a") as fh:
|
||||
fh.write(json.dumps(rec) + "\n")
|
||||
|
||||
# --- allarmi: transizioni (warn/quota/blind), ripetizione solo per il crit ---
|
||||
# --- allarmi: transizioni (warn/quota/blind), ripetizione solo per il crit -----------------
|
||||
# issue #29 (B2): fino a qui lo stato si salvava PRIMA dell'invio e il ritorno di notify()
|
||||
# non si leggeva — un invio fallito restava "gia' detto" per l'episodio intero. Schema di
|
||||
# cuscino_watch.py: `allertati` nel record = le condizioni il cui invio E' RIUSCITO. Lo stato
|
||||
# si scrive una volta sola, a fine giro, con `allertati` gia' dentro (vedi sotto).
|
||||
att_now, att_prev = condizioni(rec, c), condizioni(prev, c)
|
||||
nuove = att_now - att_prev
|
||||
nuove = nuove_condizioni(att_now, att_prev, prev)
|
||||
confermate_prev = set((prev or {}).get("allertati", att_prev))
|
||||
inviati = []
|
||||
allertati = (att_now & att_prev & confermate_prev) - {"DEPEG_CRIT"} # attive, gia' confermate
|
||||
if "DEPEG_CRIT" in att_now:
|
||||
notify("🚨 USDE DEPEG", {"indice": px, "soglia": c["depeg_crit"],
|
||||
"collaterale": f"${usd:,.0f}" if usd else "?",
|
||||
"azione": "valutare riconversione — la quota e' il controllo (N4)"})
|
||||
ok = notify("🚨 USDE DEPEG", {"indice": px, "soglia": c["depeg_crit"],
|
||||
"collaterale": f"${usd:,.0f}" if usd else "?",
|
||||
"azione": "valutare riconversione — la quota e' il controllo (N4)"})
|
||||
inviati.append("DEPEG_CRIT")
|
||||
if ok:
|
||||
allertati.add("DEPEG_CRIT")
|
||||
if "DEPEG_WARN" in nuove:
|
||||
notify("⚠️ USDE sotto la pari", {"indice": px, "soglia": c["depeg_warn"],
|
||||
"nota": "entra gia' nel sizing orario del book"})
|
||||
ok = notify("⚠️ USDE sotto la pari", {"indice": px, "soglia": c["depeg_warn"],
|
||||
"nota": "entra gia' nel sizing orario del book"})
|
||||
inviati.append("DEPEG_WARN")
|
||||
if ok:
|
||||
allertati.add("DEPEG_WARN")
|
||||
if "QUOTA_OVER" in nuove:
|
||||
notify("⚠️ quota USDE sopra il tetto", {"quota": f"{quota:.1%}" if quota else "?",
|
||||
"tetto": f"{c['quota_max_frac']:.0%}",
|
||||
"nota": "anche una deriva passiva conta (il libro perde -> quota sale)"})
|
||||
ok = notify("⚠️ quota USDE sopra il tetto", {"quota": f"{quota:.1%}" if quota else "?",
|
||||
"tetto": f"{c['quota_max_frac']:.0%}",
|
||||
"nota": "anche una deriva passiva conta (il libro perde -> quota sale)"})
|
||||
inviati.append("QUOTA_OVER")
|
||||
if ok:
|
||||
allertati.add("QUOTA_OVER")
|
||||
if "BLIND" in nuove:
|
||||
notify("⚠️ usde_watch BLIND", {"motivo": motivo_blind or "?",
|
||||
"nota": "'non vedo' non e' 'va tutto bene' (P5)"})
|
||||
ok = notify("⚠️ usde_watch BLIND", {"motivo": motivo_blind or "?",
|
||||
"nota": "'non vedo' non e' 'va tutto bene' (P5)"})
|
||||
inviati.append("BLIND")
|
||||
if ok:
|
||||
allertati.add("BLIND")
|
||||
rec["allertati"] = sorted(allertati)
|
||||
|
||||
v_prev = prev.get("verdetto") if prev else "IN_ATTESA"
|
||||
if v != v_prev:
|
||||
if v == "IDONEO":
|
||||
notify("📌 USDE: conto IDONEO ai reward", {"dettaglio": v_motivo,
|
||||
"prossimo passo": "decisione di quota dell'operatore (tetto allerta 50%)"})
|
||||
"prossimo passo": f"decisione di quota dell'operatore (tetto allerta {c['quota_max_frac']:.0%})"})
|
||||
elif v == "NON_IDONEO":
|
||||
notify("⚠️ USDE: conto NON idoneo", {"dettaglio": v_motivo})
|
||||
inviati.append(f"VERDETTO->{v}")
|
||||
|
||||
# La QUOTA e' dell'operatore ed e' stata rinviata a lunedi': da quel giorno lo si dice OGNI
|
||||
# giorno, non solo alla transizione. Ripetersi qui e' voluto — non e' un allarme che chiede
|
||||
# un'azione impossibile (P14), e' una domanda che da lunedi' si puo' finalmente rispondere,
|
||||
# con accanto il numero per rispondere. Si smette togliendo `DECISIONE_QUOTA_DAL`.
|
||||
rend = rendimento(records + [rec])
|
||||
if quota_da_decidere(now, v):
|
||||
apr = "n/d" if rend["apr"] is None else f"{rend['apr']*100:.2f}% annuo"
|
||||
notify("📌 USDE: la decisione di QUOTA e' dovuta", {
|
||||
"quota attuale": f"{quota:.1%}" if quota is not None else "n/d",
|
||||
"tetto di allerta": f"{c['quota_max_frac']:.0%} (mai 100%: R1 emittente non recuperabile)",
|
||||
"rendimento osservato": f"{apr} — {rend['nota']}, {rend['giorni']:.1f} giorni",
|
||||
"nota": "un pagamento non e' un tasso: leggere l'APR con il suo n"})
|
||||
inviati.append("QUOTA_DA_DECIDERE")
|
||||
|
||||
STATE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with STATE.open("a") as fh:
|
||||
fh.write(json.dumps(rec) + "\n")
|
||||
|
||||
cambiato = bool(inviati) or v != v_prev
|
||||
if not quiet or cambiato:
|
||||
@@ -344,14 +349,12 @@ def main() -> int:
|
||||
print(f" equity USDE : {eq_usde:,.4f} @ {px if px is not None else 'n/d'}"
|
||||
f" ({px_fonte}) -> ${usd:,.2f}" if usd is not None else " equity USDE : n/d")
|
||||
if quota is not None:
|
||||
# Il conto e' Segregated Standard Margin (letto sulla pagina margini il 31/08):
|
||||
# l'USDE NON fa margine per i perp USDC-settled, e l'haircut non si applica. Il
|
||||
# margine del book e' il solo silo USDC. Si stampa anche cosa DAREBBE il cross,
|
||||
# perche' e' la differenza che rende la cosa una decisione invece che un dettaglio.
|
||||
# Il modello di margine ATTIVO e' Cross X:SM dal 2026-08-31 (decisione
|
||||
# dell'operatore, config._nota_usde): l'USDE fa margine, scontato dell'haircut —
|
||||
# NON e' piu' il Segregated Standard Margin di prima di quella data.
|
||||
cross = eq_usdc + usd * (1.0 - c["haircut"])
|
||||
print(f" quota : {quota:.1%} del totale (tetto allerta {c['quota_max_frac']:.0%})")
|
||||
print(f" margine : ~${eq_usdc:,.0f} — il solo USDC (conto SEGREGATO: l'USDE non"
|
||||
f" fa margine). Con cross X:SM sarebbero ~${cross:,.0f} (haircut"
|
||||
print(f" margine : ~${cross:,.0f} USDC + USDE scontato (cross X:SM, haircut"
|
||||
f" {c['haircut']:.0%})")
|
||||
if an["delta"] is not None:
|
||||
attr = (f"non attribuibile (trade non leggibili: {trades_motivo})"
|
||||
@@ -366,8 +369,6 @@ def main() -> int:
|
||||
f" (tot {rend['tot']:+.6f} USDE)")
|
||||
if rend["finestre"] < 4:
|
||||
print(f" ⚠️ n={rend['finestre']}: un pagamento non e' un tasso")
|
||||
if v == "IDONEO" and not quota_da_decidere(now, v):
|
||||
print(f" quota : decisione RINVIATA a {DECISIONE_QUOTA_DAL} (operatore)")
|
||||
if inviati:
|
||||
print(f" allarmi : {', '.join(inviati)}")
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user