Compare commits
2 Commits
8f3b026f82
...
8732acf3d0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8732acf3d0 | ||
|
|
daea333555 |
227
docs/CNN_ENSEMBLE_PLAN.md
Normal file
227
docs/CNN_ENSEMBLE_PLAN.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# Plan : CNN + Ensemble Multi-Signal
|
||||
|
||||
**Créé** : 2026-03-08
|
||||
**Statut** : Phase 4c — En cours de développement
|
||||
|
||||
---
|
||||
|
||||
## Concept
|
||||
|
||||
Coupler trois modèles complémentaires pour produire un signal de trading robuste :
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Signal final pondéré │
|
||||
│ trade si score > seuil (ex: 0.60) │
|
||||
│ │
|
||||
│ score = w1×XGB_conf + w2×CNN_conf (+ w3×RL_conf) │
|
||||
│ (ex: 0.40 × 0.72 + 0.60 × 0.68 = 0.70) │
|
||||
└──────────┬──────────────────────┬───────────────────┘
|
||||
│ │
|
||||
┌──────▼──────┐ ┌──────▼──────┐
|
||||
│ XGBoost │ │ CNN │
|
||||
│ (Phase 4b) │ │ (Phase 4c) │
|
||||
│ │ │ │
|
||||
│ 50 features│ │ Fenêtre │
|
||||
│ TA calculés│ │ 64 bougies │
|
||||
│ (RSI, MACD,│ │ OHLCV → │
|
||||
│ pivots...)│ │ séquence │
|
||||
│ │ │ 1D CNN │
|
||||
│ "indicat." │ │ "visuel" │
|
||||
└─────────────┘ └─────────────┘
|
||||
│ (Phase 4d)
|
||||
┌──────▼──────┐
|
||||
│ RL │
|
||||
│ (futur) │
|
||||
│ Récompense │
|
||||
│ = PnL réel │
|
||||
│ Apprend par│
|
||||
│ essai/erreur│
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
### Complémentarité des modèles
|
||||
|
||||
| Composant | Ce qu'il voit | Ce qu'il détecte | Limite |
|
||||
|---|---|---|---|
|
||||
| XGBoost | Indicateurs calculés | Combinaisons règles TA | Dépend des features choisies |
|
||||
| CNN | Séquence brute OHLCV | Patterns visuels (double bottom, squeeze, H&S...) | Besoin de beaucoup de data |
|
||||
| RL | Historique de ses trades | Ce qui rapporte sans règles | Instable, lent à converger |
|
||||
|
||||
**Principe de l'ensemble** : un signal qui passe deux (ou trois) filtres indépendants a une probabilité nettement plus élevée d'être correct qu'un signal issu d'un seul modèle.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4c — CNN (priorité immédiate)
|
||||
|
||||
### Architecture CNN
|
||||
|
||||
```
|
||||
Entrée : dernières 64 bougies OHLCV
|
||||
→ normalisation z-score par fenêtre
|
||||
→ shape : (batch, 64, 5) # seq_len=64, features=5 (OHLCV)
|
||||
|
||||
Conv1D(filters=32, kernel=3) → ReLU → MaxPool(2)
|
||||
Conv1D(filters=64, kernel=3) → ReLU → MaxPool(2)
|
||||
Conv1D(filters=128, kernel=3) → ReLU → GlobalAvgPool
|
||||
Dense(128) → Dropout(0.3)
|
||||
Dense(3) → Softmax # [LONG, SHORT, NEUTRAL]
|
||||
```
|
||||
|
||||
Pas de conversion en image 2D — la CNN 1D sur séquences OHLCV est plus naturelle
|
||||
et plus performante pour les séries temporelles financières.
|
||||
|
||||
### Dépendance PyTorch
|
||||
|
||||
Le CNN requiert PyTorch. Il faut :
|
||||
1. Ajouter `torch==2.1.0+cpu` dans `docker/requirements/api.txt`
|
||||
*(CPU only — pas de GPU requis pour l'inférence en trading)*
|
||||
2. Rebuilder l'image : `docker compose build --no-cache trading-api`
|
||||
|
||||
### Fichiers à créer — CNN
|
||||
|
||||
```
|
||||
src/ml/cnn/
|
||||
├── __init__.py
|
||||
├── candlestick_encoder.py # Normalisation + préparation séquences OHLCV
|
||||
├── cnn_model.py # Architecture PyTorch (1D CNN)
|
||||
└── cnn_strategy_model.py # Wrapper train/predict/save/load (comme MLStrategyModel)
|
||||
|
||||
src/strategies/cnn_driven/
|
||||
├── __init__.py
|
||||
└── cnn_strategy.py # CNNDrivenStrategy (hérite BaseStrategy)
|
||||
|
||||
models/cnn_strategy/ # Sauvegardes .pt + _meta.json
|
||||
```
|
||||
|
||||
### Routes API à ajouter — CNN
|
||||
|
||||
| Méthode | Route | Description |
|
||||
|---|---|---|
|
||||
| POST | `/trading/train-cnn` | Lance entraînement CNN async |
|
||||
| GET | `/trading/train-cnn/{job_id}` | Statut + métriques |
|
||||
| GET | `/trading/cnn-models` | Liste modèles disponibles |
|
||||
|
||||
### Métriques cibles CNN
|
||||
|
||||
- `wf_accuracy > 0.52` (plus difficile que XGBoost — séquences brutes)
|
||||
- Distribution LONG/SHORT équilibrée (même méthode labels que XGBoost)
|
||||
- `wf_precision > 0.48` sur signaux directionnels
|
||||
|
||||
---
|
||||
|
||||
## Phase 4c — Ensemble XGBoost + CNN
|
||||
|
||||
### Logique de combinaison
|
||||
|
||||
```python
|
||||
# Les deux modèles prédisent indépendamment
|
||||
xgb_result = xgb_model.predict(df) # {'signal': 1, 'confidence': 0.72}
|
||||
cnn_result = cnn_model.predict(df) # {'signal': 1, 'confidence': 0.68}
|
||||
|
||||
# Score pondéré (seulement si même direction)
|
||||
if xgb_result['signal'] == cnn_result['signal']:
|
||||
score = w_xgb * xgb_result['confidence'] + w_cnn * cnn_result['confidence']
|
||||
if score >= min_confidence:
|
||||
→ signal validé (beaucoup plus fiable)
|
||||
else:
|
||||
→ NEUTRAL (désaccord entre modèles)
|
||||
```
|
||||
|
||||
### Fichiers à créer — Ensemble
|
||||
|
||||
```
|
||||
src/ml/ensemble/
|
||||
├── __init__.py
|
||||
├── ensemble_model.py # Combine XGBoost + CNN (+ RL futur)
|
||||
└── ensemble_config.py # Poids configurables par défaut
|
||||
|
||||
src/strategies/ensemble/
|
||||
├── __init__.py
|
||||
└── ensemble_strategy.py # EnsembleStrategy (hérite BaseStrategy)
|
||||
```
|
||||
|
||||
### Routes API à ajouter — Ensemble
|
||||
|
||||
| Méthode | Route | Description |
|
||||
|---|---|---|
|
||||
| POST | `/trading/ensemble/configure` | Définir les poids (xgb/cnn) |
|
||||
| GET | `/trading/ensemble/signal` | Signal combiné en temps réel |
|
||||
| GET | `/trading/ensemble/status` | Statut de chaque modèle de l'ensemble |
|
||||
|
||||
### Configuration par défaut
|
||||
|
||||
```json
|
||||
{
|
||||
"weights": {
|
||||
"xgboost": 0.40,
|
||||
"cnn": 0.60
|
||||
},
|
||||
"min_confidence": 0.60,
|
||||
"require_agreement": true
|
||||
}
|
||||
```
|
||||
|
||||
*Poids CNN légèrement supérieurs car il voit les patterns bruts sans nos biais de feature engineering.*
|
||||
|
||||
---
|
||||
|
||||
## Phase 4d — RL (après 4c validée)
|
||||
|
||||
Implémentation après validation CNN + Ensemble en paper trading (≥ 2 semaines).
|
||||
|
||||
### Environnement RL
|
||||
|
||||
```
|
||||
Framework : gymnasium (OpenAI Gym successor)
|
||||
Algorithme : PPO (Proximal Policy Optimization) — stable et adapté au trading
|
||||
|
||||
État : [dernières 64 bougies OHLCV + features XGBoost + signal CNN]
|
||||
Action : {HOLD, LONG, SHORT, CLOSE}
|
||||
Récompense : PnL réel net de frais, avec pénalité sur drawdown
|
||||
|
||||
Entraînement : sur données historiques 3 ans (simulation)
|
||||
Validation : walk-forward + paper trading
|
||||
```
|
||||
|
||||
### Intégration dans l'ensemble
|
||||
|
||||
```python
|
||||
# Phase 4d : triplet
|
||||
score = 0.30 * xgb_conf + 0.40 * cnn_conf + 0.30 * rl_conf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TODO — Phase 4c (par ordre)
|
||||
|
||||
### Étape 1 : Dépendance PyTorch
|
||||
- [ ] Ajouter `torch==2.1.0+cpu` dans `docker/requirements/api.txt`
|
||||
- [ ] Tester `docker compose build --no-cache trading-api` (peut prendre 10-15 min)
|
||||
|
||||
### Étape 2 : CNN core
|
||||
- [ ] `src/ml/cnn/candlestick_encoder.py` — normalisation z-score, padding, output shape (N, 64, 5)
|
||||
- [ ] `src/ml/cnn/cnn_model.py` — architecture PyTorch, forward(), train_epoch(), eval_epoch()
|
||||
- [ ] `src/ml/cnn/cnn_strategy_model.py` — train(), predict(), save(), load(), walk-forward eval
|
||||
|
||||
### Étape 3 : CNN strategy + API
|
||||
- [ ] `src/strategies/cnn_driven/cnn_strategy.py` — CNNDrivenStrategy
|
||||
- [ ] Routes `POST /trading/train-cnn`, `GET /trading/train-cnn/{job_id}`, `GET /trading/cnn-models`
|
||||
|
||||
### Étape 4 : Ensemble
|
||||
- [ ] `src/ml/ensemble/ensemble_model.py` — combine XGBoost + CNN
|
||||
- [ ] `src/strategies/ensemble/ensemble_strategy.py` — EnsembleStrategy
|
||||
- [ ] Routes `/trading/ensemble/*`
|
||||
|
||||
### Étape 5 : Validation
|
||||
- [ ] Entraîner CNN sur EURUSD/1h (2 ans)
|
||||
- [ ] Comparer backtest : Scalping vs XGBoost seul vs CNN seul vs Ensemble
|
||||
- [ ] Si Ensemble Sharpe > 0.8, démarrer paper trading ensemble
|
||||
|
||||
---
|
||||
|
||||
## Historique
|
||||
|
||||
| Date | Version | Description |
|
||||
|---|---|---|
|
||||
| 2026-03-08 | v0.1 | Plan initial — architecture CNN + Ensemble + RL (futur) |
|
||||
@@ -110,7 +110,7 @@ Voir [docs/ML_STRATEGY_GUIDE.md](ML_STRATEGY_GUIDE.md) pour la documentation com
|
||||
| Composant | Fichier | Statut |
|
||||
|---|---|---|
|
||||
| TechnicalFeatureBuilder (~50 features) | `src/ml/features/technical_features.py` | ✅ |
|
||||
| LabelGenerator (forward simulation) | `src/ml/features/label_generator.py` | ✅ |
|
||||
| LabelGenerator (forward simulation) | `src/ml/features/label_generator.py` | ✅ fix bug SHORT (2026-03-08) |
|
||||
| MLStrategyModel (XGBoost/LightGBM) | `src/ml/ml_strategy_model.py` | ✅ |
|
||||
| MLDrivenStrategy (hérite BaseStrategy) | `src/strategies/ml_driven/ml_strategy.py` | ✅ |
|
||||
| Route POST /trading/train | `src/api/routers/trading.py` | ✅ |
|
||||
@@ -121,6 +121,32 @@ Voir [docs/ML_STRATEGY_GUIDE.md](ML_STRATEGY_GUIDE.md) pour la documentation com
|
||||
|
||||
---
|
||||
|
||||
## Phase 4c — CNN + Ensemble 🟡 (En cours)
|
||||
|
||||
CNN 1D sur séquences brutes OHLCV + combinaison pondérée avec XGBoost.
|
||||
Voir [docs/CNN_ENSEMBLE_PLAN.md](CNN_ENSEMBLE_PLAN.md) pour l'architecture complète.
|
||||
|
||||
| Composant | Fichier | Statut |
|
||||
|---|---|---|
|
||||
| PyTorch CPU dans requirements | `docker/requirements/api.txt` | 🟡 |
|
||||
| CandlestickEncoder (normalisation séquences) | `src/ml/cnn/candlestick_encoder.py` | 🟡 |
|
||||
| CNNModel (1D Conv PyTorch) | `src/ml/cnn/cnn_model.py` | 🟡 |
|
||||
| CNNStrategyModel (train/predict/save/load) | `src/ml/cnn/cnn_strategy_model.py` | 🟡 |
|
||||
| CNNDrivenStrategy (hérite BaseStrategy) | `src/strategies/cnn_driven/cnn_strategy.py` | 🟡 |
|
||||
| Routes API CNN (train, status, list) | `src/api/routers/trading.py` | 🟡 |
|
||||
| EnsembleModel (XGBoost + CNN pondérés) | `src/ml/ensemble/ensemble_model.py` | 🟡 |
|
||||
| EnsembleStrategy (hérite BaseStrategy) | `src/strategies/ensemble/ensemble_strategy.py` | 🟡 |
|
||||
| Routes API Ensemble (configure, signal) | `src/api/routers/trading.py` | 🟡 |
|
||||
|
||||
---
|
||||
|
||||
## Phase 4d — RL (Planifié, après 4c validée)
|
||||
|
||||
Agent RL (PPO via gymnasium) intégré à l'ensemble comme troisième signal.
|
||||
Voir [docs/CNN_ENSEMBLE_PLAN.md](CNN_ENSEMBLE_PLAN.md) section Phase 4d.
|
||||
|
||||
---
|
||||
|
||||
## Routes API — État Complet
|
||||
|
||||
| Méthode | Route | Statut |
|
||||
|
||||
@@ -140,30 +140,59 @@ class LabelGenerator:
|
||||
sl_short: float,
|
||||
) -> int:
|
||||
"""
|
||||
Parcourt les barres futures bar par bar et retourne le label.
|
||||
Vérifie HIGH pour TP LONG et LOW pour SL LONG (et inversement pour SHORT).
|
||||
Simule LONG et SHORT de façon indépendante sur les barres futures.
|
||||
|
||||
LONG et SHORT sont deux trades hypothétiques distincts : le SL du LONG
|
||||
(prix baisse) ne signifie pas que le SL du SHORT (prix monte) est touché.
|
||||
Les deux simulations sont donc parcourues séparément pour éviter de
|
||||
manquer les signaux SHORT quand le prix descend.
|
||||
|
||||
Retourne le label du trade gagnant qui se résout en premier :
|
||||
1 (LONG), -1 (SHORT) ou 0 (NEUTRAL).
|
||||
"""
|
||||
for _, bar in future.iterrows():
|
||||
# LONG : TP atteint ?
|
||||
if bar['high'] >= tp_long and bar['low'] > sl_long:
|
||||
return 1
|
||||
# LONG : SL atteint en premier ?
|
||||
if bar['low'] <= sl_long:
|
||||
# Vérifie si TP atteint le même bar (candle ambiguë)
|
||||
if bar['high'] >= tp_long:
|
||||
return 0 # Ambigu → neutre
|
||||
return 0 # SL touché → pas de LONG
|
||||
# --- Simulation LONG indépendante ---
|
||||
long_win_idx = None
|
||||
long_lose_idx = None
|
||||
for idx, (_, bar) in enumerate(future.iterrows()):
|
||||
tp_hit = bar['high'] >= tp_long
|
||||
sl_hit = bar['low'] <= sl_long
|
||||
if tp_hit and sl_hit:
|
||||
long_lose_idx = idx # Barre ambiguë → perte
|
||||
break
|
||||
if tp_hit:
|
||||
long_win_idx = idx
|
||||
break
|
||||
if sl_hit:
|
||||
long_lose_idx = idx
|
||||
break
|
||||
|
||||
# SHORT : TP atteint ?
|
||||
if bar['low'] <= tp_short and bar['high'] < sl_short:
|
||||
return -1
|
||||
# SHORT : SL atteint en premier ?
|
||||
if bar['high'] >= sl_short:
|
||||
if bar['low'] <= tp_short:
|
||||
return 0
|
||||
return 0
|
||||
# --- Simulation SHORT indépendante ---
|
||||
short_win_idx = None
|
||||
short_lose_idx = None
|
||||
for idx, (_, bar) in enumerate(future.iterrows()):
|
||||
tp_hit = bar['low'] <= tp_short
|
||||
sl_hit = bar['high'] >= sl_short
|
||||
if tp_hit and sl_hit:
|
||||
short_lose_idx = idx # Barre ambiguë → perte
|
||||
break
|
||||
if tp_hit:
|
||||
short_win_idx = idx
|
||||
break
|
||||
if sl_hit:
|
||||
short_lose_idx = idx
|
||||
break
|
||||
|
||||
return 0 # Ni TP ni SL atteint dans l'horizon
|
||||
long_won = long_win_idx is not None
|
||||
short_won = short_win_idx is not None
|
||||
|
||||
if long_won and not short_won:
|
||||
return 1
|
||||
if short_won and not long_won:
|
||||
return -1
|
||||
if long_won and short_won:
|
||||
# Les deux trades seraient gagnants : prendre celui qui se résout en premier
|
||||
return 1 if long_win_idx <= short_win_idx else -1
|
||||
return 0 # Aucun TP atteint dans l'horizon
|
||||
|
||||
@staticmethod
|
||||
def _log_distribution(labels: pd.Series) -> None:
|
||||
|
||||
Reference in New Issue
Block a user