Files
PythagorasGoal/Old/tests/portfolio/test_pairs_execution.py
Adriano Dal Pastro 14522262e6 chore(reset): v2.0.0 — storico certificato Deribit mainnet, ripartenza pulita
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera
libreria "validata OOS" era artefatto di feed contaminato (print fantasma del
feed Cerbero TESTNET + storico Binance/USDT).

- Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e
  CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia
  (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample
  (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE
  50-82% barre flat; XRP/BNB non certificabili).
- Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni
  portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST
  con segnale residuo, da ri-validare in isolamento.
- Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio,
  runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/
  portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/
  (preservati, non cancellati). Diario consolidato in un unico documento.
- Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal +
  src/backtest/engine + load_data; tool dati certificati (rebuild_history,
  certify_feed, audit_feed, multi_source_check).
- Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico
  (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 15:20:59 +00:00

102 lines
4.4 KiB
Python

"""PairsWorker esecuzione reale a 2 gambe (shadow): apertura/chiusura, leg-risk unwind,
ledger reale parallelo. Executor finto, nessuna rete."""
from types import SimpleNamespace
import numpy as np
import pandas as pd
import pytest
from src.live.execution import Fill, PairFill
from src.live.pairs_worker import PairsWorker
class FakePairsExec:
"""Simula PairsExecutionClient: fill deterministici, con possibilità di leg-fail."""
def __init__(self, fail_leg=None):
self.fail_leg = fail_leg # None | 'a' | 'b' : quale gamba NON filla all'open
self.opened = []
self.closed = []
self.unwound = []
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_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"
side_b = "sell" if direction == 1 else "buy"
ok_a = self.fail_leg != 'a'
ok_b = self.fail_leg != 'b'
fa = self._fill(inst_a, side_a, 100.0, ok=ok_a)
fb = self._fill(inst_b, side_b, 50.0, ok=ok_b)
self.opened.append((inst_a, inst_b, direction))
if ok_a and ok_b:
return PairFill(True, fa, fb)
if ok_a and fa.amount > 0:
self.unwound.append(inst_a)
if ok_b and fb.amount > 0:
self.unwound.append(inst_b)
return PairFill(False, fa, fb, unwound=bool(self.unwound), notes="leg-fail")
def close_pair(self, inst_a, inst_b, side_a, side_b, amt_a, amt_b, label=None):
opp_a = "sell" if side_a == "buy" else "buy"
opp_b = "sell" if side_b == "buy" else "buy"
self.closed.append((inst_a, inst_b))
# prezzi di uscita: A salito a 102, B fermo a 50 -> long-A/short-B guadagna
return PairFill(True, self._fill(inst_a, opp_a, 102.0, amt_a),
self._fill(inst_b, opp_b, 50.0, amt_b))
INST = {"ETH": "ETH_USDC-PERPETUAL", "BTC": "BTC_USDC-PERPETUAL"}
def _worker(tmp_path, fake, monkeypatch, notified=None):
if notified is not None:
monkeypatch.setattr("src.live.pairs_worker.notify_event",
lambda ev, data=None: notified.append(ev))
return PairsWorker("ETH", "BTC", "1h", capital=200.0, position_size=0.2, leverage=2.0,
data_dir=tmp_path, executor=fake, exec_instruments=INST)
def test_execution_enabled_wired(tmp_path):
w = _worker(tmp_path, FakePairsExec(), pytest.MonkeyPatch())
assert w.execution_enabled and w.inst_a == "ETH_USDC-PERPETUAL" and w.inst_b == "BTC_USDC-PERPETUAL"
def test_real_open_and_close_pair(tmp_path, monkeypatch):
notified = []
fake = FakePairsExec()
w = _worker(tmp_path, fake, monkeypatch, notified)
w._open(1, 100.0, 50.0, -2.5) # long ratio
assert w.real_in_position and w.real_dir == 1
assert w.real_side_a == "buy" and w.real_side_b == "sell"
assert fake.opened and "REAL_EXEC_LIVE" in notified
cap0 = w.real_capital
w._close(102.0, 50.0, 0.3, "mean_revert")
assert not w.real_in_position and fake.closed
assert w.real_capital > cap0 # A +2% / B 0 -> long-A/short-B in utile
assert w.real_trades == 1
def test_leg_fail_unwinds_and_no_position(tmp_path, monkeypatch):
notified = []
fake = FakePairsExec(fail_leg='b') # gamba B non filla
w = _worker(tmp_path, fake, monkeypatch, notified)
w._open(1, 100.0, 50.0, -2.5)
assert not w.real_in_position # niente posizione reale
assert "ETH_USDC-PERPETUAL" in fake.unwound # la gamba A fillata e' stata richiusa
assert "REAL_OPEN_FAIL" in notified
def test_real_ledger_persists_and_resumes(tmp_path, monkeypatch):
fake = FakePairsExec()
w = _worker(tmp_path, fake, monkeypatch, [])
w._open(-1, 100.0, 50.0, 2.5) # short ratio
assert w.real_in_position and w.real_dir == -1
w2 = PairsWorker("ETH", "BTC", "1h", capital=200.0, position_size=0.2, leverage=2.0,
data_dir=tmp_path, executor=fake, exec_instruments=INST)
assert w2.real_in_position and w2.real_dir == -1
assert w2.real_side_a == "sell" and w2.real_amount_a > 0