"""Strategia 10: High Precision (target >80% accuracy). Approccio: combina TUTTI gli indicatori disponibili, usa ensemble di 5 modelli, trade SOLO quando tutti concordano. Pochi trade ma molto precisi. Usa leva 3x per compensare bassa frequenza. """ from __future__ import annotations import sys sys.path.insert(0, ".") import numpy as np import pandas as pd from sklearn.ensemble import GradientBoostingClassifier, RandomForestClassifier, ExtraTreesClassifier from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from src.data.downloader import load_data from src.fractal.patterns import encode_candles, extract_body_ratios, extract_shadow_ratios from src.fractal.indicators import hurst_exponent, volatility_ratio LEVERAGE = 3 FEE_PCT = 0.001 INITIAL_CAPITAL = 1000 def build_rich_features(df: pd.DataFrame, i: int) -> np.ndarray | None: if i < 200: return None o = df["open"].values h = df["high"].values l = df["low"].values c = df["close"].values v = df["volume"].values feats = [] for w in [6, 12, 24, 48, 96]: if i < w: feats.extend([0] * 18) continue win_c = c[i - w : i] win_o = o[i - w : i] win_h = h[i - w : i] win_l = l[i - w : i] win_v = v[i - w : i] mn, mx = win_l.min(), max(win_h.max(), win_c.max()) rng = mx - mn if mx - mn > 0 else 1e-10 total = win_h - win_l total = np.where(total == 0, 1e-10, total) body = np.abs(win_c - win_o) / total direction = np.sign(win_c - win_o) log_c = np.log(np.where(win_c == 0, 1e-10, win_c)) rets = np.diff(log_c) v_mean = np.mean(win_v) feats.extend([ np.mean(rets) if len(rets) > 0 else 0, np.std(rets) if len(rets) > 0 else 0, np.sum(rets) if len(rets) > 0 else 0, float(pd.Series(rets).skew()) if len(rets) > 2 else 0, float(pd.Series(rets).kurtosis()) if len(rets) > 3 else 0, np.mean(body), np.std(body), np.mean(direction), np.mean(direction[-min(3, w):]), (win_c[-1] - mn) / rng, win_v[-1] / v_mean if v_mean > 0 else 1, np.max(body) - np.min(body), np.corrcoef(rets[:-1], rets[1:])[0, 1] if len(rets) > 1 and np.std(rets) > 0 else 0, np.max(rets) if len(rets) > 0 else 0, np.min(rets) if len(rets) > 0 else 0, np.mean(np.abs(rets)) if len(rets) > 0 else 0, np.sum(direction == 1) / w, np.sum(direction == -1) / w, ]) # Hurst on different windows for w in [48, 96]: ret_w = np.diff(np.log(np.where(c[max(0,i-w):i] == 0, 1e-10, c[max(0,i-w):i]))) if len(ret_w) > 20: feats.append(hurst_exponent(ret_w, max_lag=min(len(ret_w)//4, 15))) else: feats.append(0.5) # Volatility ratios feats.append(volatility_ratio(c[max(0,i-48):i], fast=6, slow=48)) feats.append(volatility_ratio(c[max(0,i-96):i], fast=12, slow=96)) # ATR normalized tr = np.maximum(h[i-14:i] - l[i-14:i], np.maximum(np.abs(h[i-14:i] - np.roll(c[i-14:i], 1)), np.abs(l[i-14:i] - np.roll(c[i-14:i], 1)))) atr = np.mean(tr[1:]) feats.append(atr / c[i-1] if c[i-1] > 0 else 0) # Position in range h48 = np.max(h[i-48:i]) l48 = np.min(l[i-48:i]) r48 = h48 - l48 feats.append((c[i-1] - l48) / r48 if r48 > 0 else 0.5) h96 = np.max(h[i-96:i]) l96 = np.min(l[i-96:i]) r96 = h96 - l96 feats.append((c[i-1] - l96) / r96 if r96 > 0 else 0.5) return np.nan_to_num(np.array(feats), nan=0, posinf=1e6, neginf=-1e6) def run_high_precision(asset: str, lookahead: int = 3): print(f"\n{'#'*60}") print(f" {asset} 1H — HIGH PRECISION (LA={lookahead})") print(f"{'#'*60}") df = load_data(asset, "1h") close = df["close"].values n = len(df) MIN_RETURN = 0.003 # Build dataset print(" Building features...") X_all, y_all, idx_all = [], [], [] for i in range(200, n - lookahead, 1): f = build_rich_features(df, i) if f is None: continue ret = (close[i + lookahead - 1] - close[i - 1]) / close[i - 1] if abs(ret) < MIN_RETURN: continue X_all.append(f) y_all.append(1 if ret > 0 else 0) idx_all.append(i) X = np.array(X_all) y = np.array(y_all) idx_arr = np.array(idx_all) print(f" Samples: {len(X)}, Features: {X.shape[1]}, Up: {np.mean(y)*100:.1f}%") # Walk-forward with 5-model ensemble TRAIN_SIZE = 15000 STEP_SIZE = 3000 models_config = [ ("GB1", GradientBoostingClassifier(n_estimators=200, max_depth=4, min_samples_leaf=30, learning_rate=0.05, subsample=0.8, random_state=42)), ("GB2", GradientBoostingClassifier(n_estimators=300, max_depth=5, min_samples_leaf=50, learning_rate=0.03, subsample=0.7, random_state=123)), ("RF", RandomForestClassifier(n_estimators=300, max_depth=8, min_samples_leaf=30, class_weight="balanced", random_state=42, n_jobs=-1)), ("ET", ExtraTreesClassifier(n_estimators=300, max_depth=8, min_samples_leaf=30, class_weight="balanced", random_state=42, n_jobs=-1)), ("LR", LogisticRegression(max_iter=1000, C=0.1, random_state=42)), ] capital = float(INITIAL_CAPITAL) all_trades = [] equity = [capital] fold = 0 start = 0 while start + TRAIN_SIZE + STEP_SIZE < len(X): train_end = start + TRAIN_SIZE test_end = min(train_end + STEP_SIZE, len(X)) X_tr, y_tr = X[start:train_end], y[start:train_end] X_te, y_te = X[train_end:test_end], y[train_end:test_end] idx_te = idx_arr[train_end:test_end] scaler = StandardScaler() X_tr_s = scaler.fit_transform(X_tr) X_te_s = scaler.transform(X_te) # Train all models trained = [] for name, model in models_config: m = type(model)(**model.get_params()) m.fit(X_tr_s, y_tr) trained.append((name, m)) # Test with consensus voting for j in range(len(X_te)): votes_up = 0 votes_down = 0 max_conf = 0 for name, m in trained: proba = m.predict_proba(X_te_s[j:j+1])[0] up_idx = list(m.classes_).index(1) p_up = proba[up_idx] if p_up >= 0.60: votes_up += 1 max_conf = max(max_conf, p_up) elif p_up <= 0.40: votes_down += 1 max_conf = max(max_conf, 1 - p_up) i = idx_te[j] actual_ret = (close[i + lookahead - 1] - close[i - 1]) / close[i - 1] # Trade only with strong consensus min_votes = 4 # at least 4 out of 5 models agree direction = None if votes_up >= min_votes: direction = "long" elif votes_down >= min_votes: direction = "short" if direction: if direction == "long": trade_ret = actual_ret else: trade_ret = -actual_ret net_ret = trade_ret * LEVERAGE - FEE_PCT * 2 * LEVERAGE pos_size = 0.2 # 20% of capital per trade pnl = capital * pos_size * net_ret capital += pnl capital = max(capital, 0) equity.append(capital) is_correct = (direction == "long" and actual_ret > 0) or (direction == "short" and actual_ret < 0) all_trades.append({ "fold": fold, "idx": i, "direction": direction, "votes_up": votes_up, "votes_down": votes_down, "actual_ret": actual_ret, "net_ret": net_ret, "pnl": pnl, "correct": is_correct, }) fold += 1 start += STEP_SIZE if not all_trades: print(" No trades generated!") return trades_df = pd.DataFrame(all_trades) n_correct = trades_df["correct"].sum() n_total = len(trades_df) accuracy = n_correct / n_total * 100 test_candles = idx_arr[-1] - idx_arr[TRAIN_SIZE] test_days = test_candles / 24 test_years = test_days / 365.25 ann_ret = ((capital / INITIAL_CAPITAL) ** (1 / test_years) - 1) * 100 if test_years > 0 and capital > 0 else -100 daily_pnl = (capital - INITIAL_CAPITAL) / test_days if test_days > 0 else 0 # Max DD peak = equity[0] max_dd = 0 for v in equity: if v > peak: peak = v dd = (peak - v) / peak if peak > 0 else 0 max_dd = max(max_dd, dd) print(f"\n RISULTATI:") print(f" Trades: {n_total}") print(f" Accuracy: {accuracy:.1f}%") print(f" Return: {(capital-INITIAL_CAPITAL)/INITIAL_CAPITAL*100:+.1f}%") print(f" Annualized: {ann_ret:+.1f}%") print(f" Max Drawdown: {max_dd*100:.1f}%") print(f" Capital: €{capital:.0f}") print(f" Trades/year: {n_total/test_years:.0f}") print(f" €/day avg: €{daily_pnl:.2f}") # Consensus threshold sweep print(f"\n --- CONSENSUS SWEEP ---") for min_v in [3, 4, 5]: for ind_thr in [0.55, 0.60, 0.65]: cap = float(INITIAL_CAPITAL) trades_count = 0 correct_count = 0 eq = [cap] fold_s = 0 start_s = 0 while start_s + TRAIN_SIZE + STEP_SIZE < len(X): train_end_s = start_s + TRAIN_SIZE test_end_s = min(train_end_s + STEP_SIZE, len(X)) X_tr_s2 = scaler.fit_transform(X[start_s:train_end_s]) X_te_s2 = scaler.transform(X[train_end_s:test_end_s]) y_tr_s2 = y[start_s:train_end_s] idx_te_s2 = idx_arr[train_end_s:test_end_s] trained_s = [] for name, model in models_config: m2 = type(model)(**model.get_params()) m2.fit(X_tr_s2, y_tr_s2) trained_s.append(m2) for j in range(len(X_te_s2)): vu = sum(1 for m2 in trained_s if m2.predict_proba(X_te_s2[j:j+1])[0][list(m2.classes_).index(1)] >= ind_thr) vd = sum(1 for m2 in trained_s if m2.predict_proba(X_te_s2[j:j+1])[0][list(m2.classes_).index(1)] <= (1-ind_thr)) i_s = idx_te_s2[j] ar = (close[i_s + lookahead - 1] - close[i_s - 1]) / close[i_s - 1] d = None if vu >= min_v: d = "long" elif vd >= min_v: d = "short" if d: tr = ar if d == "long" else -ar nr = tr * LEVERAGE - FEE_PCT * 2 * LEVERAGE cap += cap * 0.2 * nr cap = max(cap, 0) eq.append(cap) trades_count += 1 if (d == "long" and ar > 0) or (d == "short" and ar < 0): correct_count += 1 start_s += STEP_SIZE if trades_count > 0: acc_s = correct_count / trades_count * 100 ret_s = (cap - INITIAL_CAPITAL) / INITIAL_CAPITAL * 100 ann_s = ((cap / INITIAL_CAPITAL) ** (1 / test_years) - 1) * 100 if test_years > 0 and cap > 0 else -100 dpnl = (cap - INITIAL_CAPITAL) / test_days if test_days > 0 else 0 print(f" min_votes={min_v} ind_thr={ind_thr:.2f}: trades={trades_count:4d} acc={acc_s:.1f}% ret={ret_s:+.1f}% ann={ann_s:+.1f}% €/day={dpnl:.2f}") for asset in ["BTC", "ETH"]: for la in [3, 6]: run_high_precision(asset, la)