"""Smoke ESECUZIONE REALE a 2 gambe (PairsExecutionClient) su Deribit testnet. Apre e chiude una posizione pair micro (ETH/BTC lineari USDC) come farebbe il PairsWorker, esercitando: open_pair (2 market simultanei long A/short B), verifica per-gamba, close_pair (2 reduce-only), riconciliazione PnL/fee a 2 gambe. Verifica finale: conto flat su ENTRAMBI gli strumenti. Costo testnet = €0. Non tocca lo stato di produzione (data_dir temporanea). uv run python scripts/analysis/live_pairs_smoke.py """ from __future__ import annotations import tempfile from pathlib import Path from src.live.cerbero_client import CerberoClient from src.live.execution import ExecutionClient, PairsExecutionClient from src.live.pairs_worker import PairsWorker def main() -> None: client = CerberoClient() acct = client.get_account_summary(currency="USDC") print(f"Account testnet equity={acct.get('equity')} USDC testnet={acct.get('testnet')}") if not acct.get("testnet"): raise SystemExit("ABORT: non testnet") pex = PairsExecutionClient(leg=ExecutionClient(client=client)) inst = {"ETH": "ETH_USDC-PERPETUAL", "BTC": "BTC_USDC-PERPETUAL"} pex.ensure_specs(*inst.values()) print(f"strumenti: {inst}") # GUARDIA: questo conto e' condiviso col runner di produzione (fade reali su # BTC/ETH_USDC). Se ci sono posizioni aperte, ABORT: lo smoke userebbe lo stesso # strumento e la verifica "conto flat" sarebbe falsata (e un eventuale # close_position flatterebbe le quote del runner). Mai eseguire close_position qui. for i in inst.values(): sz = pex.leg._position_size(i) if sz != 0: raise SystemExit(f"ABORT: posizione di produzione aperta su {i} (size={sz}). " f"Eseguire lo smoke solo a conto flat.") with tempfile.TemporaryDirectory() as tmp: # capitale piccolo -> notional micro per gamba (capital*pos*lev) w = PairsWorker("ETH", "BTC", "1h", capital=60.0, position_size=0.15, leverage=2.0, data_dir=Path(tmp), executor=pex, exec_instruments=inst) print(f"execution_enabled={w.execution_enabled} notional/gamba atteso=${60*0.15*2:.0f}") # prezzi sim plausibili (il ramo sim del worker li usa per il suo ledger; # il reale usa i fill veri). Niente zeri -> niente divisione per zero. pa = pex.leg._mark_price(inst["ETH"]) or 1668.0 pb = pex.leg._mark_price(inst["BTC"]) or 63000.0 print("\n[A] OPEN long ratio (long ETH / short BTC)") w._open(1, pa, pb, -2.5) print(f" real_in_position={w.real_in_position} dir={w.real_dir}") print(f" gamba A {w.real_side_a} {w.real_amount_a} @ {w.real_entry_a} (notional ${w.real_notional_a:.2f})") print(f" gamba B {w.real_side_b} {w.real_amount_b} @ {w.real_entry_b} (notional ${w.real_notional_b:.2f})") print(f" entry_fee ${w.real_entry_fee:.5f}") assert w.real_in_position, "open pair non verificato" assert w.real_side_a == "buy" and w.real_side_b == "sell" print("\n[B] CLOSE (richiude entrambe le gambe reduce-only)") cap0 = w.real_capital pa2 = pex.leg._mark_price(inst["ETH"]) or pa pb2 = pex.leg._mark_price(inst["BTC"]) or pb w._close(pa2, pb2, 0.3, "mean_revert") print(f" real_capital {cap0:.4f} -> {w.real_capital:.4f} (Δ {w.real_capital-cap0:+.4f}) trades={w.real_trades}") assert not w.real_in_position, "close pair non azzera la posizione" # verifica conto flat su entrambi gli strumenti pa = pex.leg._position_size(inst["ETH"]) pb = pex.leg._position_size(inst["BTC"]) print(f"\n posizione netta ETH={pa} BTC={pb}") print("✓ catena pairs OK — open 2 gambe verificate, close 2 gambe, fee reali, conto flat" if pa == 0 and pb == 0 else f"⚠ residuo sul book (ETH={pa} BTC={pb}) — verificare manualmente") if __name__ == "__main__": main()