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>
59 lines
2.5 KiB
Python
59 lines
2.5 KiB
Python
"""STALE_FEED (2026-06-05): alert quando il feed e' flat da >= 2 barre 1h complete,
|
|
e al risveglio col gap %. Solo osservabilita': nessun effetto sui worker."""
|
|
import pandas as pd
|
|
from src.portfolio.runner import _check_stale_feed
|
|
|
|
|
|
def _df(closes_completed, forming=None, price0=100.0):
|
|
"""Serie 1h: barre complete con i close dati (flat = ripete il precedente
|
|
con O=H=L=C), piu' eventuale candela in corso (timestamp = ora corrente)."""
|
|
end_ms = int(pd.Timestamp.now(tz="UTC").floor("h").timestamp() * 1000)
|
|
vals = list(closes_completed) + ([forming] if forming is not None else [])
|
|
n = len(vals)
|
|
rows = []
|
|
for j, v in enumerate(vals):
|
|
ts = end_ms - (n - 1 - j) * 3_600_000
|
|
rows.append({"timestamp": ts, "open": v, "high": v, "low": v, "close": v, "volume": 0.0})
|
|
# rendi NON-flat le barre che cambiano prezzo rispetto alla precedente
|
|
for j in range(1, n):
|
|
if rows[j]["close"] != rows[j - 1]["close"]:
|
|
rows[j]["high"] = rows[j]["close"] * 1.001
|
|
return pd.DataFrame(rows)
|
|
|
|
|
|
def _events(monkeypatch):
|
|
sent = []
|
|
import src.live.telegram_notifier as tn
|
|
monkeypatch.setattr(tn, "notify_event", lambda ev, data=None: sent.append((ev, data)))
|
|
return sent
|
|
|
|
|
|
def test_alert_after_two_flat_complete_bars(monkeypatch):
|
|
sent = _events(monkeypatch)
|
|
alerted = set()
|
|
closes = [100 + i for i in range(10)] + [110.0, 110.0, 110.0] # 3 flat complete
|
|
_check_stale_feed("ETH", _df(closes, forming=110.0), alerted)
|
|
assert "ETH" in alerted
|
|
assert sent and sent[0][0] == "STALE_FEED" and sent[0][1]["flat_bars_1h"] >= 2
|
|
|
|
|
|
def test_no_alert_on_live_feed(monkeypatch):
|
|
sent = _events(monkeypatch)
|
|
alerted = set()
|
|
closes = [100 + i for i in range(13)] # sempre in movimento
|
|
_check_stale_feed("ETH", _df(closes, forming=113.5), alerted)
|
|
assert not alerted and not sent
|
|
|
|
|
|
def test_recovery_notifies_gap_once(monkeypatch):
|
|
sent = _events(monkeypatch)
|
|
alerted = {"ETH"} # episodio in corso
|
|
closes = [100.0] * 10 + [96.6] # risveglio: gap -3.4%
|
|
_check_stale_feed("ETH", _df(closes, forming=96.5), alerted)
|
|
assert "ETH" not in alerted
|
|
assert sent and sent[-1][1]["status"] == "RIPRESO" and abs(sent[-1][1]["gap_pct"] + 3.4) < 0.1
|
|
# secondo poll: nessun doppio alert
|
|
sent.clear()
|
|
_check_stale_feed("ETH", _df(closes, forming=96.5), alerted)
|
|
assert not sent
|