Files
PythagorasGoal/scripts/live/live_execute.py
T
Adriano Dal Pastro de83909db9 un flag sconosciuto non e' l'azione di default: guardia su 18 script, indice USDE orario, pulizia
MISURATO oggi durante la revisione: `trades_db.py --help` non stampava l'uso, cadeva in sync()
e riscriveva meta.ultimo_sync. Nessuno dei 18 script di scripts/live/ usava argparse: un flag
sbagliato era il ramo else. Su journal.py avrebbe scritto pagina e riga di DB, su analista.py
avrebbe speso una chiamata al modello e mandato un Telegram.

- src/live/cli.valida: prima istruzione di ogni __main__, prima di connect()/sync/rete.
  --help -> 0 con l'uso; flag ignoto, valore mancante o posizionale -> 2 con l'elenco dei
  previsti (P4). NIENTE argparse: cambierebbe messaggi, codici d'uscita e --help di script
  che il cron gia' chiama.
- 18 script cablati (i 3 che scrivono + 14 + cc01), flag invariati.
- tests/test_cli_flag.py (30): elenco DERIVATO dalla cartella (P1), valida come prima
  istruzione, uso che documenta i flag, e i flag che il CRON usa davvero restano accettati
  (P15/P16); end-to-end su --help e flag ignoto con trades.db non toccato (M15).
  Verificato a mano: monitor_health --quiet, trades_db --sync --quiet, book_execute dry-run.

Debito §5.15, primo passo: balance_watch (orario) registra `usde_usdc` a ogni campione — None
con la ragione se illeggibile, mai 1,0. Il cablaggio nel bound quando la serie ha storia.

Pulizia dalla revisione: tests/helpers.carica_script al posto della 15a copia del loader
importlib (5 file del libro live); il fill di prova via upsert_fills invece di un INSERT che
lasciava verified NULL; asserzioni non ancorate al padding; movimenti_capitale accetta le
righe gia' lette (una SELECT invece di due ai due lati di una scrittura del cron).

Test 910 verdi (+52). Diario 2026-09-02c.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDJsH3iDSaBns3ccpPBwiu
2026-09-02 15:49:40 +00:00

151 lines
6.6 KiB
Python

"""TP01 LIVE EXECUTE — loop di esecuzione GATED su Deribit mainnet (USDC linear).
Porta il conto reale al target di TP01 (causale, dati certificati): per ogni asset calcola il notional
bersaglio = min(0.5 * frazione * equity, max_notional), e apre/riduce/chiude per raggiungerlo.
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/executions.jsonl. TP01 oggi e' FLAT -> target 0 -> nessuna azione finche' il segnale non gira.
uv run python scripts/live/live_execute.py # DRY-RUN (piano, nessun ordine)
uv run python scripts/live/live_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.deribit import INSTRUMENT
from src.live.execution import DeribitTrader
from src.live.notifier import notify
from src.live.shadow import ASSETS, WEIGHT, shadow_report
CONFIG = PROJECT_ROOT / "config" / "live.json"
LOG_DIR = PROJECT_ROOT / "data" / "live"
LOG = LOG_DIR / "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
max_notional = float(cfg["max_notional_per_asset_usd"])
min_order = float(cfg["min_order_usd"])
sl_pct = float(cfg["disaster_sl_pct"])
r = shadow_report() # targets causali + conto/posizioni reali (online)
equity = r["equity"]
print("=" * 84)
print(" TP01 LIVE EXECUTE — Deribit mainnet (USDC linear)")
print("=" * 84)
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 ${max_notional:.0f} | min ${min_order:.0f} | disaster-SL -{sl_pct*100:.0f}%")
print(f" ultima barra : {r['last_data']}\n")
if not r["online"]:
print(" conto non leggibile (offline) -> stop, non eseguo a cieco.")
if do_execute:
notify("⚠️ TP01 LIVE — conto offline", {"nota": "salto l'esecuzione, non opero a cieco"})
return
trader = DeribitTrader() if do_execute else None
actions = []
for a in r["assets"]:
asset = a["asset"]; frac = a["target"]; mark = a["mark"]; cur = a["position_usd"]
tgt = min(WEIGHT * frac * equity, max_notional) if frac > 0 else 0.0
delta = tgt - cur
if abs(delta) < min_order:
act = "HOLD (a target)"
elif tgt < 1.0 and cur > 1.0:
act = f"CLOSE ${cur:,.0f}"
elif delta > 0:
act = f"BUY ${delta:,.0f}"
else:
act = f"REDUCE ${-delta:,.0f}"
print(f" {asset:<3} target {frac:+.3f}x -> ${tgt:,.0f} | pos ${cur:,.0f} | delta ${delta:+,.0f} -> {act}")
if do_execute:
if not act.startswith("HOLD"):
fills = trader.rebalance_to(INSTRUMENT[asset], tgt, mark, min_usd=min_order)
newpos = trader.position_usd(INSTRUMENT[asset])
for f in fills:
print(f" -> {f.side.upper()} {f.filled:.4f} @ ${f.price:,.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, pos_after=newpos))
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), pos_after=round(newpos, 0))
if f.verified:
notify(f"✅ TP01 {act}", det)
else:
notify("⚠️ TP01 ORDINE NON VERIFICATO", {**det, "notes": f.notes})
print(f" reconcile: pos ${newpos:,.0f}")
ds = trader.ensure_disaster_sl(INSTRUMENT[asset], sl_pct) # bracket: piazza se long, pulisce se flat
print(f" disaster-SL: {ds.get('state')}" + (f" @ ${ds['stop']:,.1f}" if ds.get("stop") else ""))
if ds.get("state") == "placed":
notify("🛡️ TP01 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("⚠️ TP01 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 di TP01 (oggi flat).")
else:
print(" => Esecuzione completata (vedi data/live/executions.jsonl).")
def main():
try:
_run()
except Exception as e:
notify("🛑 TP01 LIVE — ERRORE", {"error": f"{type(e).__name__}: {e}"})
raise
USO = """uso: live_execute.py [--execute]
uv run python scripts/live/live_execute.py # DRY-RUN (piano, nessun ordine)
uv run python scripts/live/live_execute.py --execute # esegue SOLO se execution_enabled=true
Dettaglio nel docstring in testa al file."""
if __name__ == "__main__":
from src.live.cli import valida
valida("live_execute.py", USO, flag=("--execute",), con_valore=())
main()