usde al 68,7%: il tetto del venue del 30-31/08 non c'era piu'; il versamento di prova del 25/08 dichiarato, TWR +7,83%

Su autorizzazione dell'operatore («usa piu' USDE», «cerca % massima raggiungibile»):
- usde_convert al 31,8% (774 USDE), poi sonda a saldo crescente r0906_usde_tetto_sonda.py
  (passi 100->20->4->1, doppio rifiuto prima di scendere, conferma a saldo neutro): da 31,8%
  a 68,7% in 39 ordini @ 1,0005, ZERO rifiuti. Fermata dal cuscino di regolamento (70%,
  derivato da config), non dal venue. «Il 70% non e' raggiungibile ne' ora ne' mai» (31/08)
  e' falsificato: il tetto e' sparito, non scalato. Non spiegato, puo' tornare.
- config: venue_cap_frac -> null (misura in venue_cap_misurato), quota_max_frac 0,50 -> 0,85
  (il valore scelto dall'operatore il 30/08 per questo scenario).
- debito §5.17: nessuno sorveglia il cuscino USDC (slack $58; una perdita del libro lo consuma
  da sola).

Versamento di prova di EUR 25 il 25/08 08:00Z (dichiarato dall'operatore): +3,3% su $647, sotto
la soglia del 10% del rilevatore, contato per 12 giorni come trading.
- data/live/movimenti_dichiarati.jsonl (append-only, nel backup) letto da
  journal.movimenti_dichiarati; movimenti_capitale lo fonde coi rilevati (fonte dichiarato,
  importo dell'operatore, dopo = prima + importo; dichiarato+rilevato se coincide; fuori
  letture o riga rotta -> avvisi). Report e nota di giornale lo dicono.
- trading da arming +87,35 -> +62,45; TWR +11,98% -> +7,83%. CLAUDE.md §2 aggiornato.
- tests: +6 in test_journal (conftest isola il file). Suite 916 verdi.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KSnordG9FT4q8M4MVm85GQ
This commit is contained in:
Adriano Dal Pastro
2026-09-07 05:49:15 +00:00
parent 95424f0755
commit 9f55652d99
13 changed files with 762 additions and 22 deletions
+10 -3
View File
@@ -92,14 +92,21 @@ def report() -> None:
f" (restano nel P&L, dichiarati)")
for ev in r["eventi"]:
mk = ev.get("mercato_max_pct")
dettaglio = (ev.get("mercato_nota") or "mercato non misurabile") if mk is None \
else f"mercato max {100 * mk:.2f}% a leva piena, salto {100 * ev['pct']:+.1f}%"
if ev.get("dichiarazione"):
dettaglio = ev["dichiarazione"]
elif mk is None:
dettaglio = ev.get("mercato_nota") or "mercato non misurabile"
else:
dettaglio = f"mercato max {100 * mk:.2f}% a leva piena, salto {100 * ev['pct']:+.1f}%"
print(f" {ev['ts_dopo'][:16]} {ev['delta']:+,.2f} [{ev['classe']}: {dettaglio}]")
for av in r.get("avvisi") or []:
print(f" ⚠️ {av}")
if r["trading"] is None:
print(f" trading da arming : n/d ({r['motivo']})")
else:
print(f" trading da arming : {r['trading']:+,.2f} (equity al netto dei movimenti "
f"certi; l'ora del movimento esce intera, vedi journal.rendimento_twr)")
f"certi; l'ora di un movimento RILEVATO esce intera, quella di un DICHIARATO "
f"resta — vedi journal.rendimento_twr)")
if r["twr"] is None:
print(f" TWR : n/d ({r['motivo']})")
else:
+163
View File
@@ -0,0 +1,163 @@
#!/usr/bin/env python
"""r0906_usde_tetto_sonda.py — sonda il TETTO del venue sull'USDE a SALDO NEUTRO, passi da 1-2 USDE.
INVIA ORDINI VERI (spot USDE_USDC, fee 0, ~$0,001 di spread per ordine).
Metodo (diari 30-31/08, sezione «la lezione di metodo»): un probe unico di taglia sbagliata produce un
falso negativo che si legge come risultato. Qui si sale a passi piccoli fino al primo rifiuto, si
ripete il rifiuto dopo aver riletto il book (per escludere l'ask che si e' mosso: il messaggio del
venue e' lo stesso `not_enough_funds_in_currency`), poi si CONFERMA a saldo neutro:
SELL 1 -> BUY 1 (deve passare) -> BUY 1 (deve essere rifiutato). Il bracket e' [L, L+1) USDE, con
l'equity totale letta nello stesso istante, cosi' la frazione ha la sua banda.
Guardie: prezzo dal BOOK (ask+5 tick, tetto 1,0010 / bid-5 tick, pavimento 0,9990), banda
[0,995, 1,005]; al massimo MAX_ADD USDE in piu' (quota mai oltre QUOTA_STOP); pausa fra ordini.
uv run python scripts/research/r0906_usde_tetto_sonda.py # dry-run: stato e piano
uv run python scripts/research/r0906_usde_tetto_sonda.py --esegui # sonda davvero
"""
from __future__ import annotations
import json
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts" / "live"))
from src.live import usde as U # noqa: E402
from src.live.deribit import DeribitRead # noqa: E402
import usde_convert as C # noqa: E402 (book_top, leggi_stato: stessi attrezzi)
LOG = ROOT / "data" / "live" / "usde_convert.jsonl"
PX_MIN, PX_MAX = C.PX_MIN, C.PX_MAX
TICK, MARGINE, CAP_BUY, CAP_SELL = 0.0001, 5, 1.0010, 0.9990
MAX_ADD = 60 # USDE in piu' al massimo (--max-add N): oltre, «tetto non trovato entro il limite»
QUOTA_HARD = 0.70 # mai oltre: e' la quota decisa dall'operatore il 30/08 (`usde.quota_target`), e coincide
# col CUSCINO DI REGOLAMENTO derivato da config (usde_convert.cuscino_richiesto_usd),
# che qui e' la guardia che morde davvero: si legge a ogni passo, non si ridichiara.
SCALA = [100.0, 20.0, 4.0, 1.0] # passi: si scende di uno a ogni doppio rifiuto (M13: risoluzione dell'effetto)
PAUSA, PAUSA_RIFIUTO = 12.0, 20.0
def stato(c: DeribitRead) -> dict:
eq_usdc, eq_usde, px, fonte = C.leggi_stato(c)
val, _ = U.valuta(eq_usde, px)
return {"usdc": eq_usdc, "usde": eq_usde, "px": px, "fonte": fonte, "val": val, "tot": eq_usdc + val,
"quota": val / (eq_usdc + val) if (eq_usdc + val) > 0 else None}
def prezzo_ordine(side: str) -> float | None:
bid, ask = C.book_top()
if side == "buy":
return None if ask is None else round(min(ask + MARGINE * TICK, CAP_BUY), 4)
return None if bid is None else round(max(bid - MARGINE * TICK, CAP_SELL), 4)
def ordine(c: DeribitRead, side: str, amt: float, px: float) -> tuple[bool, str]:
resp = c._unwrap(c._post("/mcp-deribit/tools/place_order",
{"instrument_name": U.SPOT, "side": side, "amount": float(amt),
"type": "limit", "price": px, "label": "usde-sonda-0906"})) or {}
if not isinstance(resp, dict) or resp.get("error") or "order" not in resp:
err = resp.get("error", resp) if isinstance(resp, dict) else resp
return False, str(err)[:120]
o = resp["order"]
return True, f"{o.get('order_state')} filled {o.get('filled_amount')}"
def main() -> int:
esegui = "--esegui" in sys.argv[1:]
max_add = float(sys.argv[sys.argv.index("--max-add") + 1]) if "--max-add" in sys.argv else MAX_ADD
c = DeribitRead()
s0 = stato(c)
if s0["px"] is None:
print(" ✗ prezzo USDE non leggibile — non si sonda al buio (P5)"); return 2
print("=" * 78)
print(f" SONDA TETTO USDE — {datetime.now(timezone.utc):%Y-%m-%dT%H:%M:%SZ} "
f"{'ESECUZIONE' if esegui else 'DRY-RUN'}")
print("=" * 78)
cap = U.cfg().get("venue_cap_frac")
print(f" conto : USDC ${s0['usdc']:,.2f} + USDE {s0['usde']:,.4f} (${s0['val']:,.2f}) = ${s0['tot']:,.2f}"
f" quota {s0['quota']:.2%} · tetto in config {cap:.1%} = {cap * s0['tot']:,.1f} USDE")
cusc, come = C.cuscino_richiesto_usd(s0["tot"])
print(f" piano : BUY a passi {''.join(f'{x:.0f}' for x in SCALA)} fino al rifiuto (max +{max_add:.0f} USDE;"
f" quota ≤{QUOTA_HARD:.0%} e USDC ≥ cuscino ${cusc:,.2f} = {come}), conferma SELL 1 → BUY 1 ok → BUY 1 rifiutato")
if not esegui:
print(" → dry-run. Rilancia con --esegui."); return 0
eventi: list[dict] = []
def fai(side: str, amt: float) -> bool:
px = prezzo_ordine(side)
if px is None or not (PX_MIN <= px <= PX_MAX):
print(f" ✗ prezzo d'ordine {px} non leggibile/fuori banda — mi fermo"); raise SystemExit(3)
ok, nota = ordine(c, side, amt, px)
time.sleep(3.0)
s = stato(c)
ev = {"ts": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "side": side, "amount": amt,
"px": px, "ok": ok, "nota": nota, "usde": s["usde"], "tot": s["tot"], "quota": s["quota"]}
eventi.append(ev)
print(f" · {side.upper():4} {amt:>3.0f} @ {px:.4f} -> {'OK ' if ok else 'RIFIUTATO'} "
f"USDE {s['usde']:,.4f} eq ${s['tot']:,.2f} quota {s['quota']:.3%} [{nota}]")
return ok
# --- salita a passi piccoli -------------------------------------------------------------
i_step, aggiunti, muro, limite = 0, 0.0, None, None
while True:
step = SCALA[i_step]
s = stato(c)
cusc, _ = C.cuscino_richiesto_usd(s["tot"])
if aggiunti + step > max_add:
limite = f"+{aggiunti:.0f} USDE raggiunti (max-add {max_add:.0f})"; break
if (s["val"] + step * s["px"]) / s["tot"] > QUOTA_HARD:
limite = f"quota {s['quota']:.2%} + passo supererebbe {QUOTA_HARD:.0%}"; break
if s["usdc"] - step * s["px"] < cusc:
limite = f"USDC ${s['usdc']:,.2f} passo scenderebbe sotto il cuscino ${cusc:,.2f}"; break
if fai("buy", step):
aggiunti += step; time.sleep(PAUSA); continue
time.sleep(PAUSA_RIFIUTO) # rifiuto: rileggo il book e riprovo UGUALE
if fai("buy", step):
aggiunti += step; time.sleep(PAUSA); continue
if i_step < len(SCALA) - 1:
i_step += 1; time.sleep(PAUSA); continue # doppio rifiuto: passo piu' fine
muro = stato(c); break
if limite:
s = stato(c)
print(f" ✗ limite della sonda: {limite} — tetto del venue NON trovato entro il limite;"
f" quota {s['quota']:.2%}, USDE {s['usde']:,.4f}")
if muro is None:
s = stato(c)
esito = {"verdetto": "NON_TROVATO", "limite": limite, "usde": s["usde"], "equity_tot": s["tot"],
"quota": s["quota"], "aggiunti": aggiunti, "eventi": eventi}
else:
# --- conferma a saldo neutro ---------------------------------------------------------
time.sleep(PAUSA)
ok_sell = fai("sell", 1); time.sleep(PAUSA)
ok_buy1 = fai("buy", 1) if ok_sell else False; time.sleep(PAUSA)
ok_buy2 = fai("buy", 1) if ok_buy1 else None; time.sleep(3.0)
fin = stato(c)
confermato = bool(ok_sell and ok_buy1 and ok_buy2 is False)
L = fin["usde"]
lo, hi = L / fin["tot"], (L + 1) / fin["tot"]
esito = {"verdetto": "CONFERMATO" if confermato else "NON_CONFERMATO",
"bracket_usde": [L, L + 1], "equity_tot": fin["tot"], "frazione": [lo, hi],
"aggiunti": aggiunti, "eventi": eventi}
print()
print(f" TETTO : [{L:,.4f} · {L + 1:,.4f}) USDE con equity ${fin['tot']:,.2f}"
f" ⇒ frazione {lo:.2%} {hi:.2%} ({esito['verdetto']}: "
f"sell1 {ok_sell} / buy1 {ok_buy1} / buy1 {ok_buy2})")
print(f" config : venue_cap_frac {cap:.3f} → bordo basso misurato oggi {lo:.4f}"
+ (" (config sotto il misurato: conservativa, ok)" if cap <= lo + 1e-9 else " ⚠️ config SOPRA il misurato"))
print(f" conto : USDC ${fin['usdc']:,.2f} + USDE {fin['usde']:,.4f} = ${fin['tot']:,.2f} quota {fin['quota']:.2%}")
with LOG.open("a") as f:
f.write(json.dumps({"ts": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"sonda_tetto": esito}) + "\n")
print(f" registrato : {LOG.relative_to(ROOT)}")
return 0 if esito["verdetto"] == "CONFERMATO" else 1
if __name__ == "__main__":
raise SystemExit(main())