"""Smoke LIVE del nuovo percorso 15m: fetch DIRETTO 15m da Cerbero per ETH/BTC + freschezza + flat-fraction + un tick reale del PairsWorker(flat_skip). Verifica cio' che il backtest non vede: che Cerbero serva candele 15m fresche per entrambe le gambe (il runner ora le fetcha dirette, non resamplate dal 1h) e che il worker 15m le processi senza errori. NON apre ordini reali (l'esecuzione a 2 gambe e' gia' coperta da live_pairs_smoke.py, indipendente dal timeframe). uv run python scripts/analysis/pairs15m_live_smoke.py """ from __future__ import annotations import sys import shutil import tempfile from datetime import datetime, timezone, timedelta from pathlib import Path import pandas as pd PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT)) from src.live.cerbero_client import CerberoClient from src.live.multi_runner import INSTRUMENT_MAP from src.live.pairs_worker import PairsWorker CFG = {"n": 66, "z_in": 1.674, "z_exit": 1.0, "max_bars": 35, "flat_skip": True} def fetch15(cli, asset, days=14): inst = INSTRUMENT_MAP.get(asset, f"{asset}-PERPETUAL") end = datetime.now(timezone.utc) start = end - timedelta(days=days) candles = cli.get_historical_v2(inst, start.strftime("%Y-%m-%d"), end.strftime("%Y-%m-%d"), "15m") if not candles: return inst, None df = pd.DataFrame(candles) df["timestamp"] = df["timestamp"].astype("int64") return inst, df.sort_values("timestamp").reset_index(drop=True) def main(): print("=" * 84) print(" SMOKE LIVE — ETH/BTC pairs 15m (fetch diretto Cerbero + tick worker flat-skip)") print("=" * 84) cli = CerberoClient() inst_a, da = fetch15(cli, "ETH") inst_b, db = fetch15(cli, "BTC") ok = True for asset, inst, df in [("ETH", inst_a, da), ("BTC", inst_b, db)]: if df is None or df.empty: print(f" {asset} ({inst}): NESSUNA candela 15m -> FAIL"); ok = False; continue last = pd.to_datetime(df["timestamp"].iloc[-1], unit="ms", utc=True) age_min = (datetime.now(timezone.utc) - last).total_seconds() / 60 flat = ((df["open"] == df["high"]) & (df["high"] == df["low"]) & (df["low"] == df["close"])).mean() * 100 fresh = age_min < 60 print(f" {asset} ({inst}): {len(df)} barre 15m | ultima {last:%Y-%m-%d %H:%M} " f"({age_min:.0f} min fa, {'FRESCO' if fresh else 'STALE'}) | flat {flat:.1f}%") ok &= fresh if da is None or db is None: print("\n ESITO: FAIL (feed 15m assente)."); return # tick reale del worker 15m tmp = Path(tempfile.mkdtemp(prefix="smoke15m_")) try: w = PairsWorker("ETH", "BTC", "15m", params=CFG, fee_rt=0.001, data_dir=tmp) df_a = pd.DataFrame({"timestamp": da["timestamp"], "open": da["open"], "high": da["high"], "low": da["low"], "close": da["close"]}) df_b = pd.DataFrame({"timestamp": db["timestamp"], "open": db["open"], "high": db["high"], "low": db["low"], "close": db["close"]}) w.tick(df_a, df_b) print(f"\n Worker 15m flat_skip={w.flat_skip} -> tick OK | {w.status_summary}") print(f" ESITO: {'OK — feed 15m fresco e worker ticca' if ok else 'ATTENZIONE: feed 15m stale/parziale'}") finally: shutil.rmtree(tmp, ignore_errors=True) if __name__ == "__main__": main()