14522262e6
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>
87 lines
3.3 KiB
Python
87 lines
3.3 KiB
Python
"""Circuit-breaker venue-lock (2026-06-12): dopo lock_trip reject 'locked'
|
|
consecutivi le APERTURE sono sospese senza toccare l'API (i worker seguono il
|
|
path REAL_OPEN_FAIL/sim_fallback); le chiusure si tentano sempre; il probe a
|
|
fine cooldown riarma o resetta. Nessuna rete: CerberoClient stubbato."""
|
|
import time
|
|
|
|
from src.live.execution import ExecutionClient
|
|
|
|
|
|
class _Venue:
|
|
"""Stub CerberoClient: locked finche' .locked=True, poi ordini ok."""
|
|
def __init__(self):
|
|
self.locked = True
|
|
self.place_calls = 0
|
|
|
|
def place_order(self, instrument, side, amount, order_type="market",
|
|
price=None, label=None, reduce_only=False):
|
|
self.place_calls += 1
|
|
if self.locked:
|
|
return {"state": "error", "error": "locked_by_admin"}
|
|
return {"order": {"order_id": "o1", "order_state": "filled",
|
|
"filled_amount": amount, "average_price": 100.0},
|
|
"trades": [{"amount": amount, "price": 100.0, "fee": 0.0}]}
|
|
|
|
def get_ticker(self, instrument):
|
|
return {"mark_price": 100.0}
|
|
|
|
def get_trade_history(self, limit=50, instrument_name=None):
|
|
return []
|
|
|
|
|
|
def _ec(monkeypatch, sent):
|
|
monkeypatch.setattr("src.live.telegram_notifier.notify_event",
|
|
lambda ev, data: sent.append((ev, data)))
|
|
venue = _Venue()
|
|
ec = ExecutionClient(client=venue)
|
|
return ec, venue
|
|
|
|
|
|
INST = "ETH_USDC-PERPETUAL"
|
|
|
|
|
|
def test_breaker_trips_and_blocks_opens(monkeypatch):
|
|
sent = []
|
|
ec, venue = _ec(monkeypatch, sent)
|
|
for _ in range(ec.lock_trip):
|
|
f = ec.open(INST, "buy", 50.0)
|
|
assert not f.verified
|
|
assert venue.place_calls == ec.lock_trip and ec.lock_blocked()
|
|
assert [e for e, _ in sent] == ["VENUE_LOCK"] # un alert al trip
|
|
# aperture sospese: NESSUNA chiamata API, nota dedicata
|
|
f = ec.open(INST, "buy", 50.0)
|
|
assert venue.place_calls == ec.lock_trip
|
|
assert "venue_lock_breaker" in f.notes
|
|
assert sent == sent[:1] # niente spam
|
|
|
|
|
|
def test_closes_still_attempted_and_refresh_cooldown(monkeypatch):
|
|
sent = []
|
|
ec, venue = _ec(monkeypatch, sent)
|
|
for _ in range(ec.lock_trip):
|
|
ec.open(INST, "buy", 50.0)
|
|
ec._net_close_allowance = lambda *a, **k: 0.0 # niente fallback netting
|
|
before = ec._lock_until
|
|
time.sleep(0.01)
|
|
f = ec.close_amount(INST, "buy", 0.5, label="w1") # la chiusura PASSA dall'API
|
|
assert venue.place_calls == ec.lock_trip + 1
|
|
assert not f.verified
|
|
assert ec._lock_until > before # locked dal close -> cooldown esteso
|
|
|
|
|
|
def test_probe_rearms_or_resets(monkeypatch):
|
|
sent = []
|
|
ec, venue = _ec(monkeypatch, sent)
|
|
for _ in range(ec.lock_trip):
|
|
ec.open(INST, "buy", 50.0)
|
|
# cooldown scaduto + venue ancora locked -> probe fallisce e riscatta
|
|
ec._lock_until = time.monotonic() - 1
|
|
ec.open(INST, "buy", 50.0)
|
|
assert venue.place_calls == ec.lock_trip + 1 and ec.lock_blocked()
|
|
# venue sbloccato -> il probe passa, breaker resettato, alert di rientro
|
|
ec._lock_until = time.monotonic() - 1
|
|
venue.locked = False
|
|
f = ec.open(INST, "buy", 50.0)
|
|
assert f.verified and not ec.lock_blocked() and ec._lock_streak == 0
|
|
assert sent[-1][0] == "VENUE_LOCK" and sent[-1][1].get("status") == "RIENTRATO"
|