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:
Adriano Dal Pastro
2026-08-29 06:45:47 +00:00
parent 2beb11b764
commit 993d17556a
5 changed files with 262 additions and 3 deletions
+64
View File
@@ -141,3 +141,67 @@ def test_leggi_jsonl_roundtrip(tmp_path):
+ json.dumps({"ts": 2, "eq_usde": 500.05}) + "\n")
rows = W.leggi(p)
assert len(rows) == 2 and rows[-1]["eq_usde"] == 500.05
# =============================================================================================
# IL RENDIMENTO OSSERVATO, E LA DECISIONE DI QUOTA RINVIATA
# =============================================================================================
# Il 2026-08-29 il gate USDE-01 si e' chiuso IDONEO (primo reward il 28/08, +0.052055 USDE) e la
# decisione di QUOTA e' stata rinviata dall'operatore a lunedi' 31/08: su UNA finestra il tasso
# implicito e' 3,80% annuo, su DUE e' 1,90% — cioe' tutta la differenza che conta.
def _lettura(ts_ms, eq, reward=None, con_trade=False):
return dict(ts=ts_ms, eq_usde=eq, reward_stimato=reward,
reward_rilevato=bool(reward and reward > 1e-3), con_trade=con_trade)
G = 86_400_000
def test_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.
Il denominatore e' il TEMPO, non il numero di finestre pagate."""
recs = [_lettura(0, 500.0), _lettura(G, 500.0, 0.0), _lettura(2 * G, 500.052055, 0.052055)]
r = W.rendimento(recs)
assert r["finestre"] == 2 and r["pagate"] == 1
# 0.052055 su 500 in 2 giorni -> ~1.90%, non il 3.80% della sola finestra che ha pagato
assert 0.018 < r["apr"] < 0.021, r
def test_la_finestra_sola_darebbe_il_doppio():
"""Il controllo positivo: se si guardasse solo il pagamento, il numero raddoppierebbe.
E' la ragione per cui l'APR si stampa sempre con il suo n."""
r = W.rendimento([_lettura(G, 500.0), _lettura(2 * G, 500.052055, 0.052055)])
assert 0.037 < r["apr"] < 0.040, r
def test_le_letture_con_trade_nel_mezzo_si_escludono():
"""P12: quel delta non e' solo reward, e ripararlo in silenzio sarebbe un'invenzione."""
recs = [_lettura(0, 500.0), _lettura(G, 600.0, 100.0, con_trade=True), _lettura(2 * G, 600.05, 0.05)]
r = W.rendimento(recs)
assert r["finestre"] == 1 and r["tot"] == 0.05
def test_troppo_poco_tempo_non_si_annualizza():
"""Un'ora di osservazione moltiplicata per 365 e' un numero, non una misura."""
r = W.rendimento([_lettura(0, 500.0), _lettura(3_600_000, 500.05, 0.05)])
assert r["apr"] is None and "non si annualizza" in r["nota"]
def test_nessuna_finestra_misurabile_non_e_zero():
r = W.rendimento([_lettura(0, 500.0)])
assert r["apr"] is None and r["finestre"] == 0
def test_la_quota_si_decide_da_lunedi_e_solo_se_IDONEO():
from datetime import datetime, timezone
def q(giorno, v):
return W.quota_da_decidere(datetime.fromisoformat(giorno + "T12:35:00+00:00"), v)
assert W.DECISIONE_QUOTA_DAL == "2026-08-31"
assert q("2026-08-30", "IDONEO") is False, "il rinvio dell'operatore non e' rispettato"
assert q("2026-08-31", "IDONEO") is True, "lunedi' la domanda non viene posta"
assert q("2026-09-30", "IDONEO") is True, "smette di chiedere: la decisione si dimentica"
assert q("2026-08-31", "IN_ATTESA") is False, "chiede la quota su un conto non idoneo"
assert q("2026-08-31", "NON_IDONEO") is False