"""FADE TF SWEEP — i fade MR01/02/07 su 1m/2m/5m/10m/30m (oltre a 15m live e 1h). Ricerca 2026-06-12 (post-swap 15m). Diario: docs/diary/2026-06-12-fade-tf-sweep.md. Due banchi di prova: A. STORIA COMPLETA (parquet locale; 10m = resample dal 5m): engine canonico build_trades, daily equity su IDX comune, OOS da 2024-10, fee 0.10% e 2x. B. FINESTRA COMUNE RECENTE (2026-02-12 -> 06-12): 1m/2m (fetch Cerbero, non esiste storia locale 1m: il refresh la esclude per costo) vs 5m/15m sugli STESSI 120 giorni — confronto apples-to-apples sul regime corrente. Esiti: - La frontiera Sharpe e' MONOTONA al scendere del tf per MR01/MR07 (full history OOS: 5m > 10m > 15m > 30m > 1h)... ma il margine fee si assottiglia insieme: a fee 2x MR02_BTC muore a 5m (-1.70) e resta fragile a 10m (0.32). - MR02 (donchian, 3-6x i trade degli altri) sotto i 15m muore di fee nel regime corrente: 1m -64%, 2m -44%, 5m -22% sulla finestra recente. - 1m/2m: SCARTATI. MR01 a 1m brilla sulla finestra recente (ETH +60%, Sh 5.7) ma muore a fee 2x, il flat-share 1m e' alto (ETH 25.6%, BTC 13.3% -> rischio stale-print) e la validazione full-history e' impraticabile (storia 1m non mantenuta). Il regime recente e' CALMO: anche il 5m vi e' fiacco — i tf veloci pagano nella volatilita', non nella calma. - 10m: il miglior candidato OLTRE il 15m (quasi l'edge del 5m con piu' margine fee; corr daily col 15m live 0.53 media). Eventuale ADD da gateare in futuro, NON ora: il 15m e' appena andato live (v1.1.30), un cambio alla volta. - VERDETTO: tenere il 15m (ginocchio della frontiera margine-fee/rendimento); 10m in watchlist; 1m/2m chiusi; 5m no-swap (fee-fragile su MR02_BTC). uv run python scripts/analysis/fade_tf_sweep.py """ from __future__ import annotations import sys from pathlib import Path import numpy as np import pandas as pd PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT)) from src.data.downloader import load_data from scripts.analysis.risk_management import strats_for, build_trades, INIT, POS from scripts.analysis.combine_portfolio import IDX, SPLIT, _norm, metrics EPOCH = pd.Timestamp(0, tz="UTC") WINDOW_START = "2026-02-12" # finestra comune del banco B RECENT_1M = {a: Path(f"/tmp/{a.lower()}_1m_recent.parquet") for a in ("BTC", "ETH")} def resample_ohlcv(df: pd.DataFrame, minutes: int) -> pd.DataFrame: """Resample OHLCV unit-safe (pandas 2.x conserva datetime64[ms]: niente aritmetica diretta su .view int64 — il //10**6 doppio manda i ts nel 1970).""" ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True) g = df.set_index(ts).resample(f"{minutes}min") out = pd.DataFrame({"open": g["open"].first(), "high": g["high"].max(), "low": g["low"].min(), "close": g["close"].last(), "volume": g["volume"].sum()}).dropna() out["timestamp"] = (out.index - EPOCH) // pd.Timedelta(milliseconds=1) return out.reset_index(drop=True) def daily_eq(df: pd.DataFrame, fn, params, fee_rt: float = 0.001) -> tuple[pd.Series, int]: ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True) trades = build_trades(fn(df, **params), df, fee_rt=fee_rt, trend_max=3.0) n = len(df) eq = np.full(n, INIT) cap = INIT for i, j, ret in sorted(trades, key=lambda t: t[1]): cap = max(cap + cap * POS * ret, 10.0) eq[j:] = cap s = pd.Series(eq, index=ts).resample("1D").last().reindex(IDX).ffill().bfill() return _norm(s), len(trades) def full_history() -> None: print("=== A. STORIA COMPLETA (OOS da 2024-10, fee 0.10% RT; f2x = OOS Sharpe a fee 2x) ===") print(f"{'tf':<5} {'sleeve':<10} {'FULL%':>10} {'DD%':>6} {'Sh':>6} | {'OOS%':>8} {'oDD%':>6} {'oSh':>6} | {'f2x_oSh':>7} {'n':>6}") for tf in ("5m", "10m", "15m", "30m"): for asset in ("BTC", "ETH"): df = resample_ohlcv(load_data(asset, "5m"), 10) if tf == "10m" else load_data(asset, tf) for nm, (fn, params) in strats_for(asset).items(): eq, n = daily_eq(df, fn, params) r = eq.pct_change().fillna(0.0) f, o = metrics(r), metrics(r, lo=SPLIT) eq2, _ = daily_eq(df, fn, params, fee_rt=0.002) o2 = metrics(eq2.pct_change().fillna(0.0), lo=SPLIT) print(f"{tf:<5} {nm + '_' + asset:<10} {f['ret']:>10.0f} {f['dd']:>6.1f} {f['sharpe']:>6.2f}" f" | {o['ret']:>8.0f} {o['dd']:>6.1f} {o['sharpe']:>6.2f} | {o2['sharpe']:>7.2f} {n:>6}") print() def trade_stats(df: pd.DataFrame, fn, params, fee_rt: float = 0.001) -> dict: trades = build_trades(fn(df, **params), df, fee_rt=fee_rt, trend_max=3.0) cap = peak = INIT dd = 0.0 rets = [] wins = 0 for i, j, ret in sorted(trades, key=lambda t: t[1]): cap = max(cap + cap * POS * ret, 10.0) peak = max(peak, cap) dd = max(dd, (peak - cap) / peak) rets.append(ret * POS) wins += ret > 0 n = len(trades) sh = float(np.mean(rets) / np.std(rets) * np.sqrt(n)) if n > 1 and np.std(rets) > 0 else 0.0 return dict(ret=(cap / INIT - 1) * 100, dd=dd * 100, n=n, wr=wins / n * 100 if n else 0.0, sh=sh) def recent_window() -> None: if not all(p.exists() for p in RECENT_1M.values()): print("\n=== B. saltato: manca il parquet 1m recente (fetch Cerbero, vedi diario) ===") return start_ms = int(pd.Timestamp(WINDOW_START, tz="UTC").timestamp() * 1000) data: dict[tuple[str, str], pd.DataFrame] = {} for asset in ("BTC", "ETH"): m1 = pd.read_parquet(RECENT_1M[asset]).sort_values("timestamp").reset_index(drop=True) data[(asset, "1m")] = m1 data[(asset, "2m")] = resample_ohlcv(m1, 2) for tf in ("5m", "15m"): df = load_data(asset, tf) data[(asset, tf)] = df[df["timestamp"] >= start_ms].reset_index(drop=True) print(f"\n=== B. FINESTRA COMUNE {WINDOW_START} -> oggi (regime corrente) ===") print("flat share (O=H=L=C):") for (asset, tf), df in sorted(data.items()): fl = ((df["open"] == df["high"]) & (df["high"] == df["low"]) & (df["low"] == df["close"])).mean() * 100 print(f" {asset} {tf:>3}: {fl:5.1f}%") print(f"\n{'tf':<4} {'sleeve':<10} {'ret%':>8} {'DD%':>6} {'n':>5} {'WR%':>5} {'Sh_tr':>6} | {'fee2x_ret%':>10}") for tf in ("1m", "2m", "5m", "15m"): for asset in ("BTC", "ETH"): for nm, (fn, params) in strats_for(asset).items(): r1 = trade_stats(data[(asset, tf)], fn, params) r2 = trade_stats(data[(asset, tf)], fn, params, fee_rt=0.002) print(f"{tf:<4} {nm + '_' + asset:<10} {r1['ret']:>8.1f} {r1['dd']:>6.1f} {r1['n']:>5}" f" {r1['wr']:>5.1f} {r1['sh']:>6.2f} | {r2['ret']:>10.1f}") print() def correlations() -> None: print("=== C. corr daily (storia completa) vs twin 15m LIVE ===") c5s, c10s = [], [] for asset in ("BTC", "ETH"): d15, d5 = load_data(asset, "15m"), load_data(asset, "5m") d10 = resample_ohlcv(d5, 10) for nm, (fn, params) in strats_for(asset).items(): e15 = daily_eq(d15, fn, params)[0].pct_change() c5 = daily_eq(d5, fn, params)[0].pct_change().corr(e15) c10 = daily_eq(d10, fn, params)[0].pct_change().corr(e15) c5s.append(c5) c10s.append(c10) print(f" {nm}_{asset:<4} 5m-15m {c5:.2f} 10m-15m {c10:.2f}") print(f" media: 5m-15m {np.mean(c5s):.2f} | 10m-15m {np.mean(c10s):.2f}") if __name__ == "__main__": full_history() recent_window() correlations()