"""S2-01: Mean Reversion oraria con filtro orario. Idea: crypto ha bias di ritorno alla media nelle ore notturne (00-06 UTC) e di momentum nelle ore diurne USA (14-20 UTC). - Compra quando RSI < 30 in ore notturne - Vendi quando RSI > 70 in ore notturne - Hold max 4h, stop loss 1.5% Timeframe: 1h. Ingresso quasi giornaliero. """ from __future__ import annotations import sys sys.path.insert(0, ".") import numpy as np import pandas as pd from src.data.downloader import load_data FEE = 0.001 INITIAL = 1000 LEVERAGE = 3 def rsi(close: np.ndarray, period: int = 14) -> np.ndarray: delta = np.diff(close) gain = np.where(delta > 0, delta, 0) loss = np.where(delta < 0, -delta, 0) result = np.full(len(close), 50.0) avg_gain = np.mean(gain[:period]) avg_loss = np.mean(loss[:period]) for i in range(period, len(delta)): avg_gain = (avg_gain * (period - 1) + gain[i]) / period avg_loss = (avg_loss * (period - 1) + loss[i]) / period if avg_loss == 0: result[i + 1] = 100 else: rs = avg_gain / avg_loss result[i + 1] = 100 - 100 / (1 + rs) return result def bollinger_pct(close: np.ndarray, window: int = 20) -> np.ndarray: result = np.full(len(close), 0.5) for i in range(window, len(close)): w = close[i - window : i] ma = np.mean(w) std = np.std(w) if std > 0: result[i] = (close[i] - (ma - 2 * std)) / (4 * std) return result def run_mean_reversion(asset, tf="1h"): df = load_data(asset, tf) close = df["close"].values high = df["high"].values low = df["low"].values n = len(df) timestamps = pd.to_datetime(df["timestamp"], unit="ms", utc=True) hours = timestamps.dt.hour.values rsi_vals = rsi(close, 14) bb_pct = bollinger_pct(close, 20) split = int(n * 0.7) configs = [ # (rsi_low, rsi_high, allowed_hours, hold_max, stop_pct, name) (25, 75, list(range(0, 7)), 4, 0.015, "night_0-6_rsi25"), (30, 70, list(range(0, 7)), 4, 0.015, "night_0-6_rsi30"), (25, 75, list(range(0, 10)), 6, 0.02, "extended_0-9"), (30, 70, list(range(20, 24)) + list(range(0, 6)), 4, 0.015, "late_night"), (20, 80, list(range(0, 24)), 4, 0.015, "all_hours_rsi20"), # Bollinger band mean reversion ] print(f"\n{'#'*60}") print(f" {asset} {tf} — MEAN REVERSION") print(f"{'#'*60}") for rsi_low, rsi_high, allowed, hold_max, stop, name in configs: capital = float(INITIAL) correct = 0 total = 0 daily_trades = {} for i in range(max(split, 20), n - hold_max): hour = hours[i] if hour not in allowed: continue day = timestamps[i].strftime("%Y-%m-%d") if daily_trades.get(day, 0) >= 2: continue direction = None if rsi_vals[i] < rsi_low and bb_pct[i] < 0.2: direction = "long" elif rsi_vals[i] > rsi_high and bb_pct[i] > 0.8: direction = "short" if direction is None: continue entry = close[i] best_exit = i + 1 for j in range(i + 1, min(i + hold_max + 1, n)): price = close[j] if direction == "long": pnl_pct = (price - entry) / entry if pnl_pct <= -stop: best_exit = j break if pnl_pct >= stop * 1.5: best_exit = j break else: pnl_pct = (entry - price) / entry if pnl_pct <= -stop: best_exit = j break if pnl_pct >= stop * 1.5: best_exit = j break best_exit = j exit_price = close[best_exit] if direction == "long": trade_ret = (exit_price - entry) / entry else: trade_ret = (entry - exit_price) / entry net = trade_ret * LEVERAGE - FEE * 2 * LEVERAGE capital += capital * 0.15 * net capital = max(capital, 0) is_correct = trade_ret > 0 total += 1 if is_correct: correct += 1 daily_trades[day] = daily_trades.get(day, 0) + 1 if total < 20: continue acc = correct / total * 100 ret = (capital - INITIAL) / INITIAL * 100 test_days = (n - split) / 24 test_years = test_days / 365.25 ann = ((capital / INITIAL) ** (1 / test_years) - 1) * 100 if test_years > 0 and capital > 0 else -100 dpnl = (capital - INITIAL) / test_days if test_days > 0 else 0 days_with_trades = len(daily_trades) trades_per_day = total / days_with_trades if days_with_trades > 0 else 0 tag = "✅" if acc >= 60 and ann >= 30 else "" print(f" {name:25s}: trades={total:5d} acc={acc:.1f}% ret={ret:+.1f}% ann={ann:+.1f}% dd_est ~{abs(min(0, ret/3)):.0f}% €/day={dpnl:.2f} days_active={days_with_trades} {tag}") for asset in ["ETH", "BTC"]: run_mean_reversion(asset, "1h") run_mean_reversion(asset, "15m")