Compare commits

...

2 Commits

Author SHA1 Message Date
Tika
8732acf3d0 docs: plan Phase 4c CNN+Ensemble + mise à jour PROJECT_STATUS
- Nouveau : docs/CNN_ENSEMBLE_PLAN.md — architecture complète CNN 1D + Ensemble pondéré + RL (Phase 4d)
- PROJECT_STATUS : Phase 4b marquée avec fix bug SHORT LabelGenerator
- PROJECT_STATUS : Phase 4c CNN+Ensemble et Phase 4d RL ajoutées

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 22:24:16 +00:00
Tika
daea333555 fix: LabelGenerator _classify_bar — simulation LONG/SHORT indépendante
Bug : quand le SL LONG était touché, la fonction retournait 0 (NEUTRAL)
immédiatement sans évaluer les conditions SHORT. Résultat : 0 labels SHORT
sur 12230 barres, modèle inutilisable pour signaux SHORT.

Fix : deux boucles indépendantes (LONG et SHORT) qui évaluent chacune
leur propre TP/SL. Si les deux gagnent, priorité au premier résolu.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 22:22:27 +00:00
3 changed files with 304 additions and 22 deletions

227
docs/CNN_ENSEMBLE_PLAN.md Normal file
View 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) |

View File

@@ -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 |

View File

@@ -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: