feat(pairs): attiva ETH/BTC 15m flat-skip in PORT06 (BLEND, mezza size)
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>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user