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
+49 -2
View File
@@ -10,8 +10,9 @@ def _fill(inst, side, amount, filled, px, verified=True, state="filled", fee=0.0
f"oid-{side}-{filled}", state, verified, filled_amount=filled)
def _client_with(submits):
"""ExecutionClient con _submit finto: consuma la lista `submits` e registra le chiamate."""
def _client_with(submits, allowance=float("inf")):
"""ExecutionClient con _submit finto e guard del netting stubbato (il guard
reale legge conto+libri via HTTP/filesystem; qui si inietta il gap)."""
ec = ExecutionClient()
calls = []
@@ -21,6 +22,7 @@ def _client_with(submits):
return submits.pop(0)(instrument, side, amount)
ec._submit = fake_submit
ec._net_close_allowance = lambda *a, **k: allowance
return ec, calls
@@ -80,3 +82,48 @@ def test_net_leg_also_fails_reports_partial():
assert len(calls) == 2
assert not f.verified
assert abs(f.filled_amount - 0.05) < 1e-9 # solo il fillato vero nel ledger
def test_stale_state_guard_blocks_naked_order():
# quota gia' chiusa (DSL in outage / flatten manuale): reduce-only filla 0 e
# il gap conto-vs-libri e' 0 -> NESSUN ordine market nudo (code-review 2026-06-11)
ec, calls = _client_with([
lambda i, s, a: _fill(i, s, a, filled=0.0, px=None, verified=False, state="error", fee=0.0),
], allowance=0.0)
f = ec.close_amount(INST, "buy", 0.105, label="w1")
assert len(calls) == 1 # solo il reduce-only, niente fallback
assert not f.verified and f.filled_amount == 0.0
assert "netting NEGATO" in f.notes
def test_guard_unknown_fails_safe():
# guard non calcolabile (rete): FAIL-SAFE, niente ordine nudo
ec, calls = _client_with([
lambda i, s, a: _fill(i, s, a, filled=0.0, px=None, verified=False, state="error", fee=0.0),
], allowance=None)
f = ec.close_amount(INST, "buy", 0.105, label="w1")
assert len(calls) == 1
assert "non calcolabile" in f.notes
def test_guard_caps_residual_to_allowance():
# gap parziale: il residuo nudo e' cappato a quanto i libri giustificano
ec, calls = _client_with([
lambda i, s, a: _fill(i, s, a, filled=0.0, px=None, verified=False, state="error", fee=0.0),
lambda i, s, a: _fill(i, s, a, filled=a, px=1700.0),
], allowance=0.027)
f = ec.close_amount(INST, "sell", 0.105, label="w1")
assert len(calls) == 2
assert abs(calls[1]["amount"] - 0.027) < 1e-9
assert not f.verified # 0.027 < 0.105: chiusura parziale onesta
assert abs(f.filled_amount - 0.027) < 1e-9
def test_dust_residual_no_fallback():
# residuo da artefatto float (1e-17): NON deve diventare un lotto minimo nudo
ec, calls = _client_with([
lambda i, s, a: _fill(i, s, a, filled=a - 1e-17, px=1700.0),
])
f = ec.close_amount(INST, "sell", 0.105, label="w1")
assert len(calls) == 1
assert f.verified
+2 -1
View File
@@ -21,7 +21,8 @@ class FakePairsExec:
def _fill(self, inst, side, price, amt=1.0, ok=True):
return Fill(inst, side, 0.0, amt if ok else 0.0, price if ok else None,
0.0, 0.05 if ok else 0.0, "oid" if ok else None,
"filled" if ok else "error", ok)
"filled" if ok else "error", ok,
filled_amount=amt if ok else 0.0)
def open_pair(self, inst_a, inst_b, direction, notional, label=None):
side_a = "buy" if direction == 1 else "sell"
+2 -1
View File
@@ -134,7 +134,8 @@ class FakePairsExec:
self.px = dict(open_a=open_a, open_b=open_b, close_a=close_a, close_b=close_b)
def _fill(self, inst, side, amount, px):
return Fill(inst, side, 0.0, amount, px, 0.0, 0.02, "oid", "filled", True)
return Fill(inst, side, 0.0, amount, px, 0.0, 0.02, "oid", "filled", True,
filled_amount=amount)
def open_pair(self, inst_a, inst_b, direction, notional, label=None):
sa = "buy" if direction == 1 else "sell"