QC-Py-Cloud-05 : Prevision par Reseau de Neurones (MLP)

< RL DQN | Retour au sommaire

Niveau : Avance | Duree estimee : 30 min | Kernel : Python 3

Objectifs

  • Comprendre l’ingenierie de features temporelles pour les series financieres
  • Implementer un classifieur MLP (Multi-Layer Perceptron) avec sklearn
  • Gerer le re-entrainement mensuel sur fenêtre glissante
  • Deployer et evaluer la stratégie multi-actifs sur QC Cloud

Partie 1 : Reseaux de Neurones pour la Prevision Financiere

Les reseaux de neurones artificiels (MLP) sont des approximateurs universels capables de capturer des relations non-lineaires dans les données financieres.

Le problème posé ici est volontairement modeste : prédire la direction du lendemain (hausse ou baisse), pas son ampleur. C’est ce qui le rend traitable — le signal directionnel est faible, à peine mieux qu’aléatoire, mais la classification binaire tolère ce bruit là où une régression sur le rendement exact échoue. Toute la difficulté est de construire des entrées qui exposent ce faible signal au classifieur : c’est le rôle de l’ingénierie de features de ce notebook.

Pourquoi MLP et non LSTM ?

Dans le contexte QC Cloud (sklearn uniquement, pas de PyTorch/TensorFlow) :

  1. Le MLP de sklearn est suffisant pour la classification binaire (hausse/baisse)
  2. Les features temporelles (lag returns, autocorrelation) compensent l’absence de memoire
  3. Le Pipeline sklearn (StandardScaler + MLP) est robuste et rapide a entrainer

Un LSTM apprend lui-même les dépendances temporelles ; le MLP, lui, est myope : il ne voit qu’un vecteur d’entrées à un instant donné. La mémoire perdue côté modèle doit donc être réinjectée côté données — c’est exactement ce que font les fenêtres glissantes des features qui suivent.

Architecture du modèle

Input (18 features)
  -> StandardScaler (normalisation)
  -> Dense(64, ReLU)
  -> Dense(32, ReLU)
  -> Output (P(hausse))

Les 18 features construites par le code se répartissent en six familles : lag returns (6), volatilité glissante (3), RSI (1), momentum cumulatif (3), autocorrelation (3) et mean return (2). La démonstration de la cellule suivante construit exactement ce vecteur et l’affiche : « Vecteur de features : 18 dimensions ».

# Demonstration : ingenierie de features temporelles
import numpy as np

np.random.seed(42)
returns = np.random.normal(0.0003, 0.01, 100)

def build_features(r, lookback=60):
    if len(r) < lookback + 5:
        return None
    r_window = r[-(lookback + 1):]
    features = []
    for lag in [1, 2, 3, 5, 10, 20]:
        features.append(r_window[-lag] if len(r_window) > lag else 0.0)
    for w in [5, 10, 20]:
        features.append(float(np.std(r_window[-w:])) if len(r_window) >= w else 0.0)
    d = r_window[-14:]
    gains = d[d > 0]
    losses = -d[d < 0]
    ag = float(np.mean(gains)) if len(gains) > 0 else 1e-8
    al = float(np.mean(losses)) if len(losses) > 0 else 1e-8
    rs = ag / (al + 1e-8)
    features.append(rs / (1.0 + rs))
    for w in [5, 10, 20]:
        features.append(float(np.sum(r_window[-w:])) if len(r_window) >= w else 0.0)
    for ac_lag in [1, 2, 5]:
        if len(r_window) > ac_lag:
            c = np.corrcoef(r_window[:-ac_lag], r_window[ac_lag:])
            val = float(c[0, 1])
            features.append(val if not np.isnan(val) else 0.0)
        else:
            features.append(0.0)
    for w in [5, 10]:
        features.append(float(np.mean(r_window[-w:])) if len(r_window) >= w else 0.0)
    return np.array(features)

feats = build_features(returns)
print(f"Vecteur de features : {len(feats)} dimensions")
print(f"Features : {[f'{v:.4f}' for v in feats[:6]]} ...")
print(f"\nLabel : 1 si rendement jour suivant > 0, sinon 0")
Vecteur de features : 18 dimensionsFeatures : ['-0.0020', '0.0004', '0.0029', '-0.0143', '0.0013', '-0.0019'] ...Label : 1 si rendement jour suivant > 0, sinon 0

Exercice 1 : Ingenierie de features temporelles

Le MLP utilise 18 features pour predire la direction du prix. Ces features incluent des lag returns, volatilite glissante, RSI, momentum et autocorrelation.

Objectif : Implementez une fonction build_lag_features(returns, lags=[1,2,3,5,10,20]) qui extrait les rendements retardees. Verifiez que les 6 premières features correspondent aux lag returns.

Indices : - Indice : lag_returns[i] = returns[-lag] pour chaque lag dans la liste. - Indice : Attention a l’indexation : returns[-1] est le dernier rendement, returns[-2] l’avant-dernier, etc.

Méthode : le vecteur complet de l’algorithme assemble six familles (lags, volatilité, RSI, momentum, autocorrélation, mean return) ; l’exercice n’en demande que la première, la plus directe. La démonstration ci-dessus (build_features) montre l’assemblage complet si vous souhaitez aller plus loin.

# Exercice 1 : Ingenierie de features temporelles
# TODO etudiant : implementer build_lag_features(returns, lags)
# Indice : lag_returns[i] = returns[-lag]

import numpy as np

def build_lag_features(returns, lags=[1, 2, 3, 5, 10, 20]):
    # TODO etudiant : extraire les rendements retardees
    return None  # TODO etudiant : retourner le vecteur de features

np.random.seed(42)
test_returns = np.random.normal(0.0003, 0.01, 100)
result_lags = None  # TODO etudiant : tester build_lag_features
print("Exercice a completer : features temporelles")

Lecture de la demonstration

La sortie confirme la construction : « Vecteur de features : 18 dimensions ». Les six premières valeurs affichées — [‘-0.0020’, ‘0.0004’, ‘0.0029’, ‘-0.0143’, ‘0.0013’, ‘-0.0019’] — sont les lag returns : le classifieur « voit » ainsi le passé récent directement dans son vecteur d’entrée. La dernière ligne rappelle la cible supervisée : « Label : 1 si rendement jour suivant > 0, sinon 0 » — c’est ce label binaire, décalé d’un jour, que le MLP apprend à prédire.

Chaque famille du vecteur capte une facette distincte de la dynamique des prix :

Famille Fenêtres Ce qu’elle expose au classifieur
Lag returns 1, 2, 3, 5, 10, 20 Le rendement récent lui-même (mémoire courte)
Volatilité glissante 5, 10, 20 Le régime de risque courant
RSI 14 La position dans le range récent (survente/surachat)
Momentum cumulatif 5, 10, 20 La tendance directionnelle multi-horizon
Autocorrélation 1, 2, 5 La persistance du signal (tendance vs bruit)
Mean return 5, 10 La dérive locale du prix

L’autocorrélation est la famille qui motive directement l’usage d’un modèle prédictif : si les rendements étaient indépendants (bruit pur), toute autocorrélation serait nulle et aucun modèle ne battrait le hasard. C’est cette hypothèse que le backtest de la Partie 4 met à l’épreuve.

Note de fidélité : la docstring historique de l’algorithme annonçait un vecteur de 20 éléments alors que le code en construit 18 — la « moyenne mobile » listée dans la prose d’origine n’existe pas dans le code. La prose de ce notebook est ré-ancrée sur le réel : 18 dimensions, six familles.

Partie 2 : Algorithme QuantConnect - MLP Forecasting v2.1

L’algorithme implemente : - 7 ETFs diversifies : SPY, QQQ, IWM, EFA, TLT, GLD, IEF - MLPClassifier (64, 32) par symbole avec StandardScaler - Re-entrainement mensuel sur fenêtre glissante de 252 jours - Seuil de probabilite 0.52, max 4 positions, min 2 positions

Trois de ces choix méritent d’être lus pour ce qu’ils sont — des compromis assumés :

  • 7 ETFs multi-classes (actions US petites et grandes, internationales, obligations, or) : un signal appris sur SPY seul sur-apprendrait son régime ; la diversification inter-classes donne au classifieur des régimes variés.
  • Fenêtre de 252 jours (~1 an boursier) : assez longue pour couvrir plusieurs régimes, assez courte pour que le re-entraînement mensuel suive les rotations.
  • Un modèle par symbole plutôt qu’un modèle global : chaque actif a sa propre structure de rendement ; le coût — 7 entraînements mensuels — reste négligeable sur Cloud.

Le code source complet est presente dans la section “Algorithme QuantConnect”, plus loin dans le notebook, apres l’exercice sur le seuil de probabilite.

Exercice 2 : Impact du seuil de probabilite

L’algorithme utilise un seuil de P(hausse) > 0.52 pour decider de prendre une position. Ce seuil filtre les predictions incertaines. Un seuil plus bas genere plus de trades, un seuil plus haut est plus selectif.

Objectif : Simulez 1000 predictions avec des probabilites entre 0.3 et 0.7. Pour différents seuils (0.50, 0.52, 0.55, 0.60), calculez le nombre de trades et le taux de reussite estime.

Indices : - Indice : Plus le seuil est eleve, moins de trades mais meilleur taux de reussite. - Indice : Le seuil optimal depend du cout de transaction (plus de trades = plus de frais).

Ce qu’on attend d’observer : à mesure que le seuil monte de 0.50 à 0.60, le nombre de trades retenus chute et le taux de réussite des trades restants monte — mais le portefeuille passe de « presque toujours investi » à « rarement investi ». Le seuil 0.52 de l’algorithme se situe volontairement près du bas de la plage : filtrer les prédictions les plus incertaines sans sacrifier l’exposition.

# Exercice 2 : Impact du seuil de probabilite
# TODO etudiant : simuler 1000 predictions et tester differents seuils
# Indice : seuil plus haut = moins de trades mais meilleur taux de reussite

import numpy as np

np.random.seed(42)
n_predictions = 1000
probabilities = np.random.uniform(0.3, 0.7, n_predictions)
true_directions = (np.random.random(n_predictions) < probabilities).astype(int)

def evaluate_threshold(probs, truths, threshold):
    # TODO etudiant : filtrer les predictions au-dessus du seuil
    # TODO etudiant : calculer le nombre de trades et le taux de reussite
    return None  # TODO etudiant : retourner (n_trades, win_rate)

result_threshold = None  # TODO etudiant : tester seuils 0.50, 0.52, 0.55, 0.60
print("Exercice a completer : seuil de probabilite")
# Code source de l'algorithme - pret pour deploiement QC Cloud
qc_code = '''#region imports
from AlgorithmImports import *
import numpy as np
from collections import deque
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# endregion


class LSTMForecasting(QCAlgorithm):
    """
    Neural Network (MLP) Forecasting Strategy - v2.1

    Real sklearn MLPClassifier replacing hand-rolled fake LSTM.
    Temporal features mimic LSTM's ability to capture sequential patterns.

    Reference: Hands-On AI Trading, Ch06/Ex07
    Version  : 2.1 - threshold 0.52, min 2 positions to reduce cash drag

    Architecture
    ------------
    - MLPClassifier, hidden layers (64, 32), relu activation
    - StandardScaler pre-processing in a Pipeline
    - Monthly re-training on a 252-day rolling window

    Features (20 per symbol):
    - Lag returns: 1, 2, 3, 5, 10, 20 days
    - Rolling vol: 5, 10, 20 days
    - RSI-like: 14-day normalised
    - Momentum: 5, 10, 20 day cumulative return
    - Auto-correlation: lag-1, lag-2, lag-5
    - Mean return: 5, 10 days

    Universe: SPY, QQQ, IWM, EFA, TLT, GLD, IEF
    Rebalance: every Monday
    Training : monthly (first rebalance of new month)

    Results (2015-2026):
    - Sharpe: 0.525 (vs 0.366 fake LSTM, +0.159)
    - CAGR: 11.3% (vs 9.75% fake LSTM)
    - MaxDD: 32.5% (vs 37.2% fake LSTM)
    - Alpha: +0.016 (vs -0.008 fake LSTM)
    - Beta: 0.544 (vs 0.886 fake LSTM - genuinely signal-driven)
    """

    def initialize(self):
        self.set_start_date(2015, 1, 1)
        self.set_end_date(2026, 1, 1)
        self.set_cash(100_000)

        # Universe - diversified liquid ETFs
        self._tickers = ["SPY", "QQQ", "IWM", "EFA", "TLT", "GLD", "IEF"]
        self._symbols = []
        for ticker in self._tickers:
            sym = self.add_equity(ticker, Resolution.DAILY).symbol
            self._symbols.append(sym)

        # Parameters
        self._lookback = 60          # days of history for feature building
        self._train_window = 252     # days of history for training
        self._threshold = 0.52       # minimum probability to trade
        self._max_positions = 4      # max long ETFs at once
        self._min_positions = 2      # always hold at least this many (top-N by score)

        # Per-symbol return deques
        self._return_windows = {
            sym: deque(maxlen=self._train_window + self._lookback + 5)
            for sym in self._symbols
        }

        # ROC indicators - one per symbol
        self._roc_indicators = {}
        for sym in self._symbols:
            roc = self.roc(sym, 1, Resolution.DAILY)

            def make_handler(s):
                def handler(ind, point):
                    if ind.is_ready:
                        self._return_windows[s].append(point.value)
                return handler

            roc.updated += make_handler(sym)
            self._roc_indicators[sym] = roc

        # ML models
        self._models = {sym: None for sym in self._symbols}
        self._models_trained = {sym: False for sym in self._symbols}
        self._last_train_month = -1

        # Warm-up: prime each ROC indicator individually
        warmup_days = self._train_window + self._lookback + 30
        for sym in self._symbols:
            roc = self._roc_indicators[sym]
            bars = self.history[TradeBar](sym, timedelta(days=warmup_days), Resolution.DAILY)
            for bar in bars:
                roc.update(bar.end_time, bar.close)

        # Weekly rebalance schedule (every Monday)
        self.schedule.on(
            self.date_rules.every(DayOfWeek.MONDAY),
            self.time_rules.after_market_open(self._symbols[0], 30),
            self._rebalance
        )

        self.log(
            "LSTMForecasting v2.1: MLPClassifier (64,32), 7-ETF, threshold=0.52, min_pos=2"
        )

    # ------------------------------------------------------------------
    # Feature engineering
    # ------------------------------------------------------------------

    def _build_features(self, returns_list):
        """
        Build an 18-element feature vector from a returns list.
        Returns None if not enough data.
        """
        if len(returns_list) < self._lookback + 5:
            return None

        r = np.array(returns_list[-(self._lookback + 1):])

        features = []

        # Lag returns: 1, 2, 3, 5, 10, 20
        for lag in [1, 2, 3, 5, 10, 20]:
            features.append(r[-lag] if len(r) > lag else 0.0)

        # Rolling volatility: 5, 10, 20
        for w in [5, 10, 20]:
            features.append(float(np.std(r[-w:])) if len(r) >= w else 0.0)

        # RSI-like (14-day), normalised 0-1
        if len(r) >= 14:
            d = r[-14:]
            gains = d[d > 0]
            losses = -d[d < 0]
            ag = float(np.mean(gains)) if len(gains) > 0 else 1e-8
            al = float(np.mean(losses)) if len(losses) > 0 else 1e-8
            rs = ag / (al + 1e-8)
            features.append(rs / (1.0 + rs))
        else:
            features.append(0.5)

        # Cumulative momentum: 5, 10, 20
        for w in [5, 10, 20]:
            features.append(float(np.sum(r[-w:])) if len(r) >= w else 0.0)

        # Auto-correlation at lags 1, 2, 5
        if len(r) >= 15:
            for ac_lag in [1, 2, 5]:
                if len(r) > ac_lag:
                    c = np.corrcoef(r[:-ac_lag], r[ac_lag:])
                    val = float(c[0, 1])
                    features.append(val if not np.isnan(val) else 0.0)
                else:
                    features.append(0.0)
        else:
            features.extend([0.0, 0.0, 0.0])

        # Mean return: 5, 10
        for w in [5, 10]:
            features.append(float(np.mean(r[-w:])) if len(r) >= w else 0.0)

        return np.array(features, dtype=float)

    def _build_training_data(self, returns_list):
        """Build (X, y) from returns list; label = 1 if next-day return > 0."""
        X, y = [], []
        r = list(returns_list)
        min_needed = self._lookback + 5
        for i in range(min_needed, len(r) - 1):
            feats = self._build_features(r[:i + 1])
            if feats is not None:
                X.append(feats)
                y.append(1 if r[i + 1] > 0 else 0)
        if len(X) < 30:
            return None, None
        return np.array(X), np.array(y)

    # ------------------------------------------------------------------
    # Training
    # ------------------------------------------------------------------

    def _train_all_models(self):
        """Train one MLP per symbol on recent history."""
        for sym in self._symbols:
            rl = list(self._return_windows[sym])
            if len(rl) < self._lookback + 40:
                continue
            recent = rl[-self._train_window:]
            X, y = self._build_training_data(recent)
            if X is None or len(X) < 30:
                continue
            if len(np.unique(y)) < 2:
                continue
            try:
                model = Pipeline([
                    ('scaler', StandardScaler()),
                    ('mlp', MLPClassifier(
                        hidden_layer_sizes=(64, 32),
                        activation='relu',
                        max_iter=300,
                        random_state=42,
                        early_stopping=True,
                        validation_fraction=0.15,
                        n_iter_no_change=15,
                        learning_rate_init=0.001
                    ))
                ])
                model.fit(X, y)
                self._models[sym] = model
                self._models_trained[sym] = True
                acc = float(np.mean(model.predict(X) == y))
                self.log(f"MLP trained {sym.value}: {len(X)} samples, acc={acc:.2%}")
            except Exception as exc:
                self.log(f"Training error {sym.value}: {exc}")

    # ------------------------------------------------------------------
    # Prediction
    # ------------------------------------------------------------------

    def _predict_proba(self, sym):
        """Return P(up) for sym; 0.5 if model not ready."""
        if not self._models_trained[sym]:
            return 0.5
        rl = list(self._return_windows[sym])
        feats = self._build_features(rl)
        if feats is None:
            return 0.5
        try:
            proba = self._models[sym].predict_proba(feats.reshape(1, -1))[0]
            return float(proba[1])
        except Exception as exc:
            self.log(f"Predict error {sym.value}: {exc}")
            return 0.5

    # ------------------------------------------------------------------
    # Rebalance
    # ------------------------------------------------------------------

    def _rebalance(self):
        """Weekly rebalance; monthly re-training."""
        current_month = self.time.month
        if current_month != self._last_train_month:
            self._last_train_month = current_month
            self._train_all_models()

        if not any(self._models_trained.values()):
            return

        # Score all symbols
        scores = {sym: self._predict_proba(sym) for sym in self._symbols}

        for sym, prob in scores.items():
            self.plot('MLP P(up)', sym.value, prob)

        # Rank all symbols by score
        ranked = sorted(self._symbols, key=lambda s: scores[s], reverse=True)

        # Above-threshold candidates
        bullish = [s for s in ranked if scores[s] >= self._threshold]
        bullish = bullish[:self._max_positions]

        # If fewer than min_positions above threshold, fill with top-ranked
        if len(bullish) < self._min_positions and any(self._models_trained.values()):
            for s in ranked:
                if s not in bullish:
                    bullish.append(s)
                if len(bullish) >= self._min_positions:
                    break

        target_weight = 1.0 / len(bullish) if bullish else 0.0

        # Exit positions not in selected
        for sym in self._symbols:
            if sym not in bullish:
                if self.portfolio[sym].invested:
                    self.liquidate(sym)

        # Enter / maintain selected positions
        for sym in bullish:
            self.set_holdings(sym, target_weight)

        best_sym = ranked[0] if ranked else None
        self.log(
            f"Rebalance {self.time.date()}: {len(bullish)} longs (min={self._min_positions}), "
            f"top={best_sym.value if best_sym else 'none'} p={scores.get(best_sym, 0):.2%}"
        )

    def on_end_of_algorithm(self):
        final_value = self.portfolio.total_portfolio_value
        total_return = (final_value - 100_000) / 100_000
        trained = sum(1 for v in self._models_trained.values() if v)
        self.log(
            f"v2.1 DONE: Final=${final_value:,.0f}, Return={total_return:.2%}, "
            f"Models trained: {trained}/{len(self._symbols)}"
        )

'''

print("Seuil : P(hausse) > 0.52, max 4 positions, min 2")
Seuil : P(hausse) > 0.52, max 4 positions, min 2

Architecture de l’algorithme

Composant Méthode Description
Features _build_features() 18 features temporelles
Entrainement _train_all_models() MLP mensuel par symbole
Prediction _predict_proba() P(hausse) par symbole
Sélection _rebalance() Top-N par score, min 2 positions

Paramètres cles

Paramètre Valeur Impact
threshold 0.52 Seuil de confiance pour le trade
max_positions 4 Diversification maximale
min_positions 2 Anti cash-drag
train_window 252 jours ~1 an de données d’entrainement
hidden_layers (64, 32) Capacite du reseau

La cellule ci-dessus n’exécute pas la stratégie : elle définit le code source comme une chaîne Python (qc_code) et en imprime la configuration. L’exécution réelle — entraînement des 7 MLP et backtest — se produit sur QuantConnect Cloud, pas dans le kernel local du notebook.


Partie 3 : Deploiement via MCP QuantConnect

Le workflow de déploiement est le même que pour le reste de la série Cloud-native :

  1. Projet QC : le code source de la cellule précédente est chargé dans un projet QuantConnect, via le serveur MCP dédié (jamais en REST direct).
  2. Compilation Cloud (create_compile) : QC Cloud compile l’algorithme dans son propre environnement, où sklearn et numpy sont préinstallés — aucune gestion d’environnement locale n’est nécessaire.
  3. Backtest (create_backtest puis read_backtest) : l’exécution consomme les données institutionnelles QC (dividendes, splits, ajustements) sur la période de référence et produit les métriques commentées en Partie 4.

Deux raisons de déléguer l’exécution au Cloud plutôt qu’au kernel local : les données (le notebook n’embarque pas d’historique multi-actifs) et la fidélité (l’algorithme tourne dans l’environnement exact de la production QC). C’est la ligne de partage de toute la série Cloud-native — le notebook enseigne et vérifie les briques localement, le Cloud exécute la stratégie entière.


Partie 4 : Résultats attendus et interpretation

Metriques de reference (v2.1, 2015-2026)

Metrique Valeur estimee Commentaire
Sharpe ~0.52 Ameliore vs fake LSTM (0.37)
CAGR ~11% Correct pour multi-asset
MaxDD ~33% Trop eleve, a ameliorer
Alpha ~+1.6% Signal predictif reel
Beta ~0.54 Pas un clone du marche

Comment lire ces métriques

  • Sharpe ~0.52 : un rendement ajusté du risque modéré mais positif — le signal directionnel apporte quelque chose au-dessus du hasard, sans être spectaculaire.
  • CAGR ~11% avec Beta ~0.54 : un peu plus de la moitié de la variance s’explique par l’exposition au marché ; l’Alpha ~+1.6% est la part réellement prédite — c’est lui qui justifie tout l’appareil de features.
  • MaxDD ~33% : le point faible assumé de la v2.1. La contrainte min_positions=2 maintient de l’exposition même quand peu de signaux dépassent le seuil : en régime baissier durable, cette exposition forcée amplifie la séquence de pertes.
  • vs fake LSTM (0.37) : la comparaison annoncée en Partie 1 — sans mémoire native, le MLP devait compenser par ses features ; le backtest confirme qu’il y réussit (0.37 -> 0.52).

Points d’attention

  • Le MLP est sensible aux hyperparametres (layers, learning rate)
  • Le seuil 0.52 est empirique : un backtest d’optimisation peut trouver mieux
  • Le re-entrainement mensuel est un compromis entre frais de calcul et fraicheur
  • La contrainte min_positions=2 reduit le cash drag mais augmente l’exposition

Conclusion

Ce dernier notebook de la serie Cloud-native demontre l’utilisation de reseaux de neurones pour la prevision financiere sur QuantConnect Cloud :

  1. Features temporelles : 18 features ingeniees pour compenser l’absence de LSTM
  2. MLP sklearn : Pipeline StandardScaler + MLPClassifier (64, 32)
  3. Re-entrainement mensuel : adaptation continue au regime de marche
  4. Multi-actifs : 7 ETFs avec sélection par score de probabilite
  5. Anti cash-drag : minimum 2 positions en permanence

En tant que dernier maillon, ce notebook referme la boucle ouverte par Cloud-01 : là où les premiers notebooks exécutaient des bribes d’API localement, celui-ci vérifie chaque brique (features, seuil, architecture) puis confie la stratégie entière à l’infrastructure Cloud.

La serie Cloud-native illustre le workflow complet notebook vers production : conception pedagogique -> code source QC -> deploiement MCP -> backtest Cloud.

Concept Outil QC Utilisation
Features _build_features() 18 features temporelles
MLP sklearn MLPClassifier Classification hausse/baisse
Pipeline StandardScaler + MLP Normalisation + modèle
Re-entrainement Schedule.month_start Mensuel sur fenêtre 252j
Sélection Seuil P>0.52 Top-N avec min positions

RL DQN < | Retour au sommaire

Exercice 3 : Entrainement d’un MLPClassifier

Le MLPClassifier de sklearn avec couches cachees (64, 32) est l’architecture utilisee dans l’algorithme. Il est reentraine mensuellement sur une fenêtre glissante de 252 jours.

Objectif : Generez un jeu de données synthetiques (X: 20 features, y: binaire) avec un signal non-lineaire. Entrainez un MLPClassifier avec StandardScaler et evaluez la precision. Comparez avec un classifieur lineaire (LogisticRegression).

Indices : - Indice : Utilisez sklearn.pipeline.Pipeline pour combiner StandardScaler et MLPClassifier. - Indice : Le MLP devrait surpasser la regression logistique sur des données non-lineaires.

Remarque : les 20 features de l’exercice sont un choix de dimension arbitraire — inutile de reproduire les 18 de l’algorithme. Ce qui compte ici est l’architecture (Pipeline + MLP) et sa comparaison à un classifieur linéaire sur un signal non-linéaire.

# Exercice 3 : Entrainement d'un MLPClassifier
# TODO etudiant : generer des donnees synthetiques et entrainer un MLP
# TODO etudiant : comparer avec une regression logistique
# Indice : Pipeline(StandardScaler + MLPClassifier) pour normalisation

import numpy as np

def generate_synthetic_data(n_samples=500, n_features=20):
    # TODO etudiant : generer X aleatoire et y avec un signal non-lineaire
    return None, None  # TODO etudiant : retourner X, y

result_mlp = None  # TODO etudiant : comparer MLP vs LogisticRegression
print("Exercice a completer : entrainement MLP")
Retour au sommet