feat: ML-Driven Strategy — apprentissage des patterns TA humains
Nouveau module complet pour entraîner un modèle XGBoost/LightGBM
qui apprend à détecter des opportunités depuis des indicateurs classiques :
RSI (divergences), MACD (crossovers), Bollinger (squeeze/rebond),
Supports/Résistances (pivots locaux), Points Pivots (classiques + Fibonacci),
patterns chandeliers (marteau, engulfing), alignement EMAs, volume.
Fichiers créés :
- src/ml/features/technical_features.py (~50 features TA)
- src/ml/features/label_generator.py (labels LONG/SHORT/NEUTRAL par forward simulation ATR)
- src/ml/ml_strategy_model.py (entraînement + walk-forward + sauvegarde joblib)
- src/strategies/ml_driven/ml_strategy.py (stratégie compatible StrategyEngine)
Routes API ajoutées :
- POST /trading/train (entraînement async)
- GET /trading/train/{job_id} (état du job)
- GET /trading/ml-models (liste modèles disponibles)
- GET /trading/ml-models/{symbol}/{tf}/importance (feature importance)
Documentation : docs/ML_STRATEGY_GUIDE.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
306
docs/ML_STRATEGY_GUIDE.md
Normal file
306
docs/ML_STRATEGY_GUIDE.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# ML-Driven Strategy — Guide Complet
|
||||
|
||||
## Concept
|
||||
|
||||
La stratégie ML-Driven remplace les règles codées en dur par un modèle
|
||||
d'apprentissage supervisé (XGBoost/LightGBM) qui apprend à reconnaître
|
||||
les patterns utilisés par les traders humains :
|
||||
|
||||
- Rebonds sur supports / résistances
|
||||
- Divergences RSI / MACD
|
||||
- Squeeze Bollinger + expansion
|
||||
- Alignement EMAs (tendance)
|
||||
- Patterns de chandeliers (marteau, engulfing, étoile filante...)
|
||||
- Proximité des pivots classiques et Fibonacci
|
||||
|
||||
Le modèle apprend **quelles combinaisons** de ces signaux sont réellement
|
||||
prédictives sur les données historiques, ce qu'un trader expérimenté ferait
|
||||
intuitivement après des années de pratique.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
src/
|
||||
├── ml/
|
||||
│ ├── features/
|
||||
│ │ ├── technical_features.py # Calcul de ~50 features TA
|
||||
│ │ └── label_generator.py # Labels LONG/SHORT/NEUTRAL (forward simulation)
|
||||
│ └── ml_strategy_model.py # Entraînement XGBoost + sauvegarde
|
||||
└── strategies/
|
||||
└── ml_driven/
|
||||
└── ml_strategy.py # Stratégie compatible StrategyEngine
|
||||
```
|
||||
|
||||
### Pipeline de données
|
||||
|
||||
```
|
||||
Données OHLCV (2 ans, 1h)
|
||||
↓
|
||||
TechnicalFeatureBuilder (~50 features par barre)
|
||||
↓
|
||||
LabelGenerator (forward simulation : TP/SL atteint dans N barres ?)
|
||||
↓
|
||||
XGBoost (entraînement supervisé + walk-forward validation)
|
||||
↓
|
||||
MLStrategyModel sauvegardé sur disque (models/ml_strategy/)
|
||||
↓
|
||||
MLDrivenStrategy.analyze() → Signal(LONG/SHORT) si confidence >= seuil
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Features calculées
|
||||
|
||||
### RSI
|
||||
| Feature | Description |
|
||||
|---|---|
|
||||
| `rsi` | Valeur brute RSI(14) |
|
||||
| `rsi_oversold` | RSI < 30 (zone survente) |
|
||||
| `rsi_overbought` | RSI > 70 (zone surachat) |
|
||||
| `rsi_slope` | Pente RSI sur 3 barres |
|
||||
| `rsi_bullish_div` | Divergence haussière (prix LL, RSI HL) |
|
||||
| `rsi_bearish_div` | Divergence baissière (prix HH, RSI LH) |
|
||||
|
||||
### MACD
|
||||
| Feature | Description |
|
||||
|---|---|
|
||||
| `macd` | Ligne MACD (EMA12 - EMA26) |
|
||||
| `macd_signal` | Ligne signal (EMA9 du MACD) |
|
||||
| `macd_hist` | Histogramme |
|
||||
| `macd_hist_slope` | Pente histogramme (momentum) |
|
||||
| `macd_cross_up` | Crossover haussier |
|
||||
| `macd_cross_down` | Crossover baissier |
|
||||
|
||||
### Bollinger Bands
|
||||
| Feature | Description |
|
||||
|---|---|
|
||||
| `bb_position` | Position relative dans les bandes [0..1] |
|
||||
| `bb_bandwidth` | Largeur normalisée (indicateur de volatilité) |
|
||||
| `bb_squeeze` | Compression < percentile 20 (signal explosion) |
|
||||
| `bb_break_up/down` | Cassure de bande |
|
||||
| `bb_bounce_low/high` | Rebond depuis bande inf/sup |
|
||||
|
||||
### Supports / Résistances
|
||||
| Feature | Description |
|
||||
|---|---|
|
||||
| `dist_to_resistance` | Distance en ATR à la résistance la plus proche |
|
||||
| `dist_to_support` | Distance en ATR au support le plus proche |
|
||||
| `bounce_from_support` | Prix à < 1 ATR d'un support |
|
||||
| `rejection_at_resistance` | Prix à < 1 ATR d'une résistance |
|
||||
|
||||
### Points Pivots
|
||||
| Feature | Description |
|
||||
|---|---|
|
||||
| `dist_pivot` | Distance au pivot central |
|
||||
| `dist_r1/s1/r2/s2` | Distance aux niveaux classiques |
|
||||
| `dist_r1f/s1f` | Distance aux niveaux Fibonacci |
|
||||
| `near_pivot/r1/s1` | Prix dans une zone de 0.5 ATR |
|
||||
|
||||
### Chandeliers
|
||||
| Feature | Description |
|
||||
|---|---|
|
||||
| `hammer` | Marteau (rebond potentiel) |
|
||||
| `shooting_star` | Étoile filante (rejet potentiel) |
|
||||
| `bullish_engulfing` | Engulfing haussier |
|
||||
| `bearish_engulfing` | Engulfing baissier |
|
||||
| `doji` | Doji (indécision) |
|
||||
| `body_ratio` | Corps / mèche totale |
|
||||
|
||||
### Tendance / EMA
|
||||
| Feature | Description |
|
||||
|---|---|
|
||||
| `trend_bull` | EMA8 > EMA21 > EMA50 |
|
||||
| `trend_bear` | EMA8 < EMA21 < EMA50 |
|
||||
| `ema21_slope` | Pente EMA21 sur 5 barres |
|
||||
| `above_ema200` | Prix au-dessus de l'EMA200 |
|
||||
| `ema_X_dist` | Distance % du prix à chaque EMA |
|
||||
|
||||
### Temporel
|
||||
| Feature | Description |
|
||||
|---|---|
|
||||
| `is_london` | Session Londres (8h-16h UTC) |
|
||||
| `is_ny` | Session New York (13h-21h UTC) |
|
||||
| `is_overlap` | Chevauchement London+NY (13h-16h) |
|
||||
| `day_of_week` | Jour de la semaine |
|
||||
|
||||
---
|
||||
|
||||
## Génération des Labels
|
||||
|
||||
### Méthode ATR-based (recommandée)
|
||||
Pour chaque barre i, on simule un trade dans les `horizon` barres suivantes :
|
||||
- **LONG (1)** : le HIGH atteint `entry + tp_atr_mult × ATR` avant que le LOW descende sous `entry - sl_atr_mult × ATR`
|
||||
- **SHORT (-1)** : le LOW atteint `entry - tp_atr_mult × ATR` avant que le HIGH monte au-dessus de `entry + sl_atr_mult × ATR`
|
||||
- **NEUTRAL (0)** : ni TP ni SL atteint dans l'horizon
|
||||
|
||||
Paramètres par défaut : `tp_atr_mult=2.0`, `sl_atr_mult=1.0` → R:R = 2:1
|
||||
|
||||
### Méthode pourcentage fixe
|
||||
Même logique avec des seuils en % : `tp_pct=0.003` (0.3%), `sl_pct=0.002` (0.2%).
|
||||
|
||||
---
|
||||
|
||||
## Validation
|
||||
|
||||
Walk-forward cross-validation (3 folds temporels) :
|
||||
- Fold 1 : entraîné sur 0..33%, testé sur 33..67%
|
||||
- Fold 2 : entraîné sur 0..67%, testé sur 67..100%
|
||||
- etc.
|
||||
|
||||
Métriques retournées :
|
||||
- `wf_accuracy` : accuracy moyenne inter-folds
|
||||
- `wf_precision` : précision sur signaux directionnels
|
||||
- `wf_recall` : recall sur signaux directionnels
|
||||
- `label_dist` : distribution LONG/SHORT/NEUTRAL
|
||||
|
||||
---
|
||||
|
||||
## Routes API
|
||||
|
||||
### `POST /trading/train`
|
||||
Lance l'entraînement en arrière-plan.
|
||||
|
||||
**Body :**
|
||||
```json
|
||||
{
|
||||
"symbol": "EURUSD",
|
||||
"timeframe": "1h",
|
||||
"period": "2y",
|
||||
"model_type": "xgboost",
|
||||
"tp_atr_mult": 2.0,
|
||||
"sl_atr_mult": 1.0,
|
||||
"horizon": 30,
|
||||
"min_confidence": 0.55
|
||||
}
|
||||
```
|
||||
|
||||
**Réponse :**
|
||||
```json
|
||||
{
|
||||
"job_id": "uuid",
|
||||
"status": "pending",
|
||||
"symbol": "EURUSD",
|
||||
"timeframe": "1h"
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /trading/train/{job_id}`
|
||||
Consulter l'état d'un entraînement.
|
||||
|
||||
**Réponse (completed) :**
|
||||
```json
|
||||
{
|
||||
"job_id": "...",
|
||||
"status": "completed",
|
||||
"n_samples": 8760,
|
||||
"n_features": 52,
|
||||
"wf_accuracy": 0.58,
|
||||
"wf_precision": 0.61,
|
||||
"label_dist": {"long": 1200, "short": 1150, "neutral": 6410},
|
||||
"trained_at": "2026-03-08T12:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /trading/ml-models`
|
||||
Liste tous les modèles entraînés.
|
||||
|
||||
### `GET /trading/ml-models/{symbol}/{timeframe}/importance`
|
||||
Top features les plus importantes du modèle.
|
||||
|
||||
---
|
||||
|
||||
## Sauvegarde des Modèles
|
||||
|
||||
Les modèles sont sauvegardés dans `models/ml_strategy/` :
|
||||
```
|
||||
models/ml_strategy/
|
||||
├── EURUSD_1h_xgboost.joblib # Modèle + scaler + feature names
|
||||
└── EURUSD_1h_xgboost_meta.json # Métriques et configuration
|
||||
```
|
||||
|
||||
Au redémarrage, `MLDrivenStrategy` tente de charger automatiquement le modèle
|
||||
existant pour le symbole/timeframe configuré (`auto_load=True`).
|
||||
|
||||
---
|
||||
|
||||
## Utilisation Manuelle (Python)
|
||||
|
||||
```python
|
||||
from src.ml.ml_strategy_model import MLStrategyModel
|
||||
|
||||
# Entraînement
|
||||
model = MLStrategyModel(symbol='EURUSD', timeframe='1h', model_type='xgboost')
|
||||
metrics = model.train(df_ohlcv)
|
||||
print(f"WF Accuracy : {metrics['wf_metrics']['avg_accuracy']:.2%}")
|
||||
|
||||
# Prédiction
|
||||
result = model.predict(df_recent)
|
||||
print(f"Signal : {result['signal']}, Confidence : {result['confidence']:.2%}")
|
||||
# → {'signal': 1, 'confidence': 0.72, 'tradeable': True, 'probas': {...}}
|
||||
|
||||
# Chargement depuis disque
|
||||
model = MLStrategyModel.load('EURUSD', '1h', 'xgboost')
|
||||
|
||||
# Importance des features
|
||||
for f in model.get_feature_importance(top_n=10):
|
||||
print(f" {f['feature']}: {f['importance']:.4f}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Intégration avec la Stratégie
|
||||
|
||||
```python
|
||||
from src.strategies.ml_driven import MLDrivenStrategy
|
||||
|
||||
config = {
|
||||
'name': 'ml_driven',
|
||||
'symbol': 'EURUSD',
|
||||
'timeframe': '1h',
|
||||
'risk_per_trade': 0.01,
|
||||
'model_type': 'xgboost',
|
||||
'min_confidence': 0.55,
|
||||
'tp_atr_mult': 2.0,
|
||||
'sl_atr_mult': 1.0,
|
||||
'auto_load': True, # Charge automatiquement si modèle existant
|
||||
}
|
||||
strategy = MLDrivenStrategy(config)
|
||||
|
||||
# Génération d'un signal
|
||||
signal = strategy.analyze(df_ohlcv)
|
||||
if signal:
|
||||
print(f"{signal.direction} @ {signal.entry_price}, conf={signal.confidence:.2%}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Considérations
|
||||
|
||||
### Overfitting
|
||||
- La walk-forward validation (3 folds temporels) protège contre l'overfitting in-sample
|
||||
- Utiliser au minimum 1 an de données (≥ 8 000 barres en 1h)
|
||||
- Éviter les périodes trop courtes (`period < 6m`)
|
||||
|
||||
### Déséquilibre des classes
|
||||
- En général : ~15% LONG, ~15% SHORT, ~70% NEUTRAL
|
||||
- XGBoost gère naturellement le déséquilibre
|
||||
- Le seuil `min_confidence` filtre les signaux peu sûrs
|
||||
|
||||
### Re-entraînement
|
||||
- Recommandé tous les 3-6 mois pour capturer les changements de régime
|
||||
- Ou après un changement de volatilité significatif (crise, FOMC...)
|
||||
|
||||
### Métriques cibles
|
||||
- `wf_accuracy > 0.55` (mieux que le hasard)
|
||||
- `wf_precision > 0.50` sur signaux directionnels
|
||||
- Distribución LONG/SHORT relativement équilibrée
|
||||
|
||||
---
|
||||
|
||||
## Historique
|
||||
|
||||
| Date | Version | Description |
|
||||
|---|---|---|
|
||||
| 2026-03-08 | v1.0 | Création initiale — XGBoost/LightGBM + features TA classiques |
|
||||
Reference in New Issue
Block a user