c655604131
PairsExecutionClient (compone ExecutionClient): open_pair (2 market long A/short B, verifica per-gamba, LEG-RISK unwind se una sola filla), close_pair (2 reduce-only via close_amount, MAI close_position), register_contract (fetch spec USDC). Spec LTC/ADA/SOL aggiunti. PairsWorker: ledger reale shadow a 2 gambe resume-safe (_real_open_pair/ _real_close_pair, PnL per gamba dir A=+d/B=-d, doppio arrotondamento riportato). Runner: pairs_executor gated su execution.pairs_enabled (false di default). Validazione: test 92/92 (open/close, leg-risk unwind, resume) + smoke testnet end-to-end (open 2 gambe verificate, close reduce-only, PnL reale -0.039 vs sim -0.036, conto flat). Smoke ora aborta se ci sono posizioni di produzione + usa solo close_amount. NB incidente testnet documentato (diario): pulizia manuale con close_position ha flattato le quote shadow dei fade sul conto condiviso -> auto-riconciliazione al prossimo close. Lezione: mai close_position su strumenti condivisi. pairs_enabled resta FALSE: accendere con finestra a conto flat + osservazione. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
3.9 KiB
Python
82 lines
3.9 KiB
Python
"""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()
|