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>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user