"""BOOK DERIBIT-ONLY LIVE EXECUTE — TP01 + SKH01 NETTATI in software su un solo conto Deribit mainnet. Porta il conto reale al target NETTO per asset (vedi src/live/book.py): per ogni asset combina la frazione long-flat di TP01 (peso 0.75) e il segno L/S di SKH01 (peso 0.25), e manda UN ordine con segno (long/short/flip) per raggiungerlo. Poi assicura un disaster-SL on-book sulla posizione NETTA. DOPPIO GATE DI SICUREZZA (entrambi necessari per inviare ordini reali): 1. config/live.json -> "execution_enabled": true (master switch, default false) 2. flag CLI --execute Senza entrambi e' un DRY-RUN (stampa il piano, NON invia). Reconciliation dopo ogni ordine; log in data/live/book_executions.jsonl. ⚠️ CADENZA: SKH01 decide su griglia 230m -> questo script va lanciato ogni ~230 minuti con la feed fresca all'ultima barra chiusa (NON il cron giornaliero, che mancherebbe gli ingressi). Gli exit di SKH sono SOFTWARE (latenza fino a fine barra 230m); solo il disaster-SL (-30%) e' on-book. uv run python scripts/live/book_execute.py # DRY-RUN (piano, nessun ordine) uv run python scripts/live/book_execute.py --execute # esegue SOLO se execution_enabled=true """ from __future__ import annotations import json import sys from pathlib import Path import pandas as pd PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT)) from src.live.book import book_report from src.live.execution import DeribitTrader from src.live.notifier import notify CONFIG = PROJECT_ROOT / "config" / "live.json" LOG_DIR = PROJECT_ROOT / "data" / "live" LOG = LOG_DIR / "book_executions.jsonl" def load_config() -> dict: cfg = json.loads(CONFIG.read_text()) if CONFIG.exists() else {} cfg.setdefault("execution_enabled", False) cfg.setdefault("max_notional_per_asset_usd", 300.0) cfg.setdefault("min_order_usd", 5.0) cfg.setdefault("disaster_sl_pct", 0.30) return cfg def log_event(rec: dict): LOG_DIR.mkdir(parents=True, exist_ok=True) with open(LOG, "a") as f: f.write(json.dumps(rec) + "\n") def _run(): cfg = load_config() want_execute = "--execute" in sys.argv[1:] enabled = bool(cfg["execution_enabled"]) do_execute = want_execute and enabled min_order = float(cfg["min_order_usd"]) sl_pct = float(cfg["disaster_sl_pct"]) r = book_report(live_feed=True) # target NETTO + conto/posizioni reali (feed SKH fresco) equity = r["equity"] print("=" * 88) print(" BOOK DERIBIT LIVE EXECUTE — TP01(0.75)+SKH01(0.25) NETTATI — Deribit mainnet (USDC linear)") print("=" * 88) mode = ("ESECUZIONE REALE" if do_execute else ("ARMATO ma manca --execute" if enabled else "DRY-RUN (execution_enabled=false)")) print(f" modo : {mode}") print(f" gate : execution_enabled={enabled} | --execute={want_execute}") print(f" conto reale : ${r['real_equity']:,.2f}" if r["real_equity"] else f" conto: {r['eq_basis']}") print(f" sizing base : ${equity:,.2f} | cap/asset ${r['cap_per_asset']:.0f} | min ${min_order:.0f} | disaster-SL -{sl_pct*100:.0f}%") print(f" ultima barra : {r['last_data']}\n") if r.get("skh_error"): # SKH feed fallito -> book.py ha forzato flat IN SILENZIO print(f" ⚠️ SKH FEED ERRORE (SKH forzato flat!): {r['skh_error']}") notify("⚠️ BOOK — SKH feed fallito (sleeve forzato flat)", {"error": r["skh_error"]}) if not r["online"]: print(" conto non leggibile (offline) -> stop, non eseguo a cieco.") if do_execute: notify("⚠️ BOOK LIVE — conto offline", {"nota": "salto l'esecuzione, non opero a cieco"}) return if r.get("pos_error"): # ONLINE ma posizione IGNOTA (read fallita -> assunta flat) print(f" 🛑 POSIZIONE NON LEGGIBILE -> NON eseguo a cieco: {r['pos_error']}") if do_execute: notify("🛑 BOOK LIVE — posizione non leggibile", {"error": r["pos_error"], "nota": "salto l'esecuzione, non opero a cieco"}) return if r.get("eq_fallback"): # equity reale non leggibile -> sizing su paper_cap print(f" ⚠️ EQUITY FALLBACK (sizing su paper_cap, NON blocco): {r['eq_fallback']}") if do_execute: # solo diagnostica: l'hard-cap $/asset limita il downside notify("⚠️ BOOK LIVE — equity fallback (sizing su paper_cap)", {"nota": r["eq_fallback"]}) trader = DeribitTrader() if do_execute else None actions = [] for a in r["assets"]: asset, inst = a["asset"], a["instrument"] net, cur, mark = a["net_target"], a["position_usd"], a["mark"] sk = a["skh_state"] sk_txt = "flat" if sk == "flat" else f"{sk['dir']}@{sk.get('entry')}" order = a["order"] if order is None: act = "HOLD (a target)" elif order.get("is_close"): act = f"CLOSE ${cur:,.0f}" elif order.get("needs_flip"): act = f"FLIP -> ${net:,.0f}" else: act = f"{order['side'].upper()} ${order['delta']:+,.0f}" print(f" {asset:<3} TP {a['tp_frac']:+.3f} · SKH {a['skh_sign']:+d}({sk_txt}) -> net ${net:+,.0f} " f"| pos ${cur:+,.0f} -> {act}") if do_execute and order is not None: fills = trader.rebalance_signed(inst, net, mark, min_usd=min_order) newpos = trader.position_usd(inst) for f in fills: print(f" -> {f.side.upper()} {f.filled:.4f} @ ${f.price or 0:,.1f} fee {f.fee_usdc:.5f} " f"({'OK' if f.verified else 'NON VERIFICATO: ' + f.notes})") log_event(dict(ts_utc=str(pd.Timestamp(r['last_data'])), asset=asset, action=act, side=f.side, filled=f.filled, price=f.price, fee=f.fee_usdc, verified=f.verified, notes=f.notes, net_target=net, pos_after=newpos, tp_frac=a["tp_frac"], skh_sign=a["skh_sign"])) det = dict(asset=asset, side=f.side, amount=round(f.filled, 4), price=round(f.price or 0, 1), fee=round(f.fee_usdc, 5), net=round(net, 0), pos_after=round(newpos, 0)) notify(f"✅ BOOK {act}" if f.verified else "⚠️ BOOK ORDINE NON VERIFICATO", det if f.verified else {**det, "notes": f.notes}) print(f" reconcile: pos ${newpos:,.0f}") if do_execute: ds = trader.ensure_disaster_sl(inst, sl_pct) # bracket su posizione NETTA (adatta long/short) print(f" disaster-SL: {ds.get('state')}" + (f" @ ${ds['stop']:,.1f}" if ds.get("stop") else "")) if ds.get("state") == "placed": notify("🛡️ BOOK disaster-SL piazzato", {"asset": asset, "stop": round(ds.get("stop") or 0, 1), "amount": round(ds.get("amount") or 0, 4)}) elif ds.get("state") == "place-failed": notify("⚠️ BOOK disaster-SL FALLITO", {"asset": asset, "notes": ds.get("notes")}) actions.append(act) print() if not do_execute: print(" => DRY-RUN: nessun ordine inviato." + ("" if enabled else " Per armare: config/live.json execution_enabled=true + --execute.")) elif all(x.startswith("HOLD") for x in actions): print(" => Nessuna azione: conto gia' al target netto del book.") else: print(" => Esecuzione completata (vedi data/live/book_executions.jsonl).") def main(): try: _run() except Exception as e: notify("🛑 BOOK LIVE — ERRORE", {"error": f"{type(e).__name__}: {e}"}) raise if __name__ == "__main__": main()