"""Strategia 2: DTW pattern matching. Idea: per ogni finestra di N candele, cerca le K finestre più simili nel passato via DTW sui prezzi normalizzati. Se la maggioranza delle match passate è salita dopo, vai long. Se è scesa, vai short. """ 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 from src.fractal.similarity import dtw_distance from src.fractal.patterns import normalize_pattern_window from src.backtest.engine import run_backtest print("=" * 60) print(" STRATEGIA 2: DTW PATTERN MATCHING — BTC 1H") print("=" * 60) df = load_data("BTC", "1h") close = df["close"].values WINDOW = 12 LOOKAHEAD = 6 K_NEIGHBORS = 20 LOOKBACK = 2000 THRESHOLD = 0.65 split_idx = int(len(df) * 0.7) def normalize_window(arr: np.ndarray) -> np.ndarray: mn, mx = arr.min(), arr.max() if mx - mn == 0: return np.zeros_like(arr) return (arr - mn) / (mx - mn) def compute_returns(close_arr: np.ndarray, idx: int, ahead: int) -> float: if idx + ahead >= len(close_arr): return 0.0 return (close_arr[idx + ahead] - close_arr[idx]) / close_arr[idx] print(f"\nParametri: window={WINDOW}, lookahead={LOOKAHEAD}, K={K_NEIGHBORS}") print(f"Lookback: {LOOKBACK} candele, threshold: {THRESHOLD}") print(f"Train: 0→{split_idx}, Test: {split_idx}→{len(df)}") signals = pd.Series(0, index=df.index) accuracies = [] step = 6 test_range = range(split_idx, len(df) - LOOKAHEAD, step) total_steps = len(list(test_range)) print(f"\nValutazione: {total_steps} punti (step={step})...") for count, i in enumerate(test_range): if count % 500 == 0: print(f" Progresso: {count}/{total_steps} ({count/total_steps*100:.0f}%)") current = normalize_window(close[i - WINDOW : i]) search_start = max(WINDOW, i - LOOKBACK) search_end = i - LOOKAHEAD if search_end - search_start < K_NEIGHBORS: continue distances = [] for j in range(search_start, search_end): candidate = normalize_window(close[j - WINDOW : j]) if len(candidate) != len(current): continue d = dtw_distance(current, candidate) future_ret = compute_returns(close, j, LOOKAHEAD) distances.append((d, future_ret)) if len(distances) < K_NEIGHBORS: continue distances.sort(key=lambda x: x[0]) top_k = distances[:K_NEIGHBORS] up_count = sum(1 for _, ret in top_k if ret > 0) up_ratio = up_count / K_NEIGHBORS if up_ratio >= THRESHOLD: signals.iloc[i] = 1 elif up_ratio <= (1 - THRESHOLD): signals.iloc[i] = -1 actual_ret = compute_returns(close, i, LOOKAHEAD) predicted_up = up_ratio >= THRESHOLD predicted_down = up_ratio <= (1 - THRESHOLD) if predicted_up: accuracies.append(1 if actual_ret > 0 else 0) elif predicted_down: accuracies.append(1 if actual_ret < 0 else 0) print(f"\nSegnali generati: {(signals != 0).sum()}") print(f" Long: {(signals == 1).sum()}, Short: {(signals == -1).sum()}") if accuracies: print(f"Accuratezza direzione: {np.mean(accuracies)*100:.1f}% su {len(accuracies)} segnali") test_df = df.iloc[split_idx:].reset_index(drop=True) test_signals = signals.iloc[split_idx:].reset_index(drop=True) result = run_backtest(test_df, test_signals, initial_capital=1000, fee_pct=0.001, max_hold_candles=LOOKAHEAD) print("\nRISULTATI TEST:") for k, v in result.summary().items(): print(f" {k}: {v}")