Files
PythagorasGoal/scripts/analysis/reset_flatten.py
T
Adriano Dal Pastro 82c05f6f81 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>
2026-06-11 21:36:30 +00:00

69 lines
2.7 KiB
Python

"""Flatten one-shot del conto Deribit testnet per il RESET del portafoglio (2026-06-10).
Cancella gli ordini resting noti (TP limit / disaster-SL dai status.json dei worker),
poi chiude TUTTE le posizioni USDC con market reduce-only (close_position di cerbero
non supporta i lineari USDC) e verifica che il conto sia flat.
Uso: uv run python scripts/analysis/reset_flatten.py
"""
import glob
import json
import time
from src.live.cerbero_client import CerberoClient
from src.live.execution import ExecutionClient, contract_spec, register_contract
def main():
c = CerberoClient()
ex = ExecutionClient(client=c)
# 1) ordini resting noti dai status.json (TP limit + disaster bracket)
oids = set()
for f in glob.glob("data/portfolio_paper/*/status.json"):
s = json.load(open(f))
for k in ("real_tp_order_id", "real_dsl_order_id"):
if s.get(k):
oids.add(s[k])
for oid in sorted(oids):
r = ex.cancel_order(oid)
print(f"cancel {oid}: {r.get('state', r)}")
# 2) flatten posizioni USDC (market reduce-only, max 5 passate)
for attempt in range(5):
live = [p for p in c.get_positions("USDC")
if abs(float(p.get("size", 0) or 0)) > 0]
if not live:
print("FLAT — conto pulito")
return True
for p in live:
inst = p["instrument"]
register_contract(inst, c)
size_usd = abs(float(p["size"]))
mark = float(p.get("mark_price") or 0)
step = contract_spec(inst)["step"]
entry_side = "buy" if p["direction"] == "long" else "sell"
units = size_usd / mark if mark else 0
# _quantize_step (Decimal): round(units/step)*step in float binario
# produce artefatti (0.07200000000000001) che Deribit rifiuta con
# 'Invalid params' — il bug e' documentato in execution.py
from src.live.execution import _quantize_step
amount = _quantize_step(units, step, contract_spec(inst)["min"]) if units >= step / 2 else 0.0
if amount <= 0:
print(f" {inst}: residuo {size_usd:.2f} USD sotto mezzo step, ok")
continue
f = ex.close_amount(inst, entry_side, amount, label="reset_flatten")
print(f" close {inst} amt={amount} -> {f.order_state} fill={f.fill_price} "
f"verified={f.verified} {f.notes[:60]}")
time.sleep(3)
left = [(p["instrument"], round(float(p["size"]), 4))
for p in c.get_positions("USDC") if abs(float(p.get("size", 0) or 0)) > 0]
print("RESIDUO FINALE:", left or "FLAT")
return not left
if __name__ == "__main__":
ok = main()
raise SystemExit(0 if ok else 1)