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,151 @@
|
||||
"""_real_close: cancel del disaster-SL (retry + alert orfano) e alert REAL_DIVERGENCE.
|
||||
|
||||
Usa un executor finto: nessuna rete, nessun ordine reale.
|
||||
"""
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from src.live.execution import Fill
|
||||
from src.live.strategy_worker import StrategyWorker
|
||||
|
||||
|
||||
class FakeExec:
|
||||
verify_polls = 1
|
||||
verify_sleep = 0.0
|
||||
disaster_sl_pct = 0.30
|
||||
|
||||
def __init__(self, cancel_responses):
|
||||
self.cancel_calls = []
|
||||
self._responses = list(cancel_responses)
|
||||
|
||||
def cancel_order(self, oid):
|
||||
self.cancel_calls.append(oid)
|
||||
return self._responses.pop(0) if self._responses else {"state": "error", "error": "boom"}
|
||||
|
||||
def resting_fills(self, instrument, oid):
|
||||
return 0.0, None, 0.0
|
||||
|
||||
def close_amount(self, instrument, side, amount, label=None):
|
||||
return Fill(instrument, "sell", 0.0, amount, 100.0, 0.0, 0.0,
|
||||
"oid-close", "filled", True, filled_amount=amount)
|
||||
|
||||
|
||||
class FakeFullExec(FakeExec):
|
||||
"""Aggiunge open/place_tp_limit/place_disaster_sl per testare il ciclo SH01 completo."""
|
||||
def __init__(self):
|
||||
super().__init__([{"state": "untriggered"}])
|
||||
self.tp_calls = 0
|
||||
self.dsl_calls = 0
|
||||
self.close_amounts = []
|
||||
|
||||
def open(self, instrument, side, notional, label=None):
|
||||
amt = round(notional / 100.0, 6)
|
||||
return Fill(instrument, side, notional, amt, 100.0, 0.0, 0.05, "oid-open", "filled", True)
|
||||
|
||||
def place_tp_limit(self, *a, **k):
|
||||
self.tp_calls += 1 # NON deve essere chiamato per SH01 (no TP)
|
||||
return Fill("x", "sell", 0.0, 0.0, None, 0.0, 0.0, None, None, False)
|
||||
|
||||
def place_disaster_sl(self, instrument, side, amount, stop, label=None):
|
||||
self.dsl_calls += 1
|
||||
return Fill(instrument, "sell", 0.0, amount, None, 0.0, 0.0, "dsl-1", "untriggered", True)
|
||||
|
||||
def close_amount(self, instrument, side, amount, label=None):
|
||||
self.close_amounts.append(amount)
|
||||
return Fill(instrument, "sell", 0.0, amount, 105.0, 0.0, 0.05, "oid-close",
|
||||
"filled", True, filled_amount=amount)
|
||||
|
||||
|
||||
def test_sh01_real_open_close_no_tp(tmp_path, monkeypatch):
|
||||
"""SH01 (no TP/SL, exit a orizzonte): apre reale, NON piazza TP, piazza disaster,
|
||||
chiude TUTTA la quota a market reduce-only."""
|
||||
from src.live.strategy_worker import StrategyWorker
|
||||
from src.strategies.base import Signal
|
||||
notified = []
|
||||
monkeypatch.setattr("src.live.strategy_worker.notify_event",
|
||||
lambda ev, data=None: notified.append(ev))
|
||||
fake = FakeFullExec()
|
||||
w = StrategyWorker(strategy=SimpleNamespace(name="SH01_shape_ml", fee_rt=0.001),
|
||||
asset="BTC", tf="1h", capital=100.0, position_size=0.5, leverage=2.0,
|
||||
data_dir=tmp_path, executor=fake, exec_instrument="BTC_USDC-PERPETUAL")
|
||||
w._open_position(Signal(idx=0, direction=1, entry_price=100.0,
|
||||
metadata={"max_bars": 12}), 100.0) # niente tp/sl
|
||||
assert w.real_in_position and fake.tp_calls == 0 # nessun TP per SH01
|
||||
assert fake.dsl_calls == 1 and w.real_dsl_order_id # disaster bracket sì
|
||||
amt = w.real_amount
|
||||
w._close_position(105.0, "time_limit")
|
||||
assert not w.real_in_position
|
||||
assert fake.close_amounts == [amt] # chiude TUTTA la quota a market
|
||||
|
||||
|
||||
def _worker(tmp_path, fake, monkeypatch, notified):
|
||||
monkeypatch.setattr("src.live.strategy_worker.notify_event",
|
||||
lambda ev, data=None: notified.append(ev))
|
||||
w = StrategyWorker(strategy=SimpleNamespace(name="FAKE", fee_rt=0.001),
|
||||
asset="BTC", tf="1h", capital=100.0, data_dir=tmp_path,
|
||||
executor=fake, exec_instrument="BTC_USDC-PERPETUAL")
|
||||
w.real_in_position = True
|
||||
w.real_side = "buy"
|
||||
w.real_amount = 0.001
|
||||
w.real_entry_price = 100.0
|
||||
w.real_entry_notional = 0.1
|
||||
w.real_dsl_order_id = "DSL-1"
|
||||
return w
|
||||
|
||||
|
||||
def test_dsl_cancel_untriggered_is_success(tmp_path, monkeypatch):
|
||||
notified = []
|
||||
fake = FakeExec([{"order_id": "DSL-1", "state": "untriggered"}])
|
||||
w = _worker(tmp_path, fake, monkeypatch, notified)
|
||||
w._real_close(100.0, "time_limit", 0.0)
|
||||
assert fake.cancel_calls == ["DSL-1"] # nessun retry
|
||||
assert "REAL_DSL_CANCEL_FAIL" not in notified
|
||||
assert w.real_dsl_order_id == ""
|
||||
|
||||
|
||||
def test_dsl_cancel_not_found_logs_but_no_alert(tmp_path, monkeypatch):
|
||||
# order_not_found = probabile trigger durante outage: log, NIENTE Telegram, no retry
|
||||
notified = []
|
||||
fake = FakeExec([{"order_id": "DSL-1", "state": "error", "error": "order_not_found"}])
|
||||
w = _worker(tmp_path, fake, monkeypatch, notified)
|
||||
w._real_close(100.0, "time_limit", 0.0)
|
||||
assert fake.cancel_calls == ["DSL-1"]
|
||||
assert "REAL_DSL_CANCEL_FAIL" not in notified
|
||||
|
||||
|
||||
def test_dsl_cancel_transient_error_retries_then_succeeds(tmp_path, monkeypatch):
|
||||
notified = []
|
||||
fake = FakeExec([{"state": "error", "error": "timeout"},
|
||||
{"order_id": "DSL-1", "state": "cancelled"}])
|
||||
w = _worker(tmp_path, fake, monkeypatch, notified)
|
||||
w._real_close(100.0, "time_limit", 0.0)
|
||||
assert fake.cancel_calls == ["DSL-1", "DSL-1"] # retry eseguito
|
||||
assert "REAL_DSL_CANCEL_FAIL" not in notified
|
||||
|
||||
|
||||
def test_dsl_cancel_persistent_error_alerts_orphan(tmp_path, monkeypatch):
|
||||
notified = []
|
||||
fake = FakeExec([{"state": "error", "error": "timeout"},
|
||||
{"state": "error", "error": "timeout"}])
|
||||
w = _worker(tmp_path, fake, monkeypatch, notified)
|
||||
w._real_close(100.0, "time_limit", 0.0)
|
||||
assert fake.cancel_calls == ["DSL-1", "DSL-1"]
|
||||
assert "REAL_DSL_CANCEL_FAIL" in notified # stop forse orfano -> Telegram
|
||||
|
||||
|
||||
def test_real_divergence_alert_on_close(tmp_path, monkeypatch):
|
||||
# fill reale 100.0 vs sim_exit 105.0 -> ~-476bps -> REAL_DIVERGENCE
|
||||
notified = []
|
||||
fake = FakeExec([{"order_id": "DSL-1", "state": "untriggered"}])
|
||||
w = _worker(tmp_path, fake, monkeypatch, notified)
|
||||
w._real_close(105.0, "take_profit", 0.5)
|
||||
assert "REAL_DIVERGENCE" in notified
|
||||
|
||||
|
||||
def test_no_divergence_alert_when_fill_matches(tmp_path, monkeypatch):
|
||||
notified = []
|
||||
fake = FakeExec([{"order_id": "DSL-1", "state": "untriggered"}])
|
||||
w = _worker(tmp_path, fake, monkeypatch, notified)
|
||||
w._real_close(100.05, "take_profit", 0.0) # ~-5bps: sotto soglia
|
||||
assert "REAL_DIVERGENCE" not in notified
|
||||
Reference in New Issue
Block a user