Research QuantBook: Equity Multi-Layer EMA + ML Filters
Objectif
Analyser la stratégie multi-couches avec: - EMA Crossover: EMA10 > EMA50 pour signal d’entrée - RSI Filter: 30 < RSI < 75 pour éviter surachat/survente - Bollinger Bands: Prix < Upper Band pour éviter sommets - Volatility Filter: ATR/Prix < 60% pour éviter périodes extrêmes - Trailing Stop: 92% du plus haut (ou 88% fixe) - Take Profit: 125% du prix d’entrée
Univers
SPY, QQQ, IWM (ETFs equity journaliers)
Performance de référence
Sharpe ~1.0+ (2010-2025) - Multi-filtres sur ETFs equity.
Hypothèses à tester
Période EMA: (8/42), (10/50), (12/58)
Seuil volatilité: 50%, 60%, 70%
RSI range: (25/70), (30/75), (35/80)
Prérequis
Environnement Lean Research
Données equity journalières
Durée estimée: ~5 minutes
Note : Version adaptee pour le Docker research environment (données equity au lieu de crypto).
# Setup QuantBookfrom AlgorithmImports import*import numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport warningswarnings.filterwarnings('ignore')plt.style.use('seaborn-v0_8-darkgrid')plt.rcParams['figure.figsize'] = (14, 5)qb = QuantBook()print("QuantBook initialisé.")
QuantBook initialisé.
1. Chargement des données
On charge les données equity journalières pour SPY, QQQ, IWM comme substitut pour l’environnement Docker qui ne dispose pas de données crypto.
# Equity universe (substitute for crypto in Docker research environment)tickers = ["SPY", "QQQ", "IWM"]symbols = {}for ticker in tickers: symbols[ticker] = qb.add_equity(ticker, Resolution.DAILY).symbol# Charger l'historique (2010-2026)start = datetime(2010, 1, 1)end = datetime(2026, 1, 1)history = qb.history(list(symbols.values()), start, end, Resolution.DAILY)print(f"Données chargées: {len(history)} lignes")if history.empty:print("WARNING: No data available. Check Lean data feed.")else:print(f"Tickers: {list(symbols.keys())}")print(f"Shape: {history.shape}")
Pivot de la série ‘close’ en DataFrame large, avec remapping des colonnes Symbol vers ticker pour Multi-Layer-EMA (equity).
# Pivoter les donnéesif history.empty:print("ERROR: No data loaded. Cannot proceed.") closes = pd.DataFrame()else: closes = history['close'].unstack(level=0) symbol_to_ticker = {str(v): k for k, v in symbols.items()} closes.columns = [symbol_to_ticker.get(str(c), str(c)) for c in closes.columns] closes = closes.dropna()print(f"Période: {closes.index[0].date()} à {closes.index[-1].date()}")print(f"Données: {len(closes)} jours")print(f"ETFs: {list(closes.columns)}")
Période: 2011-03-23 à 2025-12-31
Données: 3717 jours
ETFs: ['IWM', 'QQQ', 'SPY']
2. Calcul des indicateurs techniques
def compute_ema(prices, period):"""Calcule l'EMA."""return prices.ewm(span=period, adjust=False).mean()def compute_rsi(prices, period=14):"""Calcule le RSI.""" delta = prices.diff() gain = (delta.where(delta >0, 0)).rolling(window=period).mean() loss = (-delta.where(delta <0, 0)).rolling(window=period).mean() rs = gain / loss rsi =100- (100/ (1+ rs))return rsidef compute_bollinger_bands(prices, period=20, std_dev=2):"""Calcule les Bollinger Bands.""" sma = prices.rolling(period).mean() std = prices.rolling(period).std() upper = sma + std_dev * std lower = sma - std_dev * stdreturn upper, lowerdef compute_atr(prices, period=14):"""Calcule l'ATR (Average True Range).""" high = prices # Simplified (using closes as proxy for hourly) low = prices tr1 = high - low tr2 = (high - prices.shift(1)).abs() tr3 = (low - prices.shift(1)).abs() tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)return tr.rolling(period).mean()print("Indicateurs définis.")
Indicateurs définis.
3. Génération des signaux Multi-Layer
def compute_ml_signals(closes, tickers, ema_fast=10, ema_slow=50, rsi_min=30, rsi_max=75, vol_threshold=0.60):""" Génère les signaux Multi-Layer EMA. Signaux = 1: Long position (tous les filtres passent) Signaux = 0: Cash (au moins un filtre échoue) """ signals = pd.DataFrame(index=closes.index, columns=tickers)for ticker in tickers:if ticker notin closes.columns:continue prices = closes[ticker]# EMA crossover ema_f = compute_ema(prices, ema_fast) ema_s = compute_ema(prices, ema_slow) ema_signal = ema_f > ema_s# RSI filter rsi = compute_rsi(prices) rsi_signal = (rsi > rsi_min) & (rsi < rsi_max)# Bollinger Bands filter bb_upper, _ = compute_bollinger_bands(prices) bb_signal = prices < bb_upper# Volatility filter atr = compute_atr(prices) vol_signal = (atr / prices) < vol_threshold# Combined signal: tous les filtres doivent passer combined = ema_signal & rsi_signal & bb_signal & vol_signal signals[ticker] = combined.astype(int)return signals# Signaux avec paramètres par défautsignals = compute_ml_signals(closes, tickers)print("Signaux Multi-Layer (dernières 10 heures):")print(signals.iloc[-10:])print(f"\nNombre moyen de positions: {signals.sum(axis=1).mean():.1f} sur {len(tickers)}")