Files
PythagorasGoal/Old/scripts/waste/W06_structural_pattern.py
T
Adriano Dal Pastro 14522262e6 chore(reset): v2.0.0 — storico certificato Deribit mainnet, ripartenza pulita
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera
libreria "validata OOS" era artefatto di feed contaminato (print fantasma del
feed Cerbero TESTNET + storico Binance/USDT).

- Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e
  CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia
  (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample
  (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE
  50-82% barre flat; XRP/BNB non certificabili).
- Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni
  portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST
  con segnale residuo, da ri-validare in isolamento.
- Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio,
  runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/
  portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/
  (preservati, non cancellati). Diario consolidato in un unico documento.
- Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal +
  src/backtest/engine + load_data; tool dati certificati (rebuild_history,
  certify_feed, audit_feed, multi_source_check).
- Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico
  (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 15:20:59 +00:00

202 lines
6.3 KiB
Python

"""Strategia 6: Structural Pattern Matching con DTW veloce.
Idea diversa: invece di ML generico, cerca nel passato le finestre OHLC
più simili alla finestra corrente usando una versione veloce (reduced DTW).
Vota sulla direzione basandosi sui K-nearest neighbors nel passato.
Usa features normalizzate (non DTW puro sul prezzo che è lento).
"""
from __future__ import annotations
import sys
sys.path.insert(0, ".")
import numpy as np
import pandas as pd
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from src.data.downloader import load_data
from src.fractal.patterns import normalize_pattern_window
print("=" * 60)
print(" STRATEGIA 6: STRUCTURAL PATTERN KNN — BTC 1H")
print("=" * 60)
df = load_data("BTC", "1h")
close = df["close"].values
n = len(close)
WINDOW = 24
LOOKAHEAD = 6
MIN_RETURN = 0.003
def extract_structural_features(df: pd.DataFrame, idx: int, window: int) -> np.ndarray | None:
"""Extract normalized structural features from OHLC window."""
if idx < window:
return None
o = df["open"].values[idx - window : idx]
h = df["high"].values[idx - window : idx]
l = df["low"].values[idx - window : idx]
c = df["close"].values[idx - window : idx]
v = df["volume"].values[idx - window : idx]
# Normalize price to [0,1]
all_prices = np.concatenate([o, h, l, c])
mn, mx = all_prices.min(), all_prices.max()
if mx - mn == 0:
return None
o_n = (o - mn) / (mx - mn)
h_n = (h - mn) / (mx - mn)
l_n = (l - mn) / (mx - mn)
c_n = (c - mn) / (mx - mn)
# Body and shadow ratios (already normalized)
total = h - l
total = np.where(total == 0, 1e-10, total)
body = np.abs(c - o) / total
upper_shadow = (h - np.maximum(o, c)) / total
lower_shadow = (np.minimum(o, c) - l) / total
direction = np.sign(c - o)
# Returns
log_c = np.log(np.where(c == 0, 1e-10, c))
returns = np.diff(log_c)
# Volume profile (normalized)
v_mean = np.mean(v)
v_n = v / v_mean if v_mean > 0 else np.ones_like(v)
# Downsample to fixed-size feature vector
# Take every N-th candle if window is large
step = max(1, window // 12)
sampled_idx = np.arange(0, window, step)[:12]
features = np.concatenate([
c_n[sampled_idx], # 12: normalized close
body[sampled_idx], # 12: body ratios
direction[sampled_idx], # 12: direction
upper_shadow[sampled_idx], # 12: upper shadow
lower_shadow[sampled_idx], # 12: lower shadow
v_n[sampled_idx], # 12: volume profile
[np.mean(returns), np.std(returns), np.sum(returns)], # 3: return stats
[np.mean(body), np.std(body)], # 2: body stats
])
return features
print("Extracting features...")
features_all = []
labels_all = []
indices_all = []
for i in range(WINDOW, n - LOOKAHEAD, 1):
feats = extract_structural_features(df, i, WINDOW)
if feats is None:
continue
future_ret = (close[i + LOOKAHEAD - 1] - close[i - 1]) / close[i - 1]
if abs(future_ret) < MIN_RETURN:
continue
features_all.append(feats)
labels_all.append(1 if future_ret > 0 else 0)
indices_all.append(i)
X = np.array(features_all)
y = np.array(labels_all)
idx_arr = np.array(indices_all)
X = np.nan_to_num(X, nan=0.0, posinf=1e6, neginf=-1e6)
split = int(len(X) * 0.7)
X_train, X_test = X[:split], X[split:]
y_train, y_test = y[:split], y[split:]
idx_test = idx_arr[split:]
print(f"Samples: {len(X)} (train={split}, test={len(X)-split})")
print(f"Label balance: up={np.mean(y)*100:.1f}%")
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
# Test diversi K
print("\n--- KNN SWEEP ---")
for K in [5, 10, 20, 50, 100, 200]:
knn = KNeighborsClassifier(n_neighbors=K, weights="distance", n_jobs=-1)
knn.fit(X_train_s, y_train)
proba = knn.predict_proba(X_test_s)
up_idx = list(knn.classes_).index(1)
for thr in [0.55, 0.60, 0.65, 0.70]:
sigs = []
accs = []
for j in range(len(X_test)):
p_up = proba[j][up_idx]
i = idx_test[j]
actual = (close[i + LOOKAHEAD - 1] - close[i - 1]) / close[i - 1]
if p_up >= thr:
sigs.append(1)
accs.append(1 if actual > 0 else 0)
elif p_up <= (1 - thr):
sigs.append(-1)
accs.append(1 if actual < 0 else 0)
if not accs:
continue
acc = np.mean(accs) * 100
# PnL
capital = 1000
for direction, j in zip(sigs, range(len(accs))):
i_idx = idx_test[[k for k in range(len(X_test)) if proba[k][up_idx] >= thr or proba[k][up_idx] <= (1 - thr)][j]]
entry = close[i_idx - 1]
exit_ = close[i_idx + LOOKAHEAD - 1]
if direction == 1:
ret = (exit_ - entry) / entry
else:
ret = (entry - exit_) / entry
ret -= 0.002
capital *= (1 + ret * 0.5)
total_ret = (capital - 1000) / 1000 * 100
print(f" K={K:3d} thr={thr:.2f}: signals={len(accs):5d} acc={acc:.1f}% ret={total_ret:+.1f}%")
# Best combo: try with Gradient Boosting on same features
print("\n\n--- GRADIENT BOOSTING SU STRUCTURAL FEATURES ---")
from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(
n_estimators=300, max_depth=5, min_samples_leaf=30,
learning_rate=0.03, subsample=0.8, random_state=42,
)
gb.fit(X_train_s, y_train)
proba_gb = gb.predict_proba(X_test_s)
up_idx_gb = list(gb.classes_).index(1)
for thr in [0.50, 0.55, 0.60, 0.65, 0.70, 0.75]:
accs = []
capital = 1000
n_trades = 0
for j in range(len(X_test)):
p_up = proba_gb[j][up_idx_gb]
i = idx_test[j]
actual = (close[i + LOOKAHEAD - 1] - close[i - 1]) / close[i - 1]
if p_up >= thr:
accs.append(1 if actual > 0 else 0)
ret = actual - 0.002
capital *= (1 + ret * 0.5)
n_trades += 1
elif p_up <= (1 - thr):
accs.append(1 if actual < 0 else 0)
ret = -actual - 0.002
capital *= (1 + ret * 0.5)
n_trades += 1
if not accs:
continue
acc = np.mean(accs) * 100
total_ret = (capital - 1000) / 1000 * 100
print(f" thr={thr:.2f}: trades={n_trades:5d} acc={acc:.1f}% ret={total_ret:+.1f}%")