cinque punti della revisione 09/09: usde_watch a 1000 trade + finestra 24h, cuscino_watch (equity USDC, riconverte da solo), PREVDAY-01 kill/veto cablati, SCALA-01 al 2027-02-28, versamento 04/09 dichiarato
Decisi dall'operatore il 2026-09-10, verificati da revisione fable (15 segnalazioni, 12 applicate). - usde_watch: TRADE_LIMIT 1000 (count max Deribit) e trade_copertura(): lista troncata o ultima lettura oltre la finestra di 24h del gateway => somma NON leggibile, reward non attribuito (P12). Riga del 07/09 corretta nel log con campo `correzione` (400 USDE erano acquisti, non reward). - cuscino_watch.py (cron :53, monitor_health): equity USDC contro cuscino derivato da usde.cuscino_richiesto_usd (formula spostata in src/live/usde.py, usde_convert la importa); OK/PREAVVISO/SCOPERTO/BLIND; sotto zero lancia usde_convert --quota quota_ripristino(0.20)=0.64 --esegui con guardie (execution_enabled, depeg_warn, 1 tentativo/6h). Primo giro: PREAVVISO, +$26. - usde_convert: il tetto del venue vale solo in ACQUISTO (bloccava la vendita). - paper_prevday: GATE PREVDAY-01 cablato (2027-06-21, kill Sharpe giornaliero < -0,50 su >=180 g attivi, veto >=80% barre ricostruibili + divergenze non crescenti con soglia materiale). Oggi: +0,95 su 81 g, 1942/1943 ricostruibili, kill NON MATURO. - CLAUDE.md: arming 20/06 (TP01) / 23/06 (BOOK); piano EUR 5.000 chiuso col versamento 04/09 ($2.414,68 dal balance, dichiarato); SCALA-01 non prima del 2027-02-28 (A2 dal 01/09); PREVDAY-01 con data, kill, veto; §5.17 riparato con i limiti dichiarati (ratchet, slack zero a 0,70). - test: 1062 (+31): test_cuscino_watch (16), test_paper_prevday_gate (8), test_usde_watch (+5). Fixes #2 Fixes #3 Fixes #4 Fixes #5 Fixes #6 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kqvff47UBGeYfj1QeN4zE
This commit is contained in:
+54
-11
@@ -66,6 +66,25 @@ VERDETTO_DOPO = "2026-08-29T12:30:00Z" # ultima finestra reward del 29/08 conc
|
||||
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 -----------------
|
||||
# (1) `count` massimo di Deribit per get_user_trades_by_instrument: 1000. Il 07/09 il lettore
|
||||
# chiedeva limit=50 e la sonda del 06/09 aveva fatto 54 ordini: i 4 non letti (400 USDE)
|
||||
# sono finiti nel «reward» (issue #2). Se il venue restituisce ESATTAMENTE `limit` righe la
|
||||
# lista puo' essere troncata e la somma NON e' leggibile (P12: non si attribuisce).
|
||||
# (2) il gateway chiama l'endpoint senza `historical`/timestamp: Deribit restituisce solo le
|
||||
# ultime 24h — DOCUMENTATO ("Accessing historical trades and orders using API": recent
|
||||
# trades 24h, orders 30 min, count max 1000) e misurato (il fill 08/09 06:47Z invisibile
|
||||
# alle 12:55Z del 10/09, quello del 09/09 21:47Z visibile). Se l'ultima lettura e' piu'
|
||||
# vecchia della finestra, i trade fra la lettura e il bordo sono INVISIBILI e la somma non
|
||||
# e' leggibile.
|
||||
# ⚠️ BUCO DICHIARATO (D5): il cron gira ogni 24h + pochi secondi; con la tolleranza di 5 min
|
||||
# i trade nei primi ≤5 min dopo la lettura precedente sarebbero invisibili e sommati come
|
||||
# «leggibile». Innocuo: nessun attrezzo converte USDE alle 12:35 UTC, e senza tolleranza il
|
||||
# giro quotidiano sarebbe SEMPRE «non leggibile» e il tasso non avrebbe mai finestre nuove.
|
||||
TRADE_LIMIT = 1000
|
||||
FINESTRA_GATEWAY_MS = 24 * 3_600_000
|
||||
TOLL_FINESTRA_MS = 5 * 60_000
|
||||
|
||||
|
||||
def _parse(ts: str) -> datetime:
|
||||
return datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
||||
@@ -177,14 +196,36 @@ def _safe_client():
|
||||
return None
|
||||
|
||||
|
||||
def _trades_spot_da(client, ts_ms: int) -> tuple[float | None, int | None]:
|
||||
"""Somma con segno (buy +, sell -) dell'USDE scambiato spot dopo ts_ms. None = non leggibile."""
|
||||
def trade_copertura(n_righe: int, prev_ts_ms: int, now_ms: int,
|
||||
limit: int = TRADE_LIMIT, finestra_ms: int = FINESTRA_GATEWAY_MS,
|
||||
toll_ms: int = TOLL_FINESTRA_MS) -> str | None:
|
||||
"""PURA. None se la lista di trade COPRE l'intervallo (prev, now]; altrimenti il motivo per cui
|
||||
NON lo copre — e allora la somma non si usa (P12: un reward calcolato su una lista incompleta
|
||||
e' un'invenzione, il 07/09 valeva 400 USDE)."""
|
||||
if n_righe >= limit:
|
||||
return f"lista troncata: {n_righe} righe = limite {limit} del venue"
|
||||
if now_ms - prev_ts_ms > finestra_ms + toll_ms:
|
||||
ore = (now_ms - prev_ts_ms) / 3_600_000
|
||||
return (f"ultima lettura {ore:.1f}h fa, oltre la finestra di {finestra_ms / 3_600_000:.0f}h "
|
||||
f"del gateway: i trade piu' vecchi sono invisibili")
|
||||
return None
|
||||
|
||||
|
||||
def _trades_spot_da(client, ts_ms: int, now_ms: int | None = None
|
||||
) -> tuple[float | None, int | None, str | None]:
|
||||
"""Somma con segno (buy +, sell -) dell'USDE scambiato spot dopo ts_ms.
|
||||
-> (somma, n, motivo). somma None = non leggibile, e `motivo` dice perche' (P4)."""
|
||||
if client is None:
|
||||
return None, None
|
||||
return None, None, "gateway non raggiungibile"
|
||||
try:
|
||||
rows = client.trade_history(U.SPOT, limit=50)
|
||||
except Exception:
|
||||
return None, None
|
||||
rows = client.trade_history(U.SPOT, limit=TRADE_LIMIT)
|
||||
except Exception as e:
|
||||
return None, None, f"trade_history: {type(e).__name__}"
|
||||
if now_ms is None:
|
||||
now_ms = int(datetime.now(timezone.utc).timestamp() * 1000)
|
||||
motivo = trade_copertura(len(rows), ts_ms, now_ms)
|
||||
if motivo is not None:
|
||||
return None, None, motivo
|
||||
tot, n = 0.0, 0
|
||||
for t in rows:
|
||||
if int(t.get("timestamp") or 0) <= ts_ms:
|
||||
@@ -192,7 +233,7 @@ def _trades_spot_da(client, ts_ms: int) -> tuple[float | None, int | None]:
|
||||
amt = float(t.get("amount") or 0)
|
||||
tot += amt if (t.get("direction") or "").lower() == "buy" else -amt
|
||||
n += 1
|
||||
return round(tot, 8), n
|
||||
return round(tot, 8), n, None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
@@ -221,10 +262,11 @@ def main() -> int:
|
||||
if eq_usde is not None:
|
||||
usd, _nota = U.valuta(eq_usde, px)
|
||||
|
||||
trades_usde, n_trades = (None, None)
|
||||
trades_usde, n_trades, trades_motivo = (None, None, None)
|
||||
an = dict(delta=None, reward_stimato=None, reward_rilevato=False, con_trade=None)
|
||||
if stato == "OK" and prev is not None and prev.get("eq_usde") is not None:
|
||||
trades_usde, n_trades = _trades_spot_da(client, int(prev["ts"]))
|
||||
trades_usde, n_trades, trades_motivo = _trades_spot_da(
|
||||
client, int(prev["ts"]), int(now.timestamp() * 1000))
|
||||
an = analizza(prev, eq_usde, trades_usde)
|
||||
|
||||
quota = None
|
||||
@@ -237,7 +279,7 @@ def main() -> int:
|
||||
stato=stato, motivo_blind=motivo_blind,
|
||||
eq_usde=eq_usde, px=px, px_fonte=px_fonte, usd=usd,
|
||||
eq_usdc=eq_usdc, quota=quota,
|
||||
trades_usde=trades_usde, n_trades=n_trades, **an,
|
||||
trades_usde=trades_usde, n_trades=n_trades, trades_motivo=trades_motivo, **an,
|
||||
)
|
||||
v, v_motivo = verdetto(records + [rec], now)
|
||||
rec["verdetto"], rec["verdetto_motivo"] = v, v_motivo
|
||||
@@ -312,7 +354,8 @@ def main() -> int:
|
||||
f" fa margine). Con cross X:SM sarebbero ~${cross:,.0f} (haircut"
|
||||
f" {c['haircut']:.0%})")
|
||||
if an["delta"] is not None:
|
||||
attr = ("non attribuibile (trade non leggibili)" if an["reward_stimato"] is None
|
||||
attr = (f"non attribuibile (trade non leggibili: {trades_motivo})"
|
||||
if an["reward_stimato"] is None
|
||||
else f"reward stimato {an['reward_stimato']:+.6f} USDE"
|
||||
+ (" [con trade nel mezzo]" if an["con_trade"] else ""))
|
||||
print(f" delta : {an['delta']:+.6f} USDE dall'ultima lettura -> {attr}")
|
||||
|
||||
Reference in New Issue
Block a user