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>
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user