"""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