934 lines
32 KiB
Markdown
934 lines
32 KiB
Markdown
# 📊 Guide des Stratégies - Trading AI Secure
|
||
|
||
## 📋 Table des Matières
|
||
1. [Vue d'ensemble](#vue-densemble)
|
||
2. [Architecture Stratégies](#architecture-stratégies)
|
||
3. [Scalping Strategy](#scalping-strategy)
|
||
4. [Intraday Strategy](#intraday-strategy)
|
||
5. [Swing Strategy](#swing-strategy)
|
||
6. [Paramètres Adaptatifs](#paramètres-adaptatifs)
|
||
7. [Combinaison Multi-Stratégie](#combinaison-multi-stratégie)
|
||
8. [Implémentation](#implémentation)
|
||
|
||
---
|
||
|
||
## 🎯 Vue d'ensemble
|
||
|
||
### Philosophie Multi-Stratégie
|
||
|
||
Le système utilise **3 stratégies complémentaires** qui opèrent sur différents timeframes et profils de risque :
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────┐
|
||
│ STRATÉGIES COMPLÉMENTAIRES │
|
||
├────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ SCALPING (Court Terme) │
|
||
│ ├─ Timeframe: 1-5 minutes │
|
||
│ ├─ Objectif: Micro-mouvements │
|
||
│ ├─ Win Rate: 60-70% │
|
||
│ └─ Risk/Trade: 0.5-1% │
|
||
│ │
|
||
│ INTRADAY (Moyen Terme) │
|
||
│ ├─ Timeframe: 15-60 minutes │
|
||
│ ├─ Objectif: Tendances journalières │
|
||
│ ├─ Win Rate: 55-65% │
|
||
│ └─ Risk/Trade: 1-2% │
|
||
│ │
|
||
│ SWING (Long Terme) │
|
||
│ ├─ Timeframe: 4H-1D │
|
||
│ ├─ Objectif: Mouvements multi-jours │
|
||
│ ├─ Win Rate: 50-60% │
|
||
│ └─ Risk/Trade: 2-3% │
|
||
│ │
|
||
└────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Avantages Multi-Stratégie
|
||
|
||
1. **Diversification temporelle** : Réduit corrélation
|
||
2. **Opportunités multiples** : Capture différents mouvements
|
||
3. **Lissage performance** : Compense pertes d'une stratégie
|
||
4. **Adaptabilité** : Ajuste poids selon régime de marché
|
||
|
||
---
|
||
|
||
## 🏗️ Architecture Stratégies
|
||
|
||
### Classe de Base Abstraite
|
||
|
||
```python
|
||
# src/strategies/base_strategy.py
|
||
|
||
from abc import ABC, abstractmethod
|
||
from typing import Dict, List, Optional, Tuple
|
||
from dataclasses import dataclass
|
||
from datetime import datetime
|
||
import pandas as pd
|
||
import numpy as np
|
||
|
||
@dataclass
|
||
class Signal:
|
||
"""Signal de trading"""
|
||
symbol: str
|
||
direction: str # 'LONG' or 'SHORT'
|
||
entry_price: float
|
||
stop_loss: float
|
||
take_profit: float
|
||
confidence: float # 0.0 to 1.0
|
||
timestamp: datetime
|
||
strategy: str
|
||
metadata: Dict
|
||
|
||
@dataclass
|
||
class StrategyConfig:
|
||
"""Configuration stratégie"""
|
||
name: str
|
||
timeframe: str
|
||
risk_per_trade: float
|
||
max_holding_time: int # seconds
|
||
max_trades_per_day: int
|
||
min_profit_target: float
|
||
max_slippage: float
|
||
|
||
# Paramètres adaptatifs
|
||
adaptive_params: Dict
|
||
|
||
class BaseStrategy(ABC):
|
||
"""
|
||
Classe de base pour toutes les stratégies
|
||
|
||
Toutes stratégies doivent implémenter:
|
||
- analyze(): Analyse marché et génère signaux
|
||
- calculate_position_size(): Calcule taille position
|
||
- update_parameters(): Met à jour paramètres adaptatifs
|
||
"""
|
||
|
||
def __init__(self, config: StrategyConfig):
|
||
self.config = config
|
||
self.name = config.name
|
||
|
||
# État
|
||
self.active_positions: List[Dict] = []
|
||
self.closed_trades: List[Dict] = []
|
||
self.parameters = config.adaptive_params.copy()
|
||
|
||
# Performance
|
||
self.win_rate = 0.5
|
||
self.avg_win = 0.0
|
||
self.avg_loss = 0.0
|
||
self.sharpe_ratio = 0.0
|
||
|
||
@abstractmethod
|
||
def analyze(self, market_data: pd.DataFrame) -> Optional[Signal]:
|
||
"""
|
||
Analyse données marché et génère signal
|
||
|
||
Args:
|
||
market_data: DataFrame avec OHLCV + indicateurs
|
||
|
||
Returns:
|
||
Signal si opportunité détectée, None sinon
|
||
"""
|
||
pass
|
||
|
||
@abstractmethod
|
||
def calculate_indicators(self, data: pd.DataFrame) -> pd.DataFrame:
|
||
"""
|
||
Calcule indicateurs techniques nécessaires
|
||
|
||
Args:
|
||
data: DataFrame avec OHLCV
|
||
|
||
Returns:
|
||
DataFrame avec indicateurs ajoutés
|
||
"""
|
||
pass
|
||
|
||
def calculate_position_size(
|
||
self,
|
||
signal: Signal,
|
||
portfolio_value: float,
|
||
current_volatility: float
|
||
) -> float:
|
||
"""
|
||
Calcule taille position optimale
|
||
|
||
Utilise:
|
||
- Kelly Criterion adaptatif
|
||
- Volatility adjustment
|
||
- Risk per trade limit
|
||
"""
|
||
# Kelly de base
|
||
kelly = (self.win_rate * (self.avg_win / abs(self.avg_loss)) - (1 - self.win_rate)) / (self.avg_win / abs(self.avg_loss))
|
||
|
||
# Ajuster selon volatilité
|
||
vol_adjustment = 0.02 / max(current_volatility, 0.01) # Target 2% vol
|
||
kelly *= vol_adjustment
|
||
|
||
# Ajuster selon confiance du signal
|
||
kelly *= signal.confidence
|
||
|
||
# Appliquer limite risk per trade
|
||
kelly = min(kelly, self.config.risk_per_trade)
|
||
|
||
# Calculer taille position
|
||
risk_amount = portfolio_value * kelly
|
||
stop_distance = abs(signal.entry_price - signal.stop_loss)
|
||
position_size = risk_amount / stop_distance
|
||
|
||
return position_size
|
||
|
||
def update_parameters(self, recent_performance: Dict):
|
||
"""
|
||
Met à jour paramètres adaptatifs selon performance
|
||
|
||
Args:
|
||
recent_performance: Métriques des 30 derniers jours
|
||
"""
|
||
# Mettre à jour statistiques
|
||
self.win_rate = recent_performance.get('win_rate', self.win_rate)
|
||
self.avg_win = recent_performance.get('avg_win', self.avg_win)
|
||
self.avg_loss = recent_performance.get('avg_loss', self.avg_loss)
|
||
self.sharpe_ratio = recent_performance.get('sharpe', self.sharpe_ratio)
|
||
|
||
# Ajuster paramètres si sous-performance
|
||
if self.sharpe_ratio < 1.0:
|
||
self._reduce_aggressiveness()
|
||
elif self.sharpe_ratio > 2.0:
|
||
self._increase_aggressiveness()
|
||
|
||
def _reduce_aggressiveness(self):
|
||
"""Réduit agressivité si sous-performance"""
|
||
# Augmenter seuils de confiance
|
||
if 'min_confidence' in self.parameters:
|
||
self.parameters['min_confidence'] = min(
|
||
self.parameters['min_confidence'] * 1.1,
|
||
0.8
|
||
)
|
||
|
||
# Réduire nombre de trades
|
||
self.config.max_trades_per_day = max(
|
||
int(self.config.max_trades_per_day * 0.8),
|
||
1
|
||
)
|
||
|
||
def _increase_aggressiveness(self):
|
||
"""Augmente agressivité si sur-performance"""
|
||
# Réduire seuils de confiance
|
||
if 'min_confidence' in self.parameters:
|
||
self.parameters['min_confidence'] = max(
|
||
self.parameters['min_confidence'] * 0.9,
|
||
0.5
|
||
)
|
||
|
||
# Augmenter nombre de trades
|
||
self.config.max_trades_per_day = min(
|
||
int(self.config.max_trades_per_day * 1.2),
|
||
100
|
||
)
|
||
|
||
def record_trade(self, trade: Dict):
|
||
"""Enregistre trade fermé"""
|
||
self.closed_trades.append(trade)
|
||
|
||
# Mettre à jour statistiques
|
||
self._update_statistics()
|
||
|
||
def _update_statistics(self):
|
||
"""Met à jour statistiques de performance"""
|
||
if len(self.closed_trades) < 10:
|
||
return
|
||
|
||
recent_trades = self.closed_trades[-30:] # 30 derniers trades
|
||
|
||
wins = [t for t in recent_trades if t['pnl'] > 0]
|
||
losses = [t for t in recent_trades if t['pnl'] < 0]
|
||
|
||
self.win_rate = len(wins) / len(recent_trades)
|
||
self.avg_win = np.mean([t['pnl'] for t in wins]) if wins else 0
|
||
self.avg_loss = np.mean([t['pnl'] for t in losses]) if losses else 0
|
||
|
||
# Calculer Sharpe
|
||
returns = [t['pnl'] / t['risk'] for t in recent_trades]
|
||
self.sharpe_ratio = np.mean(returns) / np.std(returns) if np.std(returns) > 0 else 0
|
||
```
|
||
|
||
---
|
||
|
||
## ⚡ Scalping Strategy
|
||
|
||
### Caractéristiques
|
||
|
||
- **Timeframe** : 1-5 minutes
|
||
- **Holding Time** : 5-30 minutes maximum
|
||
- **Risk per Trade** : 0.5-1%
|
||
- **Win Rate Target** : 60-70%
|
||
- **Profit Target** : 0.3-0.5% par trade
|
||
|
||
### Indicateurs Utilisés
|
||
|
||
```python
|
||
# src/strategies/scalping/scalping_strategy.py
|
||
|
||
class ScalpingStrategy(BaseStrategy):
|
||
"""
|
||
Stratégie de scalping basée sur:
|
||
- Mean reversion (Bollinger Bands)
|
||
- Momentum (RSI, MACD)
|
||
- Volume profile
|
||
- Order flow imbalance
|
||
"""
|
||
|
||
def __init__(self, config: StrategyConfig):
|
||
super().__init__(config)
|
||
|
||
# Paramètres adaptatifs
|
||
self.parameters = {
|
||
'bb_period': 20,
|
||
'bb_std': 2.0,
|
||
'rsi_period': 14,
|
||
'rsi_oversold': 30,
|
||
'rsi_overbought': 70,
|
||
'volume_threshold': 1.5, # 1.5x volume moyen
|
||
'min_confidence': 0.65,
|
||
}
|
||
|
||
def calculate_indicators(self, data: pd.DataFrame) -> pd.DataFrame:
|
||
"""Calcule indicateurs scalping"""
|
||
df = data.copy()
|
||
|
||
# Bollinger Bands
|
||
bb_period = self.parameters['bb_period']
|
||
bb_std = self.parameters['bb_std']
|
||
|
||
df['bb_middle'] = df['close'].rolling(bb_period).mean()
|
||
df['bb_std'] = df['close'].rolling(bb_period).std()
|
||
df['bb_upper'] = df['bb_middle'] + (bb_std * df['bb_std'])
|
||
df['bb_lower'] = df['bb_middle'] - (bb_std * df['bb_std'])
|
||
df['bb_position'] = (df['close'] - df['bb_lower']) / (df['bb_upper'] - df['bb_lower'])
|
||
|
||
# RSI
|
||
rsi_period = self.parameters['rsi_period']
|
||
delta = df['close'].diff()
|
||
gain = (delta.where(delta > 0, 0)).rolling(rsi_period).mean()
|
||
loss = (-delta.where(delta < 0, 0)).rolling(rsi_period).mean()
|
||
rs = gain / loss
|
||
df['rsi'] = 100 - (100 / (1 + rs))
|
||
|
||
# MACD
|
||
df['ema_12'] = df['close'].ewm(span=12).mean()
|
||
df['ema_26'] = df['close'].ewm(span=26).mean()
|
||
df['macd'] = df['ema_12'] - df['ema_26']
|
||
df['macd_signal'] = df['macd'].ewm(span=9).mean()
|
||
df['macd_hist'] = df['macd'] - df['macd_signal']
|
||
|
||
# Volume
|
||
df['volume_ma'] = df['volume'].rolling(20).mean()
|
||
df['volume_ratio'] = df['volume'] / df['volume_ma']
|
||
|
||
# ATR pour stop-loss
|
||
df['tr'] = np.maximum(
|
||
df['high'] - df['low'],
|
||
np.maximum(
|
||
abs(df['high'] - df['close'].shift(1)),
|
||
abs(df['low'] - df['close'].shift(1))
|
||
)
|
||
)
|
||
df['atr'] = df['tr'].rolling(14).mean()
|
||
|
||
return df
|
||
|
||
def analyze(self, market_data: pd.DataFrame) -> Optional[Signal]:
|
||
"""
|
||
Génère signal de scalping
|
||
|
||
Conditions LONG:
|
||
- Prix proche BB lower (oversold)
|
||
- RSI < 30 (oversold)
|
||
- MACD histogram positif (momentum reversal)
|
||
- Volume > 1.5x moyenne
|
||
|
||
Conditions SHORT:
|
||
- Prix proche BB upper (overbought)
|
||
- RSI > 70 (overbought)
|
||
- MACD histogram négatif
|
||
- Volume > 1.5x moyenne
|
||
"""
|
||
df = self.calculate_indicators(market_data)
|
||
|
||
if len(df) < 50:
|
||
return None
|
||
|
||
current = df.iloc[-1]
|
||
prev = df.iloc[-2]
|
||
|
||
# Vérifier volume
|
||
if current['volume_ratio'] < self.parameters['volume_threshold']:
|
||
return None
|
||
|
||
# Signal LONG
|
||
if (current['bb_position'] < 0.2 and # Proche BB lower
|
||
current['rsi'] < self.parameters['rsi_oversold'] and
|
||
current['macd_hist'] > 0 and prev['macd_hist'] <= 0): # MACD cross
|
||
|
||
confidence = self._calculate_confidence(df, 'LONG')
|
||
|
||
if confidence >= self.parameters['min_confidence']:
|
||
return Signal(
|
||
symbol=market_data.attrs.get('symbol', 'UNKNOWN'),
|
||
direction='LONG',
|
||
entry_price=current['close'],
|
||
stop_loss=current['close'] - (2 * current['atr']),
|
||
take_profit=current['close'] + (4 * current['atr']), # R:R 2:1 (breakeven ~34%)
|
||
confidence=confidence,
|
||
timestamp=current.name,
|
||
strategy='scalping',
|
||
metadata={
|
||
'rsi': current['rsi'],
|
||
'bb_position': current['bb_position'],
|
||
'volume_ratio': current['volume_ratio']
|
||
}
|
||
)
|
||
|
||
# Signal SHORT
|
||
elif (current['bb_position'] > 0.8 and # Proche BB upper
|
||
current['rsi'] > self.parameters['rsi_overbought'] and
|
||
current['macd_hist'] < 0 and prev['macd_hist'] >= 0):
|
||
|
||
confidence = self._calculate_confidence(df, 'SHORT')
|
||
|
||
if confidence >= self.parameters['min_confidence']:
|
||
return Signal(
|
||
symbol=market_data.attrs.get('symbol', 'UNKNOWN'),
|
||
direction='SHORT',
|
||
entry_price=current['close'],
|
||
stop_loss=current['close'] + (2 * current['atr']),
|
||
take_profit=current['close'] - (4 * current['atr']), # R:R 2:1 (breakeven ~34%)
|
||
confidence=confidence,
|
||
timestamp=current.name,
|
||
strategy='scalping',
|
||
metadata={
|
||
'rsi': current['rsi'],
|
||
'bb_position': current['bb_position'],
|
||
'volume_ratio': current['volume_ratio']
|
||
}
|
||
)
|
||
|
||
return None
|
||
|
||
def _calculate_confidence(self, df: pd.DataFrame, direction: str) -> float:
|
||
"""
|
||
Calcule confiance du signal (0.0 à 1.0)
|
||
|
||
Facteurs:
|
||
- Force de l'oversold/overbought
|
||
- Confirmation volume
|
||
- Momentum MACD
|
||
- Historique win rate
|
||
"""
|
||
current = df.iloc[-1]
|
||
|
||
confidence = 0.5 # Base
|
||
|
||
if direction == 'LONG':
|
||
# RSI oversold strength
|
||
rsi_strength = (30 - current['rsi']) / 30
|
||
confidence += 0.2 * max(0, rsi_strength)
|
||
|
||
# BB position
|
||
bb_strength = (0.2 - current['bb_position']) / 0.2
|
||
confidence += 0.15 * max(0, bb_strength)
|
||
|
||
else: # SHORT
|
||
# RSI overbought strength
|
||
rsi_strength = (current['rsi'] - 70) / 30
|
||
confidence += 0.2 * max(0, rsi_strength)
|
||
|
||
# BB position
|
||
bb_strength = (current['bb_position'] - 0.8) / 0.2
|
||
confidence += 0.15 * max(0, bb_strength)
|
||
|
||
# Volume confirmation
|
||
volume_strength = min((current['volume_ratio'] - 1.5) / 1.5, 1.0)
|
||
confidence += 0.15 * volume_strength
|
||
|
||
# Historical win rate
|
||
confidence += 0.1 * (self.win_rate - 0.5)
|
||
|
||
return np.clip(confidence, 0.0, 1.0)
|
||
```
|
||
|
||
---
|
||
|
||
## 📈 Intraday Strategy
|
||
|
||
### Caractéristiques
|
||
|
||
- **Timeframe** : 15-60 minutes
|
||
- **Holding Time** : 2-8 heures
|
||
- **Risk per Trade** : 1-2%
|
||
- **Win Rate Target** : 55-65%
|
||
- **Profit Target** : 1-2% par trade
|
||
|
||
### Implémentation
|
||
|
||
```python
|
||
# src/strategies/intraday/intraday_strategy.py
|
||
|
||
class IntradayStrategy(BaseStrategy):
|
||
"""
|
||
Stratégie intraday basée sur:
|
||
- Trend following (EMA crossovers)
|
||
- Support/Resistance
|
||
- Volume analysis
|
||
- Market regime
|
||
"""
|
||
|
||
def __init__(self, config: StrategyConfig):
|
||
super().__init__(config)
|
||
|
||
self.parameters = {
|
||
'ema_fast': 9,
|
||
'ema_slow': 21,
|
||
'ema_trend': 50,
|
||
'atr_multiplier': 2.5,
|
||
'volume_confirmation': 1.2,
|
||
'min_confidence': 0.60,
|
||
}
|
||
|
||
def calculate_indicators(self, data: pd.DataFrame) -> pd.DataFrame:
|
||
"""Calcule indicateurs intraday"""
|
||
df = data.copy()
|
||
|
||
# EMAs
|
||
df['ema_fast'] = df['close'].ewm(span=self.parameters['ema_fast']).mean()
|
||
df['ema_slow'] = df['close'].ewm(span=self.parameters['ema_slow']).mean()
|
||
df['ema_trend'] = df['close'].ewm(span=self.parameters['ema_trend']).mean()
|
||
|
||
# Trend direction
|
||
df['trend'] = np.where(df['ema_fast'] > df['ema_slow'], 1, -1)
|
||
|
||
# ATR
|
||
df['tr'] = np.maximum(
|
||
df['high'] - df['low'],
|
||
np.maximum(
|
||
abs(df['high'] - df['close'].shift(1)),
|
||
abs(df['low'] - df['close'].shift(1))
|
||
)
|
||
)
|
||
df['atr'] = df['tr'].rolling(14).mean()
|
||
|
||
# Support/Resistance (pivot points)
|
||
df['pivot'] = (df['high'] + df['low'] + df['close']) / 3
|
||
df['r1'] = 2 * df['pivot'] - df['low']
|
||
df['s1'] = 2 * df['pivot'] - df['high']
|
||
|
||
# Volume
|
||
df['volume_ma'] = df['volume'].rolling(20).mean()
|
||
df['volume_ratio'] = df['volume'] / df['volume_ma']
|
||
|
||
# ADX (trend strength)
|
||
df['adx'] = self._calculate_adx(df)
|
||
|
||
return df
|
||
|
||
def analyze(self, market_data: pd.DataFrame) -> Optional[Signal]:
|
||
"""
|
||
Génère signal intraday
|
||
|
||
Conditions LONG:
|
||
- EMA fast cross above EMA slow
|
||
- Prix au-dessus EMA trend (uptrend)
|
||
- ADX > 25 (strong trend)
|
||
- Volume confirmation
|
||
|
||
Conditions SHORT:
|
||
- EMA fast cross below EMA slow
|
||
- Prix en-dessous EMA trend (downtrend)
|
||
- ADX > 25
|
||
- Volume confirmation
|
||
"""
|
||
df = self.calculate_indicators(market_data)
|
||
|
||
if len(df) < 100:
|
||
return None
|
||
|
||
current = df.iloc[-1]
|
||
prev = df.iloc[-2]
|
||
|
||
# Vérifier trend strength
|
||
if current['adx'] < 25:
|
||
return None
|
||
|
||
# Signal LONG (bullish crossover)
|
||
if (current['ema_fast'] > current['ema_slow'] and
|
||
prev['ema_fast'] <= prev['ema_slow'] and
|
||
current['close'] > current['ema_trend'] and
|
||
current['volume_ratio'] > self.parameters['volume_confirmation']):
|
||
|
||
confidence = self._calculate_confidence(df, 'LONG')
|
||
|
||
if confidence >= self.parameters['min_confidence']:
|
||
atr_mult = self.parameters['atr_multiplier']
|
||
|
||
return Signal(
|
||
symbol=market_data.attrs.get('symbol', 'UNKNOWN'),
|
||
direction='LONG',
|
||
entry_price=current['close'],
|
||
stop_loss=current['close'] - (atr_mult * current['atr']),
|
||
take_profit=current['close'] + (atr_mult * 2 * current['atr']),
|
||
confidence=confidence,
|
||
timestamp=current.name,
|
||
strategy='intraday',
|
||
metadata={
|
||
'adx': current['adx'],
|
||
'trend': 'UP',
|
||
'volume_ratio': current['volume_ratio']
|
||
}
|
||
)
|
||
|
||
# Signal SHORT (bearish crossover)
|
||
elif (current['ema_fast'] < current['ema_slow'] and
|
||
prev['ema_fast'] >= prev['ema_slow'] and
|
||
current['close'] < current['ema_trend'] and
|
||
current['volume_ratio'] > self.parameters['volume_confirmation']):
|
||
|
||
confidence = self._calculate_confidence(df, 'SHORT')
|
||
|
||
if confidence >= self.parameters['min_confidence']:
|
||
atr_mult = self.parameters['atr_multiplier']
|
||
|
||
return Signal(
|
||
symbol=market_data.attrs.get('symbol', 'UNKNOWN'),
|
||
direction='SHORT',
|
||
entry_price=current['close'],
|
||
stop_loss=current['close'] + (atr_mult * current['atr']),
|
||
take_profit=current['close'] - (atr_mult * 2 * current['atr']),
|
||
confidence=confidence,
|
||
timestamp=current.name,
|
||
strategy='intraday',
|
||
metadata={
|
||
'adx': current['adx'],
|
||
'trend': 'DOWN',
|
||
'volume_ratio': current['volume_ratio']
|
||
}
|
||
)
|
||
|
||
return None
|
||
|
||
def _calculate_adx(self, df: pd.DataFrame, period=14) -> pd.Series:
|
||
"""Calcule Average Directional Index"""
|
||
# Simplified ADX calculation
|
||
high_diff = df['high'].diff()
|
||
low_diff = -df['low'].diff()
|
||
|
||
pos_dm = np.where((high_diff > low_diff) & (high_diff > 0), high_diff, 0)
|
||
neg_dm = np.where((low_diff > high_diff) & (low_diff > 0), low_diff, 0)
|
||
|
||
tr = df['tr']
|
||
|
||
pos_di = 100 * pd.Series(pos_dm).rolling(period).mean() / tr.rolling(period).mean()
|
||
neg_di = 100 * pd.Series(neg_dm).rolling(period).mean() / tr.rolling(period).mean()
|
||
|
||
dx = 100 * abs(pos_di - neg_di) / (pos_di + neg_di)
|
||
adx = dx.rolling(period).mean()
|
||
|
||
return adx
|
||
|
||
def _calculate_confidence(self, df: pd.DataFrame, direction: str) -> float:
|
||
"""Calcule confiance signal intraday"""
|
||
current = df.iloc[-1]
|
||
|
||
confidence = 0.5
|
||
|
||
# ADX strength
|
||
adx_strength = min((current['adx'] - 25) / 25, 1.0)
|
||
confidence += 0.2 * adx_strength
|
||
|
||
# Volume confirmation
|
||
volume_strength = min((current['volume_ratio'] - 1.2) / 1.0, 1.0)
|
||
confidence += 0.15 * volume_strength
|
||
|
||
# Trend alignment
|
||
if direction == 'LONG':
|
||
trend_alignment = (current['close'] - current['ema_trend']) / current['ema_trend']
|
||
else:
|
||
trend_alignment = (current['ema_trend'] - current['close']) / current['ema_trend']
|
||
|
||
confidence += 0.15 * min(trend_alignment * 10, 1.0)
|
||
|
||
# Historical performance
|
||
confidence += 0.1 * (self.win_rate - 0.5)
|
||
|
||
return np.clip(confidence, 0.0, 1.0)
|
||
```
|
||
|
||
---
|
||
|
||
## 🌊 Swing Strategy
|
||
|
||
### Caractéristiques
|
||
|
||
- **Timeframe** : 4H-1D
|
||
- **Holding Time** : 2-5 jours
|
||
- **Risk per Trade** : 2-3%
|
||
- **Win Rate Target** : 50-60%
|
||
- **Profit Target** : 3-5% par trade
|
||
|
||
### Implémentation
|
||
|
||
```python
|
||
# src/strategies/swing/swing_strategy.py
|
||
|
||
class SwingStrategy(BaseStrategy):
|
||
"""
|
||
Stratégie swing basée sur:
|
||
- Multi-timeframe analysis
|
||
- Chart patterns
|
||
- Fibonacci retracements
|
||
- Macro trends
|
||
"""
|
||
|
||
def __init__(self, config: StrategyConfig):
|
||
super().__init__(config)
|
||
|
||
self.parameters = {
|
||
'sma_short': 20,
|
||
'sma_long': 50,
|
||
'rsi_period': 14,
|
||
'macd_fast': 12,
|
||
'macd_slow': 26,
|
||
'macd_signal': 9,
|
||
'min_confidence': 0.55,
|
||
}
|
||
|
||
def calculate_indicators(self, data: pd.DataFrame) -> pd.DataFrame:
|
||
"""Calcule indicateurs swing"""
|
||
df = data.copy()
|
||
|
||
# SMAs
|
||
df['sma_short'] = df['close'].rolling(self.parameters['sma_short']).mean()
|
||
df['sma_long'] = df['close'].rolling(self.parameters['sma_long']).mean()
|
||
|
||
# RSI
|
||
delta = df['close'].diff()
|
||
gain = (delta.where(delta > 0, 0)).rolling(14).mean()
|
||
loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
|
||
rs = gain / loss
|
||
df['rsi'] = 100 - (100 / (1 + rs))
|
||
|
||
# MACD
|
||
df['ema_fast'] = df['close'].ewm(span=self.parameters['macd_fast']).mean()
|
||
df['ema_slow'] = df['close'].ewm(span=self.parameters['macd_slow']).mean()
|
||
df['macd'] = df['ema_fast'] - df['ema_slow']
|
||
df['macd_signal'] = df['macd'].ewm(span=self.parameters['macd_signal']).mean()
|
||
df['macd_hist'] = df['macd'] - df['macd_signal']
|
||
|
||
# ATR
|
||
df['tr'] = np.maximum(
|
||
df['high'] - df['low'],
|
||
np.maximum(
|
||
abs(df['high'] - df['close'].shift(1)),
|
||
abs(df['low'] - df['close'].shift(1))
|
||
)
|
||
)
|
||
df['atr'] = df['tr'].rolling(14).mean()
|
||
|
||
# Fibonacci levels (simplified)
|
||
df['fib_high'] = df['high'].rolling(50).max()
|
||
df['fib_low'] = df['low'].rolling(50).min()
|
||
df['fib_range'] = df['fib_high'] - df['fib_low']
|
||
df['fib_382'] = df['fib_high'] - 0.382 * df['fib_range']
|
||
df['fib_618'] = df['fib_high'] - 0.618 * df['fib_range']
|
||
|
||
return df
|
||
|
||
def analyze(self, market_data: pd.DataFrame) -> Optional[Signal]:
|
||
"""
|
||
Génère signal swing
|
||
|
||
Conditions LONG:
|
||
- SMA short > SMA long (uptrend)
|
||
- RSI 40-60 (not overbought)
|
||
- MACD bullish
|
||
- Prix near Fibonacci support
|
||
|
||
Conditions SHORT:
|
||
- SMA short < SMA long (downtrend)
|
||
- RSI 40-60 (not oversold)
|
||
- MACD bearish
|
||
- Prix near Fibonacci resistance
|
||
"""
|
||
df = self.calculate_indicators(market_data)
|
||
|
||
if len(df) < 100:
|
||
return None
|
||
|
||
current = df.iloc[-1]
|
||
prev = df.iloc[-2]
|
||
|
||
# Signal LONG
|
||
if (current['sma_short'] > current['sma_long'] and
|
||
40 < current['rsi'] < 60 and
|
||
current['macd'] > current['macd_signal'] and
|
||
abs(current['close'] - current['fib_618']) / current['close'] < 0.01):
|
||
|
||
confidence = self._calculate_confidence(df, 'LONG')
|
||
|
||
if confidence >= self.parameters['min_confidence']:
|
||
return Signal(
|
||
symbol=market_data.attrs.get('symbol', 'UNKNOWN'),
|
||
direction='LONG',
|
||
entry_price=current['close'],
|
||
stop_loss=current['fib_low'],
|
||
take_profit=current['fib_high'],
|
||
confidence=confidence,
|
||
timestamp=current.name,
|
||
strategy='swing',
|
||
metadata={
|
||
'rsi': current['rsi'],
|
||
'macd_hist': current['macd_hist'],
|
||
'fib_level': 'support_618'
|
||
}
|
||
)
|
||
|
||
# Signal SHORT
|
||
elif (current['sma_short'] < current['sma_long'] and
|
||
40 < current['rsi'] < 60 and
|
||
current['macd'] < current['macd_signal'] and
|
||
abs(current['close'] - current['fib_382']) / current['close'] < 0.01):
|
||
|
||
confidence = self._calculate_confidence(df, 'SHORT')
|
||
|
||
if confidence >= self.parameters['min_confidence']:
|
||
return Signal(
|
||
symbol=market_data.attrs.get('symbol', 'UNKNOWN'),
|
||
direction='SHORT',
|
||
entry_price=current['close'],
|
||
stop_loss=current['fib_high'],
|
||
take_profit=current['fib_low'],
|
||
confidence=confidence,
|
||
timestamp=current.name,
|
||
strategy='swing',
|
||
metadata={
|
||
'rsi': current['rsi'],
|
||
'macd_hist': current['macd_hist'],
|
||
'fib_level': 'resistance_382'
|
||
}
|
||
)
|
||
|
||
return None
|
||
|
||
def _calculate_confidence(self, df: pd.DataFrame, direction: str) -> float:
|
||
"""Calcule confiance signal swing"""
|
||
current = df.iloc[-1]
|
||
|
||
confidence = 0.5
|
||
|
||
# Trend strength
|
||
sma_distance = abs(current['sma_short'] - current['sma_long']) / current['sma_long']
|
||
confidence += 0.2 * min(sma_distance * 20, 1.0)
|
||
|
||
# MACD strength
|
||
macd_strength = abs(current['macd_hist']) / current['close']
|
||
confidence += 0.15 * min(macd_strength * 100, 1.0)
|
||
|
||
# RSI neutral zone (good for swing)
|
||
rsi_score = 1 - abs(current['rsi'] - 50) / 50
|
||
confidence += 0.15 * rsi_score
|
||
|
||
# Historical performance
|
||
confidence += 0.1 * (self.win_rate - 0.5)
|
||
|
||
return np.clip(confidence, 0.0, 1.0)
|
||
```
|
||
|
||
---
|
||
|
||
## ML-Driven Strategy (Phase 4b — 2026-03-08)
|
||
|
||
### Concept
|
||
|
||
Contrairement aux stratégies règle-based (scalping/intraday/swing), la **MLDrivenStrategy** remplace
|
||
les conditions codées en dur par un modèle XGBoost/LightGBM entraîné sur des données historiques.
|
||
|
||
Le modèle apprend **quelles combinaisons** d'indicateurs classiques sont réellement prédictives,
|
||
reproduisant l'intuition d'un trader expérimenté.
|
||
|
||
### Fichiers
|
||
| Fichier | Rôle |
|
||
|---|---|
|
||
| `src/strategies/ml_driven/ml_strategy.py` | Stratégie (interface BaseStrategy) |
|
||
| `src/ml/ml_strategy_model.py` | Entraînement + prédiction |
|
||
| `src/ml/features/technical_features.py` | ~50 features TA |
|
||
| `src/ml/features/label_generator.py` | Labels LONG/SHORT/NEUTRAL |
|
||
|
||
Documentation complète : [docs/ML_STRATEGY_GUIDE.md](ML_STRATEGY_GUIDE.md)
|
||
|
||
### Features utilisées
|
||
- **RSI** : valeur, zones survente/surachat, divergences haussières/baissières
|
||
- **MACD** : crossovers, histogramme, pente du momentum
|
||
- **Bollinger Bands** : position relative, squeeze, cassures et rebonds
|
||
- **Supports/Résistances** : distance aux niveaux pivots locaux (50 barres)
|
||
- **Points Pivots** : classiques (R1/R2/S1/S2) + Fibonacci (38.2%, 61.8%, 100%)
|
||
- **ATR** : volatilité normalisée, ratio vs moyenne
|
||
- **Chandeliers** : marteau, étoile filante, engulfing haussier/baissier, doji
|
||
- **EMAs** : alignement 8/21/50/200, distance % du prix, pente
|
||
- **Volume** : ratio vs moyenne, pics, OBV
|
||
- **Sessions** : Londres (8h-16h), NY (13h-21h), chevauchement (13h-16h)
|
||
|
||
### Labels (supervision)
|
||
Pour chaque barre i, simulation forward sur N barres :
|
||
- **LONG (1)** : HIGH atteint `entry + tp_atr × ATR` avant que LOW descende sous `entry - sl_atr × ATR`
|
||
- **SHORT (-1)** : l'inverse
|
||
- **NEUTRAL (0)** : ni TP ni SL dans l'horizon
|
||
|
||
### Usage via API
|
||
```bash
|
||
# Entraîner
|
||
curl -X POST http://localhost:8100/trading/train \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"symbol":"EURUSD","timeframe":"1h","period":"2y","model_type":"xgboost","tp_atr_mult":2.0,"sl_atr_mult":1.0}'
|
||
|
||
# Suivre l'entraînement
|
||
curl http://localhost:8100/trading/train/{job_id}
|
||
|
||
# Lister les modèles disponibles
|
||
curl http://localhost:8100/trading/ml-models
|
||
|
||
# Feature importance
|
||
curl http://localhost:8100/trading/ml-models/EURUSD/1h/importance
|
||
```
|
||
|
||
### Paramètres de configuration
|
||
```python
|
||
config = {
|
||
'name': 'ml_driven',
|
||
'symbol': 'EURUSD',
|
||
'timeframe': '1h',
|
||
'risk_per_trade': 0.01,
|
||
'model_type': 'xgboost', # xgboost | lightgbm | random_forest
|
||
'min_confidence': 0.55, # Seuil de confiance minimum
|
||
'tp_atr_mult': 2.0, # TP = entry ± 2×ATR → R:R = 2:1
|
||
'sl_atr_mult': 1.0, # SL = entry ∓ 1×ATR
|
||
'auto_load': True, # Charge le modèle existant au démarrage
|
||
}
|
||
```
|
||
|
||
### Validation
|
||
Walk-forward cross-validation (3 folds temporels) — protège contre l'overfitting.
|
||
|
||
Métriques cibles :
|
||
- `wf_accuracy > 0.55`
|
||
- `wf_precision > 0.50` sur signaux directionnels
|
||
- Distribution LONG/SHORT équilibrée
|
||
|
||
### Modèles sauvegardés
|
||
```
|
||
models/ml_strategy/
|
||
├── EURUSD_1h_xgboost.joblib
|
||
└── EURUSD_1h_xgboost_meta.json
|
||
```
|
||
Chargement automatique au démarrage si `auto_load=True`.
|