<< Sommaire QC | Précédent : QC-Py-01-Setup << | Suivant : QC-Py-03-Data-Management >>

QC-Py-02 : QuantConnect Platform Fundamentals - QCAlgorithm Lifecycle

Introduction

Dans ce notebook, nous allons approfondir les fondamentaux de la plateforme QuantConnect en explorant le cycle de vie de la classe QCAlgorithm, qui constitue le coeur de toute stratégie de trading algorithmique sur QuantConnect.

Récapitulatif QC-Py-01

Dans le notebook précédent, nous avons : - Créé un compte QuantConnect - Exploré l’interface web - Créé et exécuté notre premier algorithme “Hello World” - Compris la différence entre backtest et live trading

Objectifs de ce notebook

À la fin de ce notebook, vous serez capable de :

  1. Maîtriser le lifecycle QCAlgorithm (Initialize, OnData, OnEndOfAlgorithm)
  2. Comprendre la gestion des securities (AddEquity, AddCrypto, etc.)
  3. Créer des stratégies simples avec indicateurs techniques intégrés
  4. Découvrir les principales méthodes de QCAlgorithm

Prérequis

  • Notebook QC-Py-01 complété (compte QuantConnect créé)
  • Python niveau intermédiaire
  • Notions de base en trading (optionnel mais utile)

Plan du notebook

  1. QCAlgorithm Lifecycle (15 min)
  2. Gestion des Securities (15 min)
  3. Indicateurs Techniques Intégrés (15 min)
  4. Exemple Complet : RSI Mean Reversion (15 min)
  5. Méthodes QCAlgorithm Essentielles (10 min)
  6. Conclusion (5 min)

Durée estimée : 60 minutes

Note de conception : Ce notebook contient du code de reference a copier dans QuantConnect Lab (main.py). Les cellules ne sont pas prevues pour etre executees en tant que notebook Jupyter. L’absence d’outputs (execution_count: null) est intentionnelle.

[REFERENCE QC Cloud] Ce notebook illustre du code QuantConnect a executer dans l’IDE Cloud (https://www.quantconnect.com/research). L’environnement local ne dispose pas de QuantBook ni de l’historical data feed. Pour executer : cloner le projet QC associe, ouvrir research.ipynb, executer cellule par cellule.


Mode d’emploi : Ce notebook est un document de reference (non executable). Le code QCAlgorithm presente ici est a copier dans main.py de votre projet QuantConnect Lab. Ne tentez pas de faire “Run All” – les imports AlgorithmImports n’existent pas en Jupyter local.

Pour demarrer un projet QC : copiez un main.py depuis projects/ ou partner-course-quant-trading/templates/, puis adaptez-le.


2. QCAlgorithm Lifecycle

Vue d’ensemble du cycle de vie

Toute stratégie QuantConnect hérite de la classe QCAlgorithm et implémente trois méthodes principales qui définissent le cycle de vie de l’algorithme :

flowchart TD
    S["BACKTEST START"] --> I["Initialize()<br/>• Appelé UNE SEULE FOIS au début<br/>• Configuration dates, capital<br/>• Ajout des securities<br/>• Création des indicateurs"]
    I --> O["OnData(data)<br/>• Appelé à CHAQUE nouvelle donnée<br/>• Logique de trading<br/>• Décisions d'achat/vente"]
    O -- "boucle principale, à chaque barre" --> O
    O --> E["OnEndOfAlgorithm()<br/>• Appelé UNE FOIS à la fin<br/>• Nettoyage<br/>• Logs finaux"]
    E --> T["BACKTEST END"]

Les trois méthodes clés

Méthode Appels Rôle Exemple d’utilisation
Initialize() 1 fois (début) Configuration initiale Définir dates, ajouter SPY, créer SMA(20)
OnData(data) À chaque tick/bar Logique de trading Si SMA croisé, acheter/vendre
OnEndOfAlgorithm() 1 fois (fin) Finalisation Logger le rendement final

Structure de base d’un algorithme

Voici le squelette minimal de tout algorithme QuantConnect :

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
# Structure de base d'un algorithme QuantConnect
# Note: Ce code est informatif, il ne peut pas s'exécuter localement
# Il doit être copié dans l'IDE QuantConnect

from AlgorithmImports import *

class BasicLifecycleAlgorithm(QCAlgorithm):
    
    def Initialize(self):
        """
        Appelé une fois au début du backtest.
        Configuration: dates, capital, securities, indicateurs.
        """
        # Définir la période de backtest
        self.SetStartDate(2015, 1, 1)
        self.SetEndDate(2024, 12, 31)
        
        # Définir le capital initial
        self.SetCash(100000)
        
        # Ajouter des actifs (securities)
        self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
        
        self.Log("Algorithm initialized")
    
    def OnData(self, data):
        """
        Appelé à chaque nouvelle donnée (tick, minute, hour, daily).
        Logique de trading ici.
        """
        # Vérifier que les données sont disponibles
        if not data.ContainsKey(self.symbol):
            return
        
        # Accéder au prix actuel
        price = data[self.symbol].Close
        
        # Exemple: acheter si on n'est pas investi
        if not self.Portfolio.Invested:
            self.SetHoldings(self.symbol, 1.0)
            self.Log(f"Bought SPY at ${price:.2f}")
    
    def OnEndOfAlgorithm(self):
        """
        Appelé à la fin du backtest.
        Nettoyage, logs finaux.
        """
        final_value = self.Portfolio.TotalPortfolioValue
        self.Log(f"Algorithm ended. Final Portfolio Value: ${final_value:.2f}")

Résultat du Backtest QC Cloud — BasicLifecycleAlgorithm (BT 517a23f820b3…, projet 31996105)

Sharpe CAGR MaxDD Trades
0.493 11.761% 33.5% 1

Fréquence d’appel de OnData selon Resolution

La méthode OnData est appelée selon la résolution (Resolution) choisie lors de l’ajout de la security :

Resolution Fréquence d’appel Exemple d’usage
Resolution.Tick À chaque tick (millisecondes) Scalping, market making
Resolution.Second Toutes les secondes Trading haute fréquence
Resolution.Minute Toutes les minutes Day trading
Resolution.Hour Toutes les heures Swing trading
Resolution.Daily Tous les jours (9h30 ET) Position trading

Important : Plus la résolution est fine, plus le backtest est lent et consomme de ressources. Commencez avec Resolution.Daily pour vos tests initiaux.

Debug avec self.Log() et self.Debug()

QuantConnect offre deux méthodes pour logger des informations :

# self.Log() - Visible dans les résultats du backtest
self.Log("Message important qui apparaîtra dans le rapport")

# self.Debug() - Visible dans la console de debug (temps réel)
self.Debug(f"Prix actuel: {price}")

Conseil : Utilisez self.Log() pour des événements importants (achats, ventes) et self.Debug() pour du debug détaillé.

3. Gestion des Securities

Qu’est-ce qu’une Security ?

Une Security (titre financier) représente un actif tradable sur QuantConnect. Avant de pouvoir trader un actif, vous devez l’ajouter à votre algorithme avec une méthode Add*.

Principales classes d’actifs

QuantConnect supporte plusieurs classes d’actifs :

Classe d’actif Méthode Exemple Résolution recommandée
Actions (Equity) AddEquity(ticker, resolution) AddEquity("AAPL", Resolution.Daily) Daily/Minute
Crypto AddCrypto(pair, resolution) AddCrypto("BTCUSD", Resolution.Hour) Hour/Minute
Forex AddForex(pair, resolution) AddForex("EURUSD", Resolution.Minute) Minute/Second
Options AddOption(underlying) AddOption("SPY") Minute (détaillé en QC-Py-06)
Futures AddFuture(ticker) AddFuture("ES") Minute (détaillé en QC-Py-07)

Symbol vs String ticker

Point clé : Les méthodes Add* retournent un objet Symbol, pas une simple string.

# ❌ MAUVAIS - utiliser directement le ticker
self.AddEquity("SPY", Resolution.Daily)
# Plus tard dans OnData...
price = data["SPY"].Close  # Peut causer des erreurs

# ✅ BON - stocker le Symbol
self.symbol_spy = self.AddEquity("SPY", Resolution.Daily).Symbol
# Plus tard dans OnData...
price = data[self.symbol_spy].Close  # Robuste

Le Symbol contient plus d’informations que le simple ticker (marché, type d’actif, etc.) et évite les ambiguïtés.

Exemples d’ajout de securities

Résultat du Backtest QC Cloud — MultiAssetAlgorithm (BT c3e7f9c329cf…, projet 31996194)

Sharpe CAGR MaxDD Trades
0.834 25.403% 40.3% 99
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
# Exemples d'ajout de différentes classes d'actifs
# Note: Code informatif pour QuantConnect IDE

from AlgorithmImports import *

class MultiAssetAlgorithm(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetEndDate(2024, 12, 31)
        self.SetCash(100000)
        
        # 1. Equity (actions)
        # SPY est un ETF très liquide qui réplique le S&P 500
        self.symbol_spy = self.AddEquity("SPY", Resolution.Daily).Symbol
        self.Log(f"Added SPY: {self.symbol_spy}")
        
        # 2. Crypto
        # BTCUSD = Bitcoin vs USD
        # Note: Résolution Hour car crypto trade 24/7
        self.symbol_btc = self.AddCrypto("BTCUSD", Resolution.Hour).Symbol
        self.Log(f"Added BTCUSD: {self.symbol_btc}")
        
        # 3. Forex
        # EURUSD = Euro vs US Dollar
        self.symbol_eur = self.AddForex("EURUSD", Resolution.Minute).Symbol
        self.Log(f"Added EURUSD: {self.symbol_eur}")
        
        # 4. Options (aperçu - détaillé dans QC-Py-06)
        # Ajoute les options disponibles sur SPY
        # self.option_spy = self.AddOption("SPY")
        # self.option_spy.SetFilter(-5, 5, 0, 30)  # Strike range, expiry days
        
        # 5. Futures (aperçu - détaillé dans QC-Py-07)
        # ES = E-mini S&P 500 futures
        # self.future_es = self.AddFuture("ES")
    
    def OnData(self, data):
        # Vérifier disponibilité des données pour chaque symbol
        if data.ContainsKey(self.symbol_spy):
            spy_price = data[self.symbol_spy].Close
            self.Debug(f"SPY: ${spy_price:.2f}")
        
        if data.ContainsKey(self.symbol_btc):
            btc_price = data[self.symbol_btc].Close
            self.Debug(f"BTC: ${btc_price:.2f}")

Exercice 1 : Portefeuille multi-actifs

La section précédente montre comment ajouter différents types d’actifs (equity, crypto, forex). Maintenant, construisez votre propre portefeuille diversifie en ajoutant plusieurs actions et en les repartissant equitablement.

Principe

Vous allez créer un algorithme qui : - Ajoute 3 actions de secteurs différents (par exemple technologie, sante, energie) - Repartit le capital equitablement (1/3 dans chaque) le premier jour de trading - Affiche un resume de la repartition avec self.Log()

Objectif

Completez les méthode Initialize et OnData de la classe MultiEquityPortfolio.

Indices :

  • Indice 1 : Stockez vos symbols dans une liste self.symbols = [] dans Initialize.
  • Indice 2 : Pour repartir equitablement, utilisez self.SetHoldings(symbol, 1.0 / len(self.symbols)) pour chaque symbol.
  • Indice 3 : Pour n’acheter qu’une seule fois, utilisez un flag self.invested = False et verifiez-le dans OnData.
# [REFERENCE QC] Exercice a completer - Code a copier dans main.py QC Lab
# Exercice 1 : Portefeuille multi-actifs diversifie

from AlgorithmImports import *

class MultiEquityPortfolio(QCAlgorithm):
    """
    Portefeuille diversifie avec 3 actions de secteurs differents.
    Le capital est reparti equitablement (1/3 par position).
    """
    
    def Initialize(self):
        self.SetStartDate(2020, 1, 1)
        self.SetEndDate(2024, 12, 31)
        self.SetCash(100000)
        
        # TODO etudiant : ajouter 3 actions de secteurs differents
        # Exemples : AAPL (tech), JNJ (sante), XOM (energie)
        # Stockez les symbols dans self.symbols = [...]
        
        # TODO etudiant : initialiser un flag self.invested = False
        
        pass  # TODO etudiant : remplacer par votre implementation
    
    def OnData(self, data):
        # TODO etudiant : verifier que toutes les donnees sont disponibles
        # (utiliser une boucle et data.ContainsKey(symbol))
        
        # TODO etudiant : si pas encore investi, repartir le capital equitablement
        # avec self.SetHoldings(symbol, 1.0 / len(self.symbols)) pour chaque symbol
        
        # TODO etudiant : logger la repartition avec self.Log()
        
        pass  # TODO etudiant : remplacer par votre implementation

print("Exercice a completer : MultiEquityPortfolio")

Warm-up period avec SetWarmUp()

Les indicateurs techniques (SMA, EMA, RSI, etc.) nécessitent un certain nombre de données historiques avant d’être prêts (“ready”). Sans warm-up, les premiers jours du backtest n’auront pas d’indicateurs valides.

Solution : Utiliser SetWarmUp() pour charger des données historiques avant le début du backtest.

def Initialize(self):
    self.SetStartDate(2020, 1, 1)
    self.SetCash(100000)
    
    self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
    
    # Créer un SMA de 50 jours
    self.sma_50 = self.SMA(self.symbol, 50)
    
    # Warm-up de 50 jours pour remplir le SMA
    self.SetWarmUp(50)

def OnData(self, data):
    # Ignorer les données de warm-up
    if self.IsWarmingUp:
        return
    
    # Vérifier que l'indicateur est prêt
    if not self.sma_50.IsReady:
        return
    
    # À ce stade, le SMA est valide
    sma_value = self.sma_50.Current.Value

Conseil : Toujours utiliser SetWarmUp() avec un nombre égal à la période du plus long indicateur.

Vérifier la disponibilité : data.ContainsKey(symbol)

Dans OnData, il faut toujours vérifier que les données sont disponibles pour un symbol donné :

def OnData(self, data):
    # ❌ MAUVAIS - peut crasher si les données manquent
    price = data[self.symbol].Close
    
    # ✅ BON - vérifier d'abord
    if data.ContainsKey(self.symbol):
        price = data[self.symbol].Close
    else:
        return  # Pas de données, on attend la prochaine itération

4. Indicateurs Techniques Intégrés

Bibliothèque d’indicateurs QuantConnect

QuantConnect intègre une bibliothèque complète d’indicateurs techniques. Plus besoin de coder les formules manuellement !

Principaux indicateurs disponibles

Indicateur Code Description Utilisation typique
SMA self.SMA(symbol, period) Simple Moving Average Tendance, support/résistance
EMA self.EMA(symbol, period) Exponential Moving Average Tendance (plus réactif que SMA)
RSI self.RSI(symbol, period) Relative Strength Index Surachat/survente (0-100)
MACD self.MACD(symbol, fast, slow, signal) Moving Average Convergence Divergence Momentum, changements de tendance
BB self.BB(symbol, period, std_dev) Bollinger Bands Volatilité, breakouts
ATR self.ATR(symbol, period) Average True Range Mesure de volatilité
STOCH self.STO(symbol, period) Stochastic Oscillator Momentum, retournements

La liste complète : QuantConnect Indicators Documentation

Résultat du Backtest QC Cloud — IndicatorsAlgorithm (BT 150fee29c7cc…, projet 31996292)

Sharpe CAGR MaxDD Trades
- - - 0 (demo)

Créer et utiliser des indicateurs

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
# Création d'indicateurs techniques dans Initialize()
# Note: Code informatif pour QuantConnect IDE

from AlgorithmImports import *

class IndicatorsAlgorithm(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetEndDate(2024, 12, 31)
        self.SetCash(100000)
        
        self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
        
        # 1. SMA (Simple Moving Average)
        # Moyenne mobile simple sur 20 jours
        self.sma_20 = self.SMA(self.symbol, 20, Resolution.Daily)
        
        # 2. EMA (Exponential Moving Average)
        # Moyenne mobile exponentielle sur 50 jours (plus réactive)
        self.ema_50 = self.EMA(self.symbol, 50)
        
        # 3. RSI (Relative Strength Index)
        # Oscillateur de momentum entre 0 et 100
        # Classiquement: RSI < 30 = survente, RSI > 70 = surachat
        self.rsi = self.RSI(self.symbol, 14)
        
        # 4. MACD (Moving Average Convergence Divergence)
        # fast=12, slow=26, signal=9 (paramètres standards)
        self.macd = self.MACD(self.symbol, 12, 26, 9)
        
        # 5. Bollinger Bands
        # period=20, std_dev=2 (paramètres standards)
        self.bb = self.BB(self.symbol, 20, 2)
        
        # Warm-up: utiliser la période du plus long indicateur
        self.SetWarmUp(50)
        
        self.Log("Indicators created successfully")
    
    def OnData(self, data):
        # Ignorer la période de warm-up
        if self.IsWarmingUp:
            return
        
        # Vérifier que tous les indicateurs sont prêts
        if not (self.sma_20.IsReady and self.ema_50.IsReady and 
                self.rsi.IsReady and self.macd.IsReady and self.bb.IsReady):
            return
        
        # Accéder aux valeurs des indicateurs
        sma_value = self.sma_20.Current.Value
        ema_value = self.ema_50.Current.Value
        rsi_value = self.rsi.Current.Value
        
        # MACD a plusieurs composantes
        macd_value = self.macd.Current.Value      # MACD line
        macd_signal = self.macd.Signal.Current.Value  # Signal line
        macd_hist = self.macd.Histogram.Current.Value  # Histogram
        
        # Bollinger Bands a 3 bandes
        bb_upper = self.bb.UpperBand.Current.Value
        bb_middle = self.bb.MiddleBand.Current.Value
        bb_lower = self.bb.LowerBand.Current.Value
        
        # Afficher les valeurs (debug)
        self.Debug(f"SMA(20): {sma_value:.2f} | EMA(50): {ema_value:.2f} | RSI: {rsi_value:.2f}")

Exercice 2 : Tableau de bord d’indicateurs

La section précédente montre comment créer et acceder aux indicateurs techniques. Votre mission est de construire un tableau de bord qui combine plusieurs indicateurs pour produire une recommandation synthetique.

Principe

Dans OnData, vous allez calculer un signal composite a partir de 3 indicateurs : - RSI : haussier si entre 40 et 70 - SMA(20) vs prix : haussier si prix > SMA(20) - MACD : haussier si MACD line > Signal line

Le signal composite compte le nombre d’indicateurs haussiers (0 a 3).

Objectif

Completez la méthode OnData pour : 1. Verifier que tous les indicateurs sont prets 2. Evaluer chaque indicateur individuellement 3. Calculer le score composite 4. Logger le résultat avec self.Log()

Indices :

  • Indice 1 : Utilisez un compteur simple (bullish_count = 0) et incrémentez-le pour chaque condition remplie.
  • Indice 2 : Pour comparer le prix au SMA, utilisez data[self.symbol].Close > self.sma_20.Current.Value.
  • Indice 3 : Le MACD haussier se verifie avec self.macd.Current.Value > self.macd.Signal.Current.Value.
# [REFERENCE QC] Exercice a completer - Code a copier dans main.py QC Lab
# Exercice 2 : Tableau de bord d'indicateurs composites

from AlgorithmImports import *

class IndicatorDashboard(QCAlgorithm):
    """
    Affiche un tableau de bord quotidien combinant RSI, SMA et MACD
    en un score composite de 0 (tous baissiers) a 3 (tous haussiers).
    """
    
    def Initialize(self):
        self.SetStartDate(2023, 1, 1)
        self.SetEndDate(2023, 12, 31)
        self.SetCash(100000)
        
        self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
        
        self.sma_20 = self.SMA(self.symbol, 20, Resolution.Daily)
        self.rsi = self.RSI(self.symbol, 14)
        self.macd = self.MACD(self.symbol, 12, 26, 9)
        
        self.SetWarmUp(50)
    
    def OnData(self, data):
        # TODO etudiant : ignorer le warmup et verifier que tous les indicateurs sont prets
        
        # TODO etudiant : evaluer le RSI (haussier si 40 < rsi < 70)
        
        # TODO etudiant : evaluer le prix vs SMA(20) (haussier si prix > sma)
        
        # TODO etudiant : evaluer le MACD (haussier si macd > signal)
        
        # TODO etudiant : calculer le score composite (0 a 3) et logger avec self.Log()
        
        pass  # TODO etudiant : remplacer par votre implementation

print("Exercice a completer : IndicatorDashboard")

Vérifier si ready : indicator.IsReady

Un indicateur n’est pas immédiatement prêt après sa création. Il faut attendre qu’il ait reçu assez de données.

# Créer un SMA(50)
self.sma_50 = self.SMA(self.symbol, 50)

# Dans OnData:
if self.sma_50.IsReady:
    value = self.sma_50.Current.Value  # ✅ Safe
else:
    self.Debug("SMA not ready yet")  # ⏳ Attendre

Important : Toujours vérifier IsReady avant d’accéder à la valeur, sinon vous risquez d’obtenir des valeurs incorrectes (NaN ou 0).

Accéder à la valeur : indicator.Current.Value

Pour obtenir la valeur actuelle d’un indicateur :

# Pour les indicateurs simples (SMA, EMA, RSI, ATR)
value = self.sma_20.Current.Value

# Pour les indicateurs complexes (MACD, BB, STOCH)
macd_line = self.macd.Current.Value
macd_signal = self.macd.Signal.Current.Value
macd_histogram = self.macd.Histogram.Current.Value

bb_upper = self.bb.UpperBand.Current.Value
bb_middle = self.bb.MiddleBand.Current.Value
bb_lower = self.bb.LowerBand.Current.Value

Visualiser avec self.Plot()

QuantConnect permet de créer des graphiques personnalisés avec self.Plot() :

def OnData(self, data):
    if not self.sma_20.IsReady:
        return
    
    price = data[self.symbol].Close
    
    # Créer un graphique avec le prix et le SMA
    self.Plot("Price vs SMA", "SPY Price", price)
    self.Plot("Price vs SMA", "SMA(20)", self.sma_20.Current.Value)
    
    # Créer un graphique séparé pour le RSI
    if self.rsi.IsReady:
        self.Plot("RSI", "RSI(14)", self.rsi.Current.Value)
        self.Plot("RSI", "Overbought", 70)  # Ligne de référence
        self.Plot("RSI", "Oversold", 30)    # Ligne de référence

Les graphiques apparaissent dans les résultats du backtest sous l’onglet “Charts”.

5. Exemple Complet : RSI Mean Reversion

Stratégie RSI Mean Reversion

Nous allons maintenant créer une stratégie complète basée sur le RSI (Relative Strength Index), un oscillateur très populaire.

Principe de la stratégie

Le RSI mesure la force du momentum d’un actif sur une échelle de 0 à 100 :

  • RSI < 30 : Zone de survente → Le prix a trop baissé, possibilité de rebond (signal d’achat)
  • RSI > 70 : Zone de surachat → Le prix a trop monté, possibilité de correction (signal de vente)

Cette stratégie suppose que les prix ont tendance à revenir vers la moyenne (mean reversion) après des mouvements extrêmes.

Règles de trading

SI RSI < 30 ET pas investi :
    ACHETER 100% du capital

SI RSI > 70 ET investi :
    VENDRE toute la position

Diagramme de la logique

        RSI(14)
          │
     ┌────┴────┐
     │         │
   < 30      > 70
     │         │
 OVERSOLD  OVERBOUGHT
     │         │
    BUY      SELL

Code complet de la stratégie

Copiez ce code dans l’IDE QuantConnect pour l’exécuter :

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
# Stratégie RSI Mean Reversion
# À copier dans l'IDE QuantConnect

from AlgorithmImports import *

class RSIMeanReversion(QCAlgorithm):
    """
    Stratégie de retour à la moyenne basée sur le RSI.

    Achète lorsque RSI < 30 (survente)
    Vend lorsque RSI > 70 (surachat)
    """

    def Initialize(self):
        # Configuration de base
        self.SetStartDate(2015, 1, 1)
        self.SetEndDate(2024, 12, 31)
        # Capital initial explicite : c'est cette valeur, pas une propriété
        # StartingCash inexistante, que OnEndOfAlgorithm relit en fin de run
        self.initial_capital = 100_000
        self.SetCash(self.initial_capital)

        # Ajouter SPY avec résolution journalière
        self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol

        # Créer l'indicateur RSI(14)
        # 14 est la période standard pour le RSI
        self.rsi = self.RSI(self.symbol, 14)

        # Warm-up de 14 jours pour initialiser le RSI
        self.SetWarmUp(14)

        self.Log("RSI Mean Reversion strategy initialized")
        self.Log(f"Backtest period: {self.StartDate} to {self.EndDate}")

    def OnData(self, data):
        # 1. Ignorer la période de warm-up
        if self.IsWarmingUp:
            return

        # 2. Vérifier que le RSI est prêt
        if not self.rsi.IsReady:
            return

        # 3. Vérifier la présence effective d'une barre SPY avant de lire son
        #    prix : sans ce garde, data[self.symbol].Close lève une erreur sur
        #    une slice qui ne contient pas de barre pour le symbole
        if self.symbol not in data or data[self.symbol] is None:
            return

        # 4. Obtenir la valeur actuelle du RSI
        rsi_value = self.rsi.Current.Value

        # 5. Obtenir le prix actuel
        price = data[self.symbol].Close

        # 6. Visualiser le RSI dans les graphiques
        self.Plot("RSI", "RSI(14)", rsi_value)
        self.Plot("RSI", "Overbought", 70)
        self.Plot("RSI", "Oversold", 30)

        # 7. Logique de trading

        # Signal d'ACHAT : RSI < 30 (survente) et pas déjà investi
        if rsi_value < 30 and not self.Portfolio.Invested:
            # Acheter 100% du capital disponible
            self.SetHoldings(self.symbol, 1.0)
            self.Log(f"BUY: RSI={rsi_value:.2f} | Price=${price:.2f}")

        # Signal de VENTE : RSI > 70 (surachat) et actuellement investi
        elif rsi_value > 70 and self.Portfolio.Invested:
            # Liquider toute la position
            self.Liquidate(self.symbol)
            self.Log(f"SELL: RSI={rsi_value:.2f} | Price=${price:.2f}")

    def OnEndOfAlgorithm(self):
        # Afficher les résultats finaux
        final_value = self.Portfolio.TotalPortfolioValue
        initial_value = self.initial_capital
        total_return = ((final_value - initial_value) / initial_value) * 100

        self.Log(f"")
        self.Log(f"="*50)
        self.Log(f"BACKTEST SUMMARY")
        self.Log(f"="*50)
        self.Log(f"Initial Capital: ${initial_value:,.2f}")
        self.Log(f"Final Portfolio Value: ${final_value:,.2f}")
        self.Log(f"Total Return: {total_return:.2f}%")
        self.Log(f"="*50)

Résultat du Backtest QC Cloud — RSIMeanReversion (BT 253cf5ff39f45442b5debdd79586f1aa, projet 31996392, run « RSIMeanReversion-v5-notebook-verbatim » du 2026-09-06) Période 2015-01-01 → 2024-12-31 (2 516 dates négociables) · capital initial explicite self.initial_capital = 100_000 · OnEndOfAlgorithm exécuté jusqu’au callback final

Sharpe CAGR MaxDD Trades
0.291 7.144% 28.8% 10 (20 ordres)

Historique : le run antérieur du même code avant capital explicite est conservé — BT 3b160a654712eb65581558346894dff8 (« RSIMeanReversion-v3-nullcheck », 2026-05-22) : 0.291 / 7.145% / 28.7% / 20 ordres. Les deux runs encore antérieurs sans garde de barre SPY (227257a1c92ca09bdfe9c84ad0c5c85f, a55b60f90276cf41092c87f985e6c53b, projet 31996392) sont en Runtime Error : c’est le garde « barre présente » du code ci-dessus qui distingue le run qui complète.

Exercice 3 : Stratégie SMA Crossover

Dans l’exemple précédent, nous avons implemente une stratégie RSI Mean Reversion. Maintenant, a vous de créer une stratégie basee sur le croisement de moyennes mobiles (SMA Crossover), l’une des stratégies de suivi de tendance les plus classiques.

Principe

  • Signal d’achat : quand la SMA courte (rapide) passe au-dessus de la SMA longue (lente)
  • Signal de vente : quand la SMA courte passe en dessous de la SMA longue

Objectif

Completez la classe SMACrossoverStrategy ci-dessous en implementant la logique de trading dans OnData.

Indices :

  • Indice 1 : Stockez l’etat précédent du croisement dans self.previous_cross pour detecter le changement de signal (passage de “en dessous” a “au-dessus” ou inversement).
  • Indice 2 : Comparez sma_fast.Current.Value avec sma_slow.Current.Value pour determiner la position relative.
  • Indice 3 : Utilisez self.SetHoldings(symbol, 1.0) pour acheter et self.Liquidate(symbol) pour vendre.
# [REFERENCE QC] Exercice a completer - Code a copier dans main.py QC Lab
# Exercice 3 : Strategie SMA Crossover

from AlgorithmImports import *

class SMACrossoverStrategy(QCAlgorithm):
    """
    Strategie de suivi de tendance basee sur le croisement de deux SMA.
    
    Achetez quand la SMA rapide (20) croise au-dessus de la SMA lente (50).
    Vendez quand la SMA rapide (20) croise en dessous de la SMA lente (50).
    """
    
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetEndDate(2024, 12, 31)
        self.SetCash(100000)
        
        self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
        
        # TODO etudiant : creer les deux indicateurs SMA
        # self.sma_fast = ...
        # self.sma_slow = ...
        
        # TODO etudiant : initialiser la variable de suivi du croisement precedent
        # self.previous_cross = ...  # True si fast > slow, False sinon, None au debut
        
        self.SetWarmUp(50)
        self.Log("SMA Crossover strategy initialized")
    
    def OnData(self, data):
        # TODO etudiant : verifier warmup et indicateurs prets
        
        # TODO etudiant : determiner si la SMA rapide est au-dessus de la SMA lente
        
        # TODO etudiant : detecter le croisement (changement d'etat) et executer les ordres
        
        # TODO etudiant : mettre a jour previous_cross
        
        pass  # TODO etudiant : remplacer par votre implementation

print("Exercice a completer : SMACrossoverStrategy")

Explication détaillée du code

Dans Initialize()

  1. Dates et capital : Backtest sur 10 ans (2015-2024) — ce sont les dates posées par SetStartDate(2015, 1, 1) / SetEndDate(2024, 12, 31) — avec 100 000 $ de capital initial rendu explicite par self.initial_capital. Il n’existe pas de propriété StartingCash lisible sur QCAlgorithm : startingCash est un argument de SetCash, pas une propriété publique — le code ci-dessus relit donc sa propre variable.
  2. Security : SPY (ETF S&P 500) avec résolution Daily
  3. Indicateur RSI(14) : Période standard de 14 jours
  4. Warm-up : 14 jours pour initialiser le RSI avant le début du backtest

Dans OnData()

  1. Vérifications préliminaires :
    • Skip la période de warm-up (IsWarmingUp)
    • Vérifier que le RSI est prêt (rsi.IsReady)
    • Vérifier qu’une barre SPY est bien présente dans la slice (self.symbol in data) avant de lire son prix — les runs sans ce garde terminent en Runtime Error sur le projet QC associé
  2. Récupération des données :
    • Valeur du RSI : rsi.Current.Value
    • Prix de clôture : data[symbol].Close
  3. Visualisation :
    • Tracer le RSI avec les lignes de référence 30/70
  4. Logique de trading :
    • Achat : RSI < 30 ET pas investi → SetHoldings(symbol, 1.0)
    • Vente : RSI > 70 ET investi → Liquidate(symbol)

Dans OnEndOfAlgorithm()

  • Calcul du rendement total sur le capital initial explicite (self.initial_capital)
  • Résumé des performances en fin de run

Lancer dans QuantConnect cloud

Pour tester cette stratégie :

  1. Connectez-vous à QuantConnect
  2. Créez un nouveau projet : “New Project” → “Python Algorithm”
  3. Copiez le code ci-dessus dans main.py
  4. Lancez le backtest : Cliquez sur “Backtest”
  5. Analysez les résultats :
    • Onglet “Overview” : Rendement total, Sharpe ratio, Drawdown
    • Onglet “Charts” : Graphique RSI, Equity curve
    • Onglet “Orders” : Tous les trades exécutés
    • Onglet “Logs” : Messages de self.Log()

Analyser les résultats

Une fois le backtest terminé, observez :

Métrique Description Valeur typique
Total Return Rendement total sur la période Variable selon marché
Sharpe Ratio Rendement ajusté au risque > 1 = bon, > 2 = excellent
Max Drawdown Plus grosse perte depuis un pic < 20% = acceptable
Win Rate % de trades gagnants 50-60% typique pour mean reversion
Number of Trades Nombre total de trades Dépend de la volatilité

Questions à se poser : - La stratégie bat-elle le buy-and-hold de SPY ? - Y a-t-il des périodes où elle performe mal ? - Le Sharpe ratio justifie-t-il le risque ? - Combien de trades sont exécutés par an ?

6. Méthodes QCAlgorithm Essentielles

Méthodes de trading

Voici les méthodes les plus utilisées pour exécuter des ordres :

Méthode Signature Usage Exemple
SetHoldings SetHoldings(symbol, percentage) Acheter/vendre pour atteindre X% du portfolio SetHoldings(spy, 0.5) = 50% en SPY
Liquidate Liquidate(symbol) Vendre toute la position d’un actif Liquidate(spy)
MarketOrder MarketOrder(symbol, quantity) Ordre au marché (exécution immédiate) MarketOrder(spy, 100) = acheter 100 actions
LimitOrder LimitOrder(symbol, quantity, limit_price) Ordre limite (exécution si prix atteint) LimitOrder(spy, 100, 450)
StopMarketOrder StopMarketOrder(symbol, quantity, stop_price) Stop loss (vendre si prix descend) StopMarketOrder(spy, -100, 440)

Différences entre les types d’ordres

SetHoldings vs MarketOrder

# SetHoldings - Spécifie un % du portfolio
self.SetHoldings(self.symbol, 0.8)  # Acheter pour avoir 80% du portfolio en SPY
# QuantConnect calcule automatiquement le nombre d'actions à acheter/vendre

# MarketOrder - Spécifie un nombre d'actions
self.MarketOrder(self.symbol, 100)  # Acheter exactement 100 actions

Conseil : Utilisez SetHoldings pour la simplicité, MarketOrder pour un contrôle précis.

MarketOrder vs LimitOrder

# MarketOrder - Exécution immédiate au meilleur prix disponible
self.MarketOrder(self.symbol, 100)
# Avantage: Garantit l'exécution
# Inconvénient: Pas de contrôle sur le prix (slippage possible)

# LimitOrder - Exécution uniquement si prix <= limite (pour achat)
self.LimitOrder(self.symbol, 100, 450)
# Avantage: Contrôle du prix maximum
# Inconvénient: Peut ne jamais s'exécuter

Exemples d’utilisation des méthodes

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
# Exemples d'utilisation des méthodes de trading
# Code informatif pour QuantConnect IDE

from AlgorithmImports import *

class TradingMethodsExample(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetCash(100000)
        self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
    
    def OnData(self, data):
        price = data[self.symbol].Close
        
        # EXEMPLE 1: SetHoldings - Acheter pour 50% du portfolio
        if not self.Portfolio.Invested:
            self.SetHoldings(self.symbol, 0.5)
            self.Log(f"SetHoldings: Acheté pour 50% du portfolio")
        
        # EXEMPLE 2: MarketOrder - Acheter 100 actions au marché
        # self.MarketOrder(self.symbol, 100)
        # self.Log(f"MarketOrder: Acheté 100 actions à ~${price:.2f}")
        
        # EXEMPLE 3: LimitOrder - Acheter 100 actions si prix <= 450$
        # self.LimitOrder(self.symbol, 100, 450)
        # self.Log(f"LimitOrder: Ordre d'achat de 100 actions à 450$ max")
        
        # EXEMPLE 4: StopMarketOrder - Vendre si prix descend sous 440$
        # if self.Portfolio[self.symbol].Quantity > 0:
        #     quantity = -self.Portfolio[self.symbol].Quantity
        #     self.StopMarketOrder(self.symbol, quantity, 440)
        #     self.Log(f"StopMarketOrder: Stop loss à 440$")
        
        # EXEMPLE 5: Liquidate - Vendre toute la position
        # if self.Portfolio.Invested:
        #     self.Liquidate(self.symbol)
        #     self.Log(f"Liquidate: Position fermée à ${price:.2f}")

Résultat du Backtest QC Cloud — TradingMethodsExample (BT 0ed23be746101678a8ecbbd5c276c8fb, projet 31996441, run « TradingMethodsExample-v2-fixed » du 2026-05-22) Run historique de la classe ci-dessus (départ 2015-01-01, sans date de fin — 2 264 dates négociables), conservé tel quel. « Trades = 0 » compte les transactions clôturées : l’unique SetHoldings(0.5) du code n’est jamais clôturé avant la fin du run (1 ordre ouvert).

Sharpe CAGR MaxDD Trades
0.379 7.139% 21.7% 0 (1 ordre ouvert)

Méthodes de logging et visualisation

Méthode Usage Exemple
Log Message dans les résultats du backtest self.Log("Trade executed")
Debug Message dans la console de debug self.Debug(f"Price: {price}")
Plot Créer un graphique personnalisé self.Plot("Chart", "Series", value)
Error Logger une erreur self.Error("Something went wrong")

Exemple de visualisation avancée

def OnData(self, data):
    price = data[self.symbol].Close
    
    # Graphique 1: Prix et moyennes mobiles
    self.Plot("Price Analysis", "SPY Price", price)
    if self.sma_20.IsReady:
        self.Plot("Price Analysis", "SMA(20)", self.sma_20.Current.Value)
    if self.sma_50.IsReady:
        self.Plot("Price Analysis", "SMA(50)", self.sma_50.Current.Value)
    
    # Graphique 2: RSI avec zones
    if self.rsi.IsReady:
        self.Plot("Momentum", "RSI", self.rsi.Current.Value)
        self.Plot("Momentum", "Overbought", 70)
        self.Plot("Momentum", "Oversold", 30)
    
    # Graphique 3: Position size
    holdings = self.Portfolio[self.symbol].Quantity
    self.Plot("Portfolio", "SPY Holdings", holdings)

Méthodes d’accès au Portfolio

# Vérifier si on a une position
is_invested = self.Portfolio.Invested
has_spy = self.Portfolio[self.symbol].Invested

# Quantité détenue
quantity = self.Portfolio[self.symbol].Quantity

# Valeur de la position
holding_value = self.Portfolio[self.symbol].HoldingsValue

# Prix moyen d'achat
avg_price = self.Portfolio[self.symbol].AveragePrice

# Profit/loss non réalisé
unrealized_pnl = self.Portfolio[self.symbol].UnrealizedProfit

# Valeur totale du portfolio
total_value = self.Portfolio.TotalPortfolioValue

# Cash disponible
cash = self.Portfolio.Cash

Conclusion

Récapitulatif

Dans ce notebook, nous avons exploré les fondamentaux de la plateforme QuantConnect :

1. QCAlgorithm Lifecycle

  • Initialize() : Configuration initiale (1 fois)
  • OnData() : Logique de trading (à chaque nouvelle donnée)
  • OnEndOfAlgorithm() : Finalisation (1 fois)

2. Gestion des Securities

  • Ajouter des actifs avec AddEquity(), AddCrypto(), AddForex()
  • Utiliser des objets Symbol (pas des strings)
  • Configurer le warm-up avec SetWarmUp()

3. Indicateurs Techniques

  • Bibliothèque complète : SMA, EMA, RSI, MACD, BB, etc.
  • Toujours vérifier indicator.IsReady
  • Accéder aux valeurs avec indicator.Current.Value

4. Stratégie RSI Mean Reversion

  • Exemple complet d’une stratégie fonctionnelle
  • Achat en zone de survente (RSI < 30)
  • Vente en zone de surachat (RSI > 70)

5. Méthodes Essentielles

  • Trading : SetHoldings(), Liquidate(), MarketOrder(), LimitOrder()
  • Logging : Log(), Debug(), Plot()
  • Portfolio : Portfolio.Invested, Portfolio[symbol].Quantity

Tableau récapitulatif des méthodes principales

Catégorie Méthode Usage
Configuration SetStartDate(), SetEndDate(), SetCash() Définir période et capital
Securities AddEquity(), AddCrypto(), AddForex() Ajouter des actifs
Indicateurs SMA(), EMA(), RSI(), MACD(), BB() Créer des indicateurs
Trading SetHoldings(), Liquidate(), MarketOrder() Exécuter des ordres
Logging Log(), Debug(), Plot() Debugger et visualiser
Portfolio Portfolio.Invested, Portfolio[symbol].Quantity Consulter l’état

Prochaine étape : QC-Py-03-Data-Management

Dans le prochain notebook, nous approfondirons la gestion des données avec :

  • History API : Récupérer des données historiques
  • Consolidators : Agréger des données (ex: minute → hour)
  • Scheduled Events : Exécuter du code à des heures précises
  • Universe Sélection : Trader plusieurs actifs dynamiquement

Exercice suggéré

Modifiez la stratégie RSI Mean Reversion pour :

  1. Ajuster les seuils : Testez RSI < 25 et RSI > 75
  2. Ajouter un stop loss : Vendez si perte > 5%
  3. Tester un autre actif : Remplacez SPY par QQQ (Nasdaq 100)
  4. Combiner avec une moyenne mobile : N’achetez que si prix > SMA(50)
  5. Créer une stratégie MACD crossover :
    • Achetez quand MACD croise au-dessus du signal
    • Vendez quand MACD croise en-dessous du signal

Ressources supplémentaires


Félicitations ! Vous maîtrisez maintenant les fondamentaux de la plateforme QuantConnect. Vous êtes prêt à créer vos propres stratégies de trading algorithmique.

Retour au sommet