d25d897fd1
Origine: gioco "Blind Traders" (100 agenti ciechi su BTC/ETH anonimizzati) -> vincitore = spread ETH/BTC reversion a 15m. Testato sul serio col gate PORT06: non duplicato (corr 1h vs 15m = 0.37), robusto (16/16 celle Sharpe>1), edge NON artefatto delle candele flat ETH 15m (filtrandole resta l'83% dello Sharpe). Percorso live costruito e validato: - pairs_research.pairs_sim_flat: engine generalizzato con exit LIVE-REALIZABLE (arma exit_ready, esce alla 1a barra pulita); regression-lock a pairs_sim. - PairsWorker: flat_skip + exit_ready + rilevamento flat da OHLC (1h byte-exact). - runner: fetch diretto dei timeframe sub-orari + override position_size per-sleeve. - validate_worker_pairs: replay worker == backtest a 15m (8452 vs 8453 trade). - _defs/build_everything: sleeve PR_ETHBTC_15M (mezza size, pos 0.10) -> PORT06 FULL 6.43->7.20, OOS 8.58->9.66, DD giu'. Rischio bilanciato col 1h. - smoke live: Cerbero serve candele 15m fresche; worker ticca. Diari docs/diary/2026-06-09-*. Caveat slippage: mezza size = blend-tilt prudente. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
3.4 KiB
Python
82 lines
3.4 KiB
Python
"""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()
|