usde: gate CHIUSO IDONEO, quota rinviata a lunedi' — l'APR si legge col suo n
Il 2026-08-28 12:35Z `usde_watch` ha rilevato il primo reward (+0.052055 USDE su 500), e la regola pre-registrata il 26/08 PRIMA dell'esito ha chiuso il gate USDE-01 su IDONEO. Si apre la decisione di QUOTA, che e' dell'operatore. DECISIONE DELL'OPERATORE (29/08): la quota si decide lunedi' 31/08, su tre-quattro finestre invece che su una. Il motivo e' un numero: sul solo pagamento il tasso implicito e' 3,80% annuo, su DUE finestre — il 27/08 aveva pagato ZERO — e' 1,90%. Fra i due c'e' tutta la decisione, e il campione e' UN pagamento. `rendimento()`: APR sulle finestre OSSERVATE, col denominatore sul TEMPO VERO e non sul numero di finestre pagate — cosi' una finestra che non paga ABBASSA la stima invece di sparire (P5: quel silenzio e' uno zero). Le letture con trade nel mezzo si escludono, non si riparano in silenzio (P12). L'APR si stampa sempre col suo `n`, e sotto 4 finestre la riga dice «un pagamento non e' un tasso». Sulle letture vere: 1,97% annuo su 1,9 giorni, 1/2 finestre pagate. `quota_da_decidere()`: vera SOLO se IDONEO E il rinvio e' scaduto. Da lunedi' il watch manda il 📌 OGNI giorno — quota attuale, tetto di allerta, APR osservato — finche' non si decide (N9: una decisione rinviata senza promemoria e' rinviata per sempre). Stesso schema della soglia $15k di GTAA01, con la data al posto del capitale. Si smette togliendo `DECISIONE_QUOTA_DAL`. 6 test nuovi, incluso il controllo positivo — «la finestra sola darebbe il doppio» — che e' esattamente la ragione per cui l'APR non si cita senza il suo n. CONFERMATO il fix IB di ieri: il giro delle 00:30 ha 0 righe `orders request timed out` (erano 2 per giro, 63 su 63) e tutti e sei gli ETF scaricati. E una conferma involontaria del debito §5.13: SPY e' passato da 1996-09-04 a 1996-09-05 — la finestra rotolante ha perso un altro giorno in una notte, come misurato. Diario: docs/diary/2026-08-29-usde-idoneo-e-ib.md Suite: 795 passati, 0 falliti. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,14 @@ 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
|
||||
|
||||
|
||||
def _parse(ts: str) -> datetime:
|
||||
return datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
||||
@@ -102,6 +110,46 @@ def verdetto(records: list[dict], now: datetime) -> tuple[str, str]:
|
||||
return "IN_ATTESA", f"finestre reward ~12:00 UTC, verdetto dopo {VERDETTO_DOPO}"
|
||||
|
||||
|
||||
def rendimento(records: list[dict]) -> dict:
|
||||
"""PURA. Il rendimento sulle finestre OSSERVATE, col loro NUMERO accanto.
|
||||
|
||||
E' il numero che serve alla decisione di quota, e l'unico modo onesto di darlo e' con `n`
|
||||
attaccato: un pagamento non e' un tasso. Il denominatore e' il TEMPO VERO fra la prima e
|
||||
l'ultima lettura misurabile, non il conteggio delle finestre — cosi' una finestra che non
|
||||
paga abbassa la stima invece di sparire (P5: il silenzio in una serie di ritorni si legge
|
||||
come zero, e qui e' proprio uno zero).
|
||||
|
||||
Si escludono le letture con trade nel mezzo (`con_trade`): quel delta non e' solo reward, e
|
||||
una riparazione silenziosa sarebbe un'invenzione (P12).
|
||||
"""
|
||||
usabili = [r for r in records
|
||||
if r.get("reward_stimato") is not None and not r.get("con_trade")]
|
||||
pagate = [r for r in usabili if r.get("reward_rilevato")]
|
||||
if not usabili:
|
||||
return dict(apr=None, finestre=0, pagate=0, giorni=0.0, tot=0.0,
|
||||
nota="nessuna finestra misurabile")
|
||||
tot = float(sum(r["reward_stimato"] for r in usabili))
|
||||
eq = [float(r["eq_usde"]) for r in usabili if r.get("eq_usde")]
|
||||
eq_med = sum(eq) / len(eq) if eq else 0.0
|
||||
# tempo vero coperto: dalla lettura PRIMA della prima misurabile, all'ultima
|
||||
i0 = records.index(usabili[0])
|
||||
t0 = records[max(0, i0 - 1)]["ts"]
|
||||
giorni = (usabili[-1]["ts"] - t0) / 86_400_000.0
|
||||
if giorni < EPS_GIORNI or eq_med <= 0:
|
||||
return dict(apr=None, finestre=len(usabili), pagate=len(pagate), giorni=giorni, tot=tot,
|
||||
nota=f"solo {giorni:.2f} giorni osservati: non si annualizza")
|
||||
apr = tot / eq_med / giorni * 365.0
|
||||
return dict(apr=apr, finestre=len(usabili), pagate=len(pagate), giorni=giorni, tot=tot,
|
||||
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:
|
||||
@@ -229,6 +277,20 @@ def main() -> int:
|
||||
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")
|
||||
|
||||
cambiato = bool(inviati) or v != v_prev
|
||||
if not quiet or cambiato:
|
||||
print("=" * 78)
|
||||
@@ -249,6 +311,14 @@ def main() -> int:
|
||||
+ (" [con trade nel mezzo]" if an["con_trade"] else ""))
|
||||
print(f" delta : {an['delta']:+.6f} USDE dall'ultima lettura -> {attr}")
|
||||
print(f" verdetto : {v} — {v_motivo}")
|
||||
if rend["finestre"]:
|
||||
apr = "n/d" if rend["apr"] is None else f"{rend['apr']*100:.2f}% annuo"
|
||||
print(f" rendimento : {apr} su {rend['giorni']:.1f} giorni — {rend['nota']}"
|
||||
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