refactor: riorganizzazione script — Strategy ABC, folder strategies/waste/analysis
- src/strategies/base.py: Strategy ABC con Signal, BacktestResult, YearlyStats - src/strategies/indicators.py: keltner_ratio, detect_squeezes, ema, atr, rv, corr - scripts/strategies/: SQ01-SQ04 (squeeze puro/filtri), ML01 (squeeze+GBM) - scripts/waste/: W01-W22 script scartati + REF originali - scripts/analysis/: compare, best_yearly, final_report, paper_status - CLAUDE.md aggiornato con nuova struttura e tabella strategie Co-Authored-By: Claude Opus 4.7 (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