fix(exec): code-review serale — guard anti-posizione-nuda sul netting + verità per-frazione

7 finder paralleli sul diff della giornata (8adf388..HEAD), fix dei confermati:

CRITICI (money-path):
- close_amount: GUARD stato-stantio sul fallback netting — il residuo
  non-reduce-only e' consentito solo fino al gap (conto reale - libri degli
  altri worker - orfani) nella direzione della chiusura (src/live/books.py =
  fonte unica, usata anche dal reconciler). Senza guard, un close su stato
  stantio (DSL scattato in outage, flatten manuale) APRIVA una posizione nuda
  a taglia piena bookata come 'chiusura verificata'. Fail-safe se il gap non
  e' calcolabile. Check polvere PRIMA di _quantize_step (che clampa al lotto
  minimo: un residuo 1e-17 diventava un ordine nudo da un lotto).
- _real_close: market_amt = filled_amount anche a merged verified=False (i
  contratti chiusi dal reduce-only non si buttano se il leg netting fallisce);
  REAL_CLOSE_PARTIAL non piu' gateato su verified (era soppresso proprio nel
  caso parziale reale).
- pairs: verita' per-FRAZIONE di gamba (gross proporzionale al fillato, orfano
  = solo il residuo — prima falsava reconciler e real_capital della parte gia'
  chiusa); REAL_OPEN_PAIR booka filled_amount; docstring applied corretta.
- open_pair unwind: chiude il FILLATO, non il richiesto (senza il cap silenzioso
  del reduce-only avrebbe mangiato quota altrui).
- place_tp_limit: quantize CONSERVATIVO (sell=floor/buy=ceil) — il rounding al
  tick piu' vicino poteva mettere il resting oltre il TP sim -> tocco genuino
  classificato phantom sistematicamente.

ROBUSTEZZA/OSSERVABILITA':
- runner: WORKER_ERROR_STREAK a 5 e poi ogni 50 poll + recovery RIPRESO +
  flag real_in_position nell'alert (prima: one-shot a n==5, poi silenzio).
- _tp_phantom: TTL 120s sul verdetto (era ~50 HTTP/h per worker a barra
  fantasma); merge notes con entrambi gli order_id (audit-trail).
- reset_flatten: _quantize_step Decimal (round float produceva amount che
  Deribit rifiuta); hourly_report: book XS01 con gambe SHORT visibili (abs).
- validate_xsec_worker: POS/LEV importati (non hardcoded); xs01_tranche:
  regression-lock ESEGUITO vs xsec_sim; reconcile su src/live/books;
  drift_monitor: rolling vettoriale + exit code 1 su warn.

Test: +4 guard/dust, fixture filled_amount -> 114 passed.
Deferiti (TODO): resting esposti al netting, lifecycle orphan_legs, finestra
trade-history TP_PHANTOM, validazione feed a monte, dedup minori.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-11 21:36:30 +00:00
parent 3f1feb1a6f
commit 82c05f6f81
15 changed files with 321 additions and 96 deletions
+34 -13
View File
@@ -224,7 +224,9 @@ class PairsWorker:
self.real_in_position = True
self.real_dir = d
self.real_side_a, self.real_side_b = pf.leg_a.side, pf.leg_b.side
self.real_amount_a, self.real_amount_b = pf.leg_a.amount, pf.leg_b.amount
# amount FILLATO, non richiesto (coerente con strategy_worker, 2026-06-11)
self.real_amount_a = pf.leg_a.filled_amount or pf.leg_a.amount
self.real_amount_b = pf.leg_b.filled_amount or pf.leg_b.amount
self.real_entry_a = pf.leg_a.fill_price or sim_a
self.real_entry_b = pf.leg_b.fill_price or sim_b
self.real_notional_a = pf.leg_a.amount * self.real_entry_a
@@ -240,11 +242,12 @@ class PairsWorker:
def _real_close_pair(self, sim_a: float, sim_b: float, reason: str,
sim_pnl: float) -> tuple[float | None, bool]:
"""Chiusura REALE shadow: richiude entrambe le gambe reduce-only, riconcilia
PnL reale (per gamba) e fee, aggiorna il ledger reale parallelo.
"""Chiusura REALE shadow: richiude entrambe le gambe (netting-aware),
riconcilia PnL reale per-gamba e fee, aggiorna il ledger reale parallelo.
Ritorna (real_pnl, applied): applied=True se almeno una gamba ha chiuso con
fill verificato (PnL reale utilizzabile come verita' in real-truth)."""
Ritorna (real_pnl, applied): applied=True SOLO se ENTRAMBE le gambe hanno
chiuso per intero con fill verificato — con una gamba orfana il "PnL dello
spread" non esiste e real-truth ricade sul sim DICHIARATO."""
if not self.real_in_position:
return None, False
pf = self.executor.close_pair(self.inst_a, self.inst_b, self.real_side_a,
@@ -257,12 +260,25 @@ class PairsWorker:
# reale (3 PnL fantasma il 2026-06-11, gamba ETH orfana sul conto).
# Ora: si booka SOLO il realizzato delle gambe con fill verificato; la gamba
# respinta diventa un ORFANO registrato (persistito) + alert Telegram.
ok_a, ok_b = bool(pf.leg_a.verified), bool(pf.leg_b.verified)
from src.live.execution import contract_spec
for leg in (pf.leg_a, pf.leg_b):
if "netting" in (getattr(leg, "notes", "") or ""):
# reduce-only cappato/respinto, residuo in market puro (v1.1.25)
self._log("NET_CLOSE", {"instrument": leg.instrument, "note": leg.notes})
self._notify("NET_CLOSE", {"instrument": leg.instrument, "note": leg.notes})
# verita' per-FRAZIONE di gamba (code-review 2026-06-11): una gamba puo'
# chiudere PARZIALMENTE (reduce-only cappato + netting negato/fallito) —
# si booka il gross della sola frazione FILLATA e l'orfano registra il
# solo RESIDUO (prima: gross binario tutto-o-niente e orfano a amount
# pieno, che falsava reconciler e real_capital della parte gia' chiusa).
filled_a = min(getattr(pf.leg_a, "filled_amount", 0.0), self.real_amount_a)
filled_b = min(getattr(pf.leg_b, "filled_amount", 0.0), self.real_amount_b)
step_a = contract_spec(self.inst_a).get("step", 0.001)
step_b = contract_spec(self.inst_b).get("step", 0.001)
ok_a = filled_a >= self.real_amount_a - step_a / 2
ok_b = filled_b >= self.real_amount_b - step_b / 2
frac_a = filled_a / self.real_amount_a if self.real_amount_a else 0.0
frac_b = filled_b / self.real_amount_b if self.real_amount_b else 0.0
exit_a = pf.leg_a.fill_price or sim_a
exit_b = pf.leg_b.fill_price or sim_b
# PnL per gamba: dir A = +d (long ratio compra A), dir B = -d
@@ -270,26 +286,31 @@ class PairsWorker:
gross_a = da * (exit_a - self.real_entry_a) / self.real_entry_a * self.real_notional_a
gross_b = db * (exit_b - self.real_entry_b) / self.real_entry_b * self.real_notional_b
exit_fee = pf.leg_a.fee_usd + pf.leg_b.fee_usd
real_pnl = ((gross_a if ok_a else 0.0) + (gross_b if ok_b else 0.0)
real_pnl = (gross_a * frac_a + gross_b * frac_b
- self.real_entry_fee - exit_fee)
self.real_capital += real_pnl
self.real_trades += 1
self._log("REAL_CLOSE_PAIR", {
"reason": reason, "exit_a": exit_a, "exit_b": exit_b,
"leg_a_ok": ok_a, "leg_b_ok": ok_b,
"filled_a": filled_a, "filled_b": filled_b,
"real_pnl_usd": round(real_pnl, 4), "sim_pnl_usd": round(sim_pnl, 4),
"entry_fee": round(self.real_entry_fee, 5), "exit_fee": round(exit_fee, 5),
"real_capital": round(self.real_capital, 4), "verified": pf.verified})
for ok, inst, side, amt in ((ok_a, self.inst_a, self.real_side_a, self.real_amount_a),
(ok_b, self.inst_b, self.real_side_b, self.real_amount_b)):
if not ok and amt > 0:
orphan = {"instrument": inst, "entry_side": side, "amount": amt,
for ok, inst, side, amt, filled, step in (
(ok_a, self.inst_a, self.real_side_a, self.real_amount_a, filled_a, step_a),
(ok_b, self.inst_b, self.real_side_b, self.real_amount_b, filled_b, step_b)):
residue = amt - filled
if not ok and residue >= step / 2:
orphan = {"instrument": inst, "entry_side": side,
"amount": round(residue, 8),
"ts": datetime.now(timezone.utc).isoformat(), "reason": reason}
self.orphan_legs.append(orphan)
self._notify("PAIR_LEG_ORPHAN", {
"worker": self.worker_id, **orphan,
"note": ("gamba NON chiusa (reduce-only respinto dal netting di "
"conto?): posizione orfana sul conto, intervento richiesto")})
"note": ("gamba NON chiusa per il residuo indicato (netting "
"negato/fallito): posizione orfana sul conto"
"risolvere e RIMUOVERE l'orfano dallo status")})
self.real_in_position = False
self.real_dir = 0
self.real_side_a = self.real_side_b = ""