<< Sommaire QC | Précédent : QC-Py-02-Platform-Fundamentals << | Suivant : QC-Py-04-Research-Workflow >>

QC-Py-03 - Data Management in QuantConnect

History API et Consolidators

Durée estimée: 75 minutes
Niveau: Intermédiaire
Prérequis: QC-Py-01 et QC-Py-02 complétés, connaissance de pandas

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 a deux parties : 1. Sections analyse/ML (pandas, sklearn, matplotlib) : executables en Jupyter local 2. Sections integration QC (classes QCAlgorithm) : code de reference a copier dans main.py de votre projet QC Lab

Les cellules QCAlgorithm sont marquees # [REFERENCE QC] et ne sont pas executables localement.


1. Introduction - L’importance de la gestion de données (5 min)

Pourquoi la gestion de données est cruciale en algo trading?

Dans l’algo trading, les données sont le carburant de vos stratégies. Une mauvaise gestion des données peut mener à: - Des backtests biaisés (look-ahead bias) - Des calculs de retours incorrects (splits/dividendes ignorés) - Des signaux erronés (timeframes incohérents) - Des performances réelles décevantes

QuantConnect Data Library

QuantConnect fournit une bibliothèque de données historiques massive:

Type d’actif Historique Résolutions disponibles
Equity (US) Depuis 1998 Tick, Second, Minute, Hour, Daily
Crypto Depuis 2015 Tick, Second, Minute, Hour, Daily
Forex Depuis 2007 Tick, Second, Minute, Hour, Daily
Futures Depuis 2003 Tick, Second, Minute, Hour, Daily
Options Depuis 2007 Minute, Hour, Daily

Objectifs de ce notebook

À la fin de ce notebook, vous saurez: 1. Récupérer des données historiques avec la History API 2. Comprendre la normalisation de données (splits, dividendes) 3. Utiliser les consolidators pour analyse multi-timeframe 4. Charger des données personnalisées (custom data) 5. Analyser les données avec pandas

Plan du notebook

1. Introduction (5 min)
2. History API - Récupération de données (20 min)
3. Normalisation de données (15 min)
4. Consolidators - Multi-timeframe (20 min)
5. Données personnalisées (10 min)
6. Analyse avec pandas (10 min)
7. Exemple complet: Multi-timeframe trend following (10 min)
8. Conclusion (5 min)

2. History API - Récupération de données historiques (20 min)

2.1 Introduction à la History API

La méthode History() permet de récupérer des données historiques pendant l’exécution de votre algorithme. C’est essentiel pour: - Calculer des indicateurs sur fenêtres historiques - Initialiser des modèles ML avec des données passées - Analyser des patterns sur plusieurs jours/semaines

2.2 Première récupération de données

Créons un algorithme simple qui récupère 30 jours de données pour Apple (AAPL).

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
from AlgorithmImports import *

class DataExplorationAlgo(QCAlgorithm):
    
    def Initialize(self):
        # Configuration de base
        self.SetStartDate(2015, 1, 1)
        self.SetEndDate(2024, 12, 31)
        self.SetCash(100000)
        
        # Ajouter Apple avec résolution Daily
        self.symbol = self.AddEquity("AAPL", Resolution.Daily).Symbol
        
        # Récupérer 30 jours de données historiques
        history = self.History(self.symbol, 30, Resolution.Daily)
        
        # Convertir en DataFrame pandas
        df = history.loc[self.symbol]
        
        # Afficher les informations
        self.Log(f"Shape: {df.shape}")
        self.Log(f"Columns: {df.columns.tolist()}")
        self.Log("\nPremières lignes:")
        self.Log(df.head().to_string())
        
        # Statistiques de base
        self.Log("\nStatistiques:")
        self.Log(f"Prix moyen: ${df['close'].mean():.2f}")
        self.Log(f"Volume moyen: {df['volume'].mean():.0f}")
        self.Log(f"Prix min: ${df['low'].min():.2f}")
        self.Log(f"Prix max: ${df['high'].max():.2f}")

Résultats du Backtest QC Cloud

Algorithme : DataExplorationAlgo BT ID : c0eea29363857469829943de0343ea94 Dates tradeables : 2516 Ordres executes : 0 Equity finale : $100,000.00 Note : Data exploration only (History, pandas stats)

Résultat du Backtest QC Cloud — DataExplorationAlgo (BT c0eea293…, projet 32005992)

Sharpe CAGR MaxDD Trades
- - - 0 (data exploration)

2.3 Les différentes signatures de History()

La méthode History() a plusieurs signatures pour différents besoins:

Signature 1: Par nombre de périodes

# Récupérer les 30 derniers jours
history = self.History(self.symbol, 30, Resolution.Daily)

# Récupérer les 100 dernières minutes
history = self.History(self.symbol, 100, Resolution.Minute)

Signature 2: Par timedelta

from datetime import timedelta

# Récupérer les 7 derniers jours
history = self.History(self.symbol, timedelta(days=7), Resolution.Daily)

# Récupérer les 2 dernières heures
history = self.History(self.symbol, timedelta(hours=2), Resolution.Minute)

Signature 3: Par dates de début et fin

from datetime import datetime

# Récupérer les données entre deux dates spécifiques
start = datetime(2020, 1, 1)
end = datetime(2020, 12, 31)
history = self.History(self.symbol, start, end, Resolution.Daily)

Signature 4: Multi-symboles

# Récupérer les données pour plusieurs symboles
symbols = [self.symbol1, self.symbol2, self.symbol3]
history = self.History(symbols, 30, Resolution.Daily)

# Accéder aux données de chaque symbole
df1 = history.loc[self.symbol1]
df2 = history.loc[self.symbol2]

2.4 TradeBar vs QuoteBar

QuantConnect fournit deux types de barres de prix:

Type Description Colonnes Usage
TradeBar Données de trading (OHLCV) open, high, low, close, volume Equity, Crypto, Futures
QuoteBar Données de cotation (bid/ask) bid.open, bid.close, ask.open, ask.close Forex, Options

Pour les actions, nous utilisons principalement TradeBar.

Résultat du Backtest QC Cloud — HistorySignaturesDemo (BT 950872b0…, projet 32005992)

Sharpe CAGR MaxDD Trades
- - - 0 (data exploration)
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
from AlgorithmImports import *

class HistorySignaturesDemo(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetCash(100000)
        
        # Ajouter plusieurs symboles
        self.aapl = self.AddEquity("AAPL", Resolution.Daily).Symbol
        self.msft = self.AddEquity("MSFT", Resolution.Daily).Symbol
        self.googl = self.AddEquity("GOOGL", Resolution.Daily).Symbol
        
        # Démonstration des différentes signatures
        self.DemoHistorySignatures()
    
    def DemoHistorySignatures(self):
        # 1. Par nombre de périodes (un seul symbole)
        self.Log("=== Signature 1: Nombre de périodes ===")
        history1 = self.History(self.aapl, 5, Resolution.Daily)
        df1 = history1.loc[self.aapl]
        self.Log(f"5 derniers jours AAPL: {len(df1)} lignes")
        self.Log(f"Dernier prix: ${df1['close'].iloc[-1]:.2f}")
        
        # 2. Par timedelta
        self.Log("\n=== Signature 2: Timedelta ===")
        history2 = self.History(self.msft, timedelta(days=10), Resolution.Daily)
        df2 = history2.loc[self.msft]
        self.Log(f"10 derniers jours MSFT: {len(df2)} lignes (trading days)")
        
        # 3. Multi-symboles
        self.Log("\n=== Signature 3: Multi-symboles ===")
        symbols = [self.aapl, self.msft, self.googl]
        history3 = self.History(symbols, 5, Resolution.Daily)
        
        for symbol in symbols:
            df = history3.loc[symbol]
            self.Log(f"{symbol.Value}: {len(df)} lignes, dernier prix ${df['close'].iloc[-1]:.2f}")
        
        # 4. Accès aux colonnes OHLCV
        self.Log("\n=== Colonnes TradeBar (OHLCV) ===")
        df_aapl = history1.loc[self.aapl]
        latest_bar = df_aapl.iloc[-1]
        self.Log(f"Open: ${latest_bar['open']:.2f}")
        self.Log(f"High: ${latest_bar['high']:.2f}")
        self.Log(f"Low: ${latest_bar['low']:.2f}")
        self.Log(f"Close: ${latest_bar['close']:.2f}")
        self.Log(f"Volume: {latest_bar['volume']:.0f}")

Résultats du Backtest QC Cloud

Algorithme : HistorySignaturesDemo BT ID : 950872b0c8b3494db8c9a7527639eb48 Dates tradeables : 2864 Ordres executes : 0 Equity finale : $100,000.00 Note : Multi-signature History API demo

2.5 Interprétation des résultats

Points clés à observer dans les résultats ci-dessus:

  1. Trading days vs Calendar days: Lorsque vous demandez 10 jours avec timedelta(days=10), vous obtenez ~7 lignes (marchés fermés weekend/fériés)

  2. Structure du DataFrame: Les données sont indexées par date, avec une colonne par métrique (open, high, low, close, volume)

  3. Multi-symboles: Avec plusieurs symboles, history.loc[symbol] permet d’accéder aux données de chaque actif individuellement

Best practice: Toujours vérifier la longueur du DataFrame historique avant de l’utiliser, car les données peuvent être manquantes (IPO récente, delisting, etc.)

Exercice 1 - Exploration multi-symboles avec la History API

Vous avez vu les différentes signatures de History() et comment acceder aux colonnes OHLCV. L’objectif de cet exercice est de mettre en pratique ces connaissances en recuperant les données de 5 actions du secteur technologique (AAPL, MSFT, GOOGL, AMZN, NVDA) et en calculant pour chacune les statistiques de base sur les 30 derniers jours.

Objectif : Completer la méthode AnalyzeTechSector() dans le squelette ci-dessous. Le résultat attendu est un log affichant, pour chaque symbole : le dernier prix, le retour sur 30 jours, et le volume moyen.

Indices : - # Étape 1 : Utiliser la signature multi-symboles : self.History(symbols, 30, Resolution.Daily) - # Étape 2 : Iterer sur chaque symbole avec for symbol in symbols: et extraire le DataFrame avec history.loc[symbol] - # Étape 3 : Dernier prix = df['close'].iloc[-1], volume moyen = df['volume'].mean() - # Étape 4 : Retour sur 30 jours = (df['close'].iloc[-1] / df['close'].iloc[0]) - 1

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
# Exercice 1 - Exploration multi-symboles avec la History API
from AlgorithmImports import *

class TechSectorScanner(QCAlgorithm):
    """Scanner le secteur technologique avec la History API multi-symboles."""

    def Initialize(self):
        self.SetStartDate(2024, 1, 1)
        self.SetCash(100000)

        # 5 actions du secteur technologique
        self.tech_tickers = ["AAPL", "MSFT", "GOOGL", "AMZN", "NVDA"]
        self.symbols = []
        for ticker in self.tech_tickers:
            symbol = self.AddEquity(ticker, Resolution.Daily).Symbol
            self.symbols.append(symbol)

        # TODO etudiant : implementer AnalyzeTechSector() ci-dessous
        self.AnalyzeTechSector()

    def AnalyzeTechSector(self):
        """Analyser les 30 derniers jours pour chaque action tech.

        Pour chaque symbole dans self.symbols :
          1. Recuperer 30 jours de donnees avec la signature multi-symboles
          2. Calculer : dernier prix, retour sur 30 jours, volume moyen
          3. Logger les resultats pour chaque symbole
        """
        # TODO etudiant : implementer l'analyse multi-symboles
        print("Exercice a completer")

3. Normalisation de Données (15 min)

3.1 Pourquoi normaliser les données?

Les prix bruts (raw prices) ne reflètent pas la vraie performance d’un investissement. Deux événements corporatifs impactent les prix:

  1. Splits: Division d’actions (ex: 1 action à $100 devient 2 actions à $50)
  2. Dividendes: Distribution de cash aux actionnaires (ex: $2 par action)

Problème: Sans ajustement, un split crée une fausse chute de 50% dans votre backtest.

3.2 Les modes de normalisation

QuantConnect offre 4 modes de normalisation via DataNormalizationMode:

Mode Ajuste Splits? Ajuste Dividendes? Usage
Raw Non Non Prix bruts historiques (recherche académique)
Adjusted Oui Oui Recommandé pour backtesting (défaut)
SplitAdjusted Oui Non Analyse technique pure
TotalReturn Oui Oui (réinvestis) Performance avec dividendes réinvestis

Défaut: Si vous n’indiquez rien, QuantConnect utilise Adjusted (splits et dividendes ajustés).

Résultat du Backtest QC Cloud — DataNormalizationDemo (BT 7f44566e…, projet 32005992)

Sharpe CAGR MaxDD Trades
- - - INIT_ERROR (History dataNormalizationMode KeyError)

3.3 Comparaison Raw vs Adjusted

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
from AlgorithmImports import *

class DataNormalizationDemo(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)  # Période incluant des splits/dividendes
        self.SetEndDate(2024, 12, 31)
        self.SetCash(100000)
        
        # Ajouter Apple (AAPL a eu un split 4:1 en août 2020)
        self.symbol = self.AddEquity("AAPL", Resolution.Daily).Symbol
        
        # Comparer les différents modes de normalisation
        self.CompareNormalizationModes()
    
    def CompareNormalizationModes(self):
        # Période couvrant le split d'août 2020
        start = datetime(2020, 8, 1)
        end = datetime(2020, 9, 30)
        
        # 1. Raw - Prix bruts (non ajustés)
        history_raw = self.History(
            [self.symbol], 
            start, 
            end, 
            Resolution.Daily,
            dataNormalizationMode=DataNormalizationMode.Raw
        )
        
        # 2. Adjusted - Ajusté pour splits et dividendes (DÉFAUT)
        history_adjusted = self.History(
            [self.symbol], 
            start, 
            end, 
            Resolution.Daily,
            dataNormalizationMode=DataNormalizationMode.Adjusted
        )
        
        # 3. SplitAdjusted - Splits seulement
        history_split = self.History(
            [self.symbol], 
            start, 
            end, 
            Resolution.Daily,
            dataNormalizationMode=DataNormalizationMode.SplitAdjusted
        )
        
        # Convertir en DataFrames
        df_raw = history_raw.loc[self.symbol]
        df_adjusted = history_adjusted.loc[self.symbol]
        df_split = history_split.loc[self.symbol]
        
        # Comparer les prix autour du split (31 août 2020)
        self.Log("=== Comparaison des prix autour du split AAPL 4:1 (31 août 2020) ===")
        
        # Dates avant et après le split
        before_split = datetime(2020, 8, 28)  # Vendredi avant le split
        after_split = datetime(2020, 8, 31)   # Lundi après le split
        
        self.Log(f"\nRaw (non ajusté):")
        self.Log(f"  28 août: ${df_raw.loc[before_split, 'close']:.2f}")
        self.Log(f"  31 août: ${df_raw.loc[after_split, 'close']:.2f}")
        self.Log(f"  Ratio: {df_raw.loc[before_split, 'close'] / df_raw.loc[after_split, 'close']:.2f}x (split 4:1)")
        
        self.Log(f"\nAdjusted (splits + dividendes):")
        self.Log(f"  28 août: ${df_adjusted.loc[before_split, 'close']:.2f}")
        self.Log(f"  31 août: ${df_adjusted.loc[after_split, 'close']:.2f}")
        self.Log(f"  Continuité: Oui (pas de saut artificiel)")
        
        # Calculer les retours
        self.Log("\n=== Impact sur les calculs de retours ===")
        
        # Retour Raw (FAUX - montre une chute de 75%)
        return_raw = (df_raw.loc[after_split, 'close'] / df_raw.loc[before_split, 'close']) - 1
        self.Log(f"Retour avec Raw: {return_raw:.2%} (FAUX - dû au split)")
        
        # Retour Adjusted (CORRECT)
        return_adjusted = (df_adjusted.loc[after_split, 'close'] / df_adjusted.loc[before_split, 'close']) - 1
        self.Log(f"Retour avec Adjusted: {return_adjusted:.2%} (CORRECT)")

Résultats du Backtest QC Cloud

Algorithme : DataNormalizationDemo BT ID : 7f44566ebdc20bf19ddc9d495ce4769c Status : Runtime Error (KeyError) Erreur : History avec dataNormalizationMode parameter KeyError pendant Initialize Note : dataNormalizationMode parameter non disponible en mode History pendant Initialize

3.4 Interprétation des résultats de normalisation

Dans les résultats ci-dessus, vous observerez:

Aspect Raw Adjusted
Prix avant split (28 août 2020) ~$500 ~$125
Prix après split (31 août 2020) ~$125 ~$125
Retour calculé -75% (FAUX) ~0% (CORRECT)
Continuité Non (saut brutal) Oui (courbe lisse)

Pourquoi c’est critique?

Si vous utilisez des prix Raw: - Vos indicateurs techniques (SMA, RSI, etc.) donneront des signaux erronés - Vos calculs de retours seront complètement faux - Votre backtest ne reflètera pas la réalité

Best practice: Toujours utiliser Adjusted (défaut) pour le backtesting. N’utilisez Raw que si vous avez une raison académique spécifique.

3.5 TotalReturn - Dividendes réinvestis

Le mode TotalReturn est utile pour comparer la performance d’une stratégie qui réinvestit automatiquement les dividendes.

Exemple: Si vous achetez $10,000 d’actions et recevez $200 en dividendes: - Adjusted: Votre capital reste $10,000 + value des actions - TotalReturn: Votre capital devient $10,200 (dividendes automatiquement réinvestis dans les actions)

C’est particulièrement pertinent pour les stratégies buy-and-hold sur actions à dividendes élevés (ex: utilities, REITs).

4. Consolidators - Analyse Multi-Timeframe (20 min)

4.1 Qu’est-ce qu’un Consolidator?

Un consolidator agrège des données haute fréquence en barres de plus basse fréquence.

Use case: Vous souscrivez à des données Minute, mais vous voulez calculer une SMA sur des barres 15-minute ou Daily.

Sans consolidator: Vous devez souscrire séparément à chaque résolution (coûteux en ressources).

Avec consolidator: Vous consolidez les données Minute en 15-minute à la volée.

4.2 Types de Consolidators

Type Description Usage
TradeBarConsolidator Consolide des TradeBars (OHLCV) Equity, Crypto, Futures
QuoteBarConsolidator Consolide des QuoteBars (bid/ask) Forex, Options
TickConsolidator Consolide des Ticks Ultra haute fréquence

4.3 Exemple: Stratégie multi-timeframe

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
from AlgorithmImports import *

class MultiTimeframeAlgo(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetEndDate(2024, 12, 31)
        self.SetCash(100000)
        
        # Souscrire à des données MINUTE (haute fréquence)
        self.symbol = self.AddEquity("SPY", Resolution.Minute).Symbol
        
        # Indicateur sur données MINUTE (court terme)
        self.fast_minute = self.SMA(self.symbol, 10, Resolution.Minute)
        
        # Consolidator pour barres 15-MINUTE
        self.consolidator_15min = TradeBarConsolidator(timedelta(minutes=15))
        self.consolidator_15min.DataConsolidated += self.On15MinuteBar
        self.SubscriptionManager.AddConsolidator(self.symbol, self.consolidator_15min)
        
        # SMA sur 15-MINUTE (moyen terme)
        self.sma_15min = IndicatorExtensions.Of(
            SimpleMovingAverage(10), 
            self.consolidator_15min
        )
        
        # Consolidator pour barres 1-HOUR
        self.consolidator_1hour = TradeBarConsolidator(timedelta(hours=1))
        self.consolidator_1hour.DataConsolidated += self.On1HourBar
        self.SubscriptionManager.AddConsolidator(self.symbol, self.consolidator_1hour)
        
        # SMA sur 1-HOUR (long terme)
        self.sma_1hour = IndicatorExtensions.Of(
            SimpleMovingAverage(10), 
            self.consolidator_1hour
        )
        
        # Compteurs pour le logging
        self.bar_count_minute = 0
        self.bar_count_15min = 0
        self.bar_count_1hour = 0
    
    def On15MinuteBar(self, sender, bar):
        """Callback appelé à chaque nouvelle barre 15-minute."""
        self.bar_count_15min += 1
        if self.bar_count_15min <= 3:  # Afficher seulement les 3 premières
            self.Log(f"15-min bar: Time={bar.Time}, Close=${bar.Close:.2f}, SMA={self.sma_15min.Current.Value:.2f}")
    
    def On1HourBar(self, sender, bar):
        """Callback appelé à chaque nouvelle barre 1-hour."""
        self.bar_count_1hour += 1
        if self.bar_count_1hour <= 3:  # Afficher seulement les 3 premières
            self.Log(f"1-hour bar: Time={bar.Time}, Close=${bar.Close:.2f}, SMA={self.sma_1hour.Current.Value:.2f}")
    
    def OnData(self, data):
        """Appelé à chaque nouvelle barre MINUTE."""
        if not data.ContainsKey(self.symbol):
            return
        
        self.bar_count_minute += 1
        
        # Afficher seulement les 3 premières barres minute
        if self.bar_count_minute <= 3:
            self.Log(f"Minute bar: Time={self.Time}, Close=${data[self.symbol].Close:.2f}")
        
        # Attendre que tous les indicateurs soient prêts
        if not (self.fast_minute.IsReady and self.sma_15min.IsReady and self.sma_1hour.IsReady):
            return
        
        # Stratégie multi-timeframe:
        # Acheter si tendance haussière sur les 3 timeframes
        price = data[self.symbol].Close
        
        # Conditions haussières
        bullish_minute = price > self.fast_minute.Current.Value
        bullish_15min = price > self.sma_15min.Current.Value
        bullish_1hour = price > self.sma_1hour.Current.Value
        
        # Entrer si toutes les timeframes sont haussières
        if bullish_minute and bullish_15min and bullish_1hour:
            if not self.Portfolio.Invested:
                self.SetHoldings(self.symbol, 1.0)
                self.Log(f"BUY: Triple timeframe bullish at {self.Time}")
        
        # Sortir si au moins une timeframe devient baissière
        elif not (bullish_minute and bullish_15min and bullish_1hour):
            if self.Portfolio.Invested:
                self.Liquidate(self.symbol)
                self.Log(f"SELL: Trend weakening at {self.Time}")

Résultat du Backtest QC Cloud — ConsolidatorByCountDemo (BT 3f54a1b5…, projet 32000820)

Sharpe CAGR MaxDD Trades
- - - 0 (data exploration)

4.4 Anatomie d’un Consolidator

Décortiquons le code ci-dessus:

# 1. Créer le consolidator
consolidator = TradeBarConsolidator(timedelta(minutes=15))

# 2. Enregistrer un callback pour être notifié des nouvelles barres
consolidator.DataConsolidated += self.On15MinuteBar

# 3. Attacher le consolidator au symbole
self.SubscriptionManager.AddConsolidator(self.symbol, consolidator)

# 4. Lier un indicateur au consolidator
self.sma = IndicatorExtensions.Of(SimpleMovingAverage(10), consolidator)

Points clés:

  1. Callback DataConsolidated: Appelé chaque fois qu’une nouvelle barre est consolidée (ex: toutes les 15 minutes)
  2. IndicatorExtensions.Of(): Lie un indicateur à un consolidator pour qu’il se mette à jour automatiquement
  3. Warm-up: Les indicateurs sur consolidators prennent plus de temps à être prêts (ex: SMA(10) sur 15-min = 150 minutes de données nécessaires)

4.5 Consolidators par nombre de barres

Vous pouvez aussi créer des consolidators par nombre de barres au lieu de durée:

Résultat du Backtest QC Cloud — CustomDataAlgo (BT d8f6e1b4…, projet 32000870)

Sharpe CAGR MaxDD Trades
- - - 0 (data exploration)
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
from AlgorithmImports import *

class ConsolidatorByCountDemo(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetCash(100000)
        
        self.symbol = self.AddEquity("SPY", Resolution.Minute).Symbol
        
        # Consolidator qui crée une barre toutes les 15 barres minute
        # (équivalent à 15 minutes si pas de gaps)
        consolidator = TradeBarConsolidator(15)  # Nombre de barres
        consolidator.DataConsolidated += self.OnConsolidatedBar
        self.SubscriptionManager.AddConsolidator(self.symbol, consolidator)
        
        self.bar_count = 0
    
    def OnConsolidatedBar(self, sender, bar):
        self.bar_count += 1
        if self.bar_count <= 5:
            self.Log(f"Consolidated bar #{self.bar_count}: Time={bar.Time}, Close=${bar.Close:.2f}")

Résultats du Backtest QC Cloud

Algorithme : ConsolidatorByCountDemo
Dates tradeables : 61
Ordres executes : 0
Equity finale : $100,000.00
Note : Converted Minute→Daily, 5-bar consolidator. Jan-Mar 2015.

4.6 Avantages des Consolidators

Avantage Description
Performance Évite de souscrire à plusieurs résolutions (économise ressources)
Flexibilité Créez des timeframes personnalisés (ex: 17 minutes, 3 heures)
Synchronisation Tous les indicateurs multi-timeframe sont synchronisés
Simplicité Un seul stream de données, plusieurs analyses

Use case typique: Stratégies qui combinent tendance long terme (daily/weekly) avec timing d’entrée court terme (minute/hour).

Exercice 2 - Consolidator personnalise avec signal de surachat/survente

Vous avez vu comment créer des consolidators pour agreger des données minute en barres de 15 minutes, 1 heure ou hebdomadaires. L’objectif de cet exercice est de combiner un consolidator personnalise (30 minutes) avec le calcul d’un indicateur RSI pour generer des signaux de surachat/survente.

Objectif : Completer la méthode On30MinuteBar() pour calculer le RSI sur les barres consolidees et logger un signal quand le RSI depasse 70 (surachat) ou passe sous 30 (survente).

Indices : - # Étape 1 : Utiliser self.RSI(self.symbol, 14, MovingAverageType.Wilders, Resolution.Minute) puis le lier au consolidator avec IndicatorExtensions.Of() - # Étape 2 : Alternativement, calculer le RSI manuellement dans le callback en memorisant les gains/pertes moyens sur 14 periodes - # Étape 3 : Dans On30MinuteBar(), verifier que l’indicateur est IsReady avant de lire sa valeur - # Étape 4 : Logger le signal avec self.Log() en specifiant le timeframe, le prix et la valeur du RSI

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
# Exercice 2 - Consolidator personnalise avec signal RSI
from AlgorithmImports import *

class RSISignalAlgo(QCAlgorithm):
    """Utiliser un consolidator 30-minute pour generer des signaux RSI."""

    def Initialize(self):
        self.SetStartDate(2024, 1, 1)
        self.SetEndDate(2024, 6, 30)
        self.SetCash(100000)

        # Souscrire en resolution Minute
        self.symbol = self.AddEquity("AAPL", Resolution.Minute).Symbol

        # Creer un consolidator 30-minute
        self.consolidator_30min = TradeBarConsolidator(timedelta(minutes=30))
        self.consolidator_30min.DataConsolidated += self.On30MinuteBar
        self.SubscriptionManager.AddConsolidator(self.symbol, self.consolidator_30min)

        # Indicateur RSI lie au consolidator
        self.rsi = IndicatorExtensions.Of(
            RelativeStrengthIndex(14),
            self.consolidator_30min
        )

        self.SetWarmUp(42, Resolution.Minute)  # 14 periodes * 3 barres/periode

    def On30MinuteBar(self, sender, bar):
        """Callback appele a chaque nouvelle barre 30-minute.

        TODO etudiant :
          1. Verifier que self.rsi.IsReady
          2. Lire la valeur du RSI
          3. Si RSI > 70 : logger "SURACHAT" avec le prix et le RSI
          4. Si RSI < 30 : logger "SURVENTE" avec le prix et le RSI
        """
        # TODO etudiant : implementer la logique de signal RSI
        pass

    def OnData(self, data):
        pass  # Pas de logique de trading dans cet exercice

5. Données Personnalisées (Custom Data) (10 min)

5.1 Pourquoi des données personnalisées?

QuantConnect fournit des prix et volumes, mais vous pourriez vouloir intégrer: - Sentiment social media (Twitter, Reddit) - Données économiques (taux d’intérêt, inflation) - News sentiment - Données alternatives (images satellite, trafic web)

5.2 Créer une source de données personnalisée

Vous devez créer une classe héritant de PythonData et implémenter: 1. GetSource(): URL de la source de données 2. Reader(): Parser une ligne de données

Résultat du Backtest QC Cloud — PandasAnalysisDemo (BT e1124252…, projet 32005992)

Sharpe CAGR MaxDD Trades
- - - 0 (data exploration)
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
from AlgorithmImports import *

class BitcoinSentiment(PythonData):
    """Exemple de données personnalisées: sentiment Bitcoin (simulé)."""
    
    def GetSource(self, config, date, isLiveMode):
        """Retourne l'URL de la source de données."""
        # En production: URL vers votre serveur de données
        # Ex: https://myserver.com/bitcoin-sentiment/data.csv
        
        # Pour la démo, on utilise un fichier local (dans Dropbox QuantConnect)
        source = "https://www.dropbox.com/s/example/bitcoin_sentiment.csv?dl=1"
        
        return SubscriptionDataSource(
            source, 
            SubscriptionTransportMedium.RemoteFile
        )
    
    def Reader(self, config, line, date, isLiveMode):
        """Parse une ligne de CSV."""
        # Format CSV attendu: date,sentiment_score
        # Ex: 2020-01-01,0.75
        
        if not (line.strip() and line[0].isdigit()):
            return None  # Ignorer les headers ou lignes vides
        
        data = line.split(',')
        
        sentiment = BitcoinSentiment()
        sentiment.Symbol = config.Symbol
        sentiment.Time = datetime.strptime(data[0], "%Y-%m-%d")
        sentiment.Value = float(data[1])  # Sentiment score (0-1)
        
        return sentiment


class CustomDataAlgo(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetCash(100000)
        
        # Ajouter Bitcoin
        self.btc = self.AddCrypto("BTCUSD", Resolution.Daily).Symbol
        
        # Ajouter les données personnalisées de sentiment
        # Note: En réalité, ce fichier n'existe pas (exemple conceptuel)
        # self.sentiment = self.AddData(BitcoinSentiment, "BTC_SENTIMENT", Resolution.Daily).Symbol
        
        self.Log("Custom data source enregistrée (exemple conceptuel)")
    
    def OnData(self, data):
        # Accéder aux données personnalisées
        # if data.ContainsKey(self.sentiment):
        #     sentiment_score = data[self.sentiment].Value
        #     
        #     # Stratégie: acheter si sentiment > 0.7
        #     if sentiment_score > 0.7 and not self.Portfolio[self.btc].Invested:
        #         self.SetHoldings(self.btc, 1.0)
        #     elif sentiment_score < 0.3 and self.Portfolio[self.btc].Invested:
        #         self.Liquidate(self.btc)
        pass

Résultat du Backtest QC Cloud — RollingAnalysisDemo (BT 7f54aaf2…, projet 32005992)

Sharpe CAGR MaxDD Trades
- - - 0 (data exploration)

5.3 Alternative: QuantConnect Alternative Data

QuantConnect propose aussi des Alternative Data payantes:

Provider Type de données Prix
Quiver Quantitative Social media, insider trading $50-200/mois
Brain Sentiment News sentiment ML $100+/mois
Tiingo News Financial news feed $10+/mois
US Treasury Yield Taux d’intérêt Gratuit

Usage:

# Ajouter les taux d'intérêt US (gratuit)
self.AddData(USTreasuryYieldCurveRate, Resolution.Daily)

# Accéder aux données
def OnData(self, data):
    if data.ContainsKey(USTreasuryYieldCurveRate):
        ten_year_rate = data[USTreasuryYieldCurveRate].TenYear

Résultat du Backtest QC Cloud — CorrelationAnalysisDemo (BT a6b3175c…, projet 32005992)

Sharpe CAGR MaxDD Trades
- - - 0 (data exploration)

5.4 Limitations des données personnalisées

Limite Impact
Latence réseau Téléchargement des fichiers prend du temps
Format strict Reader() doit parser correctement ou lever une exception
Pas de fill-forward Données manquantes ne sont pas automatiquement comblées
Validation manuelle Vous devez vérifier la qualité des données

Best practice: Testez votre source de custom data avec un petit backtest avant de l’utiliser en production.

Résultat du Backtest QC Cloud - MultiTimeframeTrendFollowing (BT 820416908df4188f3041cf6025647d2a, projet 32000959)

Sharpe CAGR MaxDD Trades Win Rate
0.36 7.093% 16.0% 138 42%

Net Profit: +98.5% | Total Fees: $314.04 | Alpha: 0.005 | Beta: 0.328

6. Analyse de Données avec Pandas (10 min)

6.1 Pourquoi pandas?

Les données historiques de QuantConnect sont converties en pandas DataFrame, ce qui permet d’utiliser toute la puissance de pandas pour: - Calculer des statistiques (mean, std, quantiles) - Appliquer des transformations (rolling, shift, pct_change) - Visualiser les données (si en Research mode) - Analyser des corrélations

6.2 Statistiques de base

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
from AlgorithmImports import *

class PandasAnalysisDemo(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetCash(100000)
        
        self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
        
        # Récupérer 1 an de données (252 trading days)
        history = self.History(self.symbol, 252, Resolution.Daily)
        df = history.loc[self.symbol]
        
        # Calculer les retours
        df['returns'] = df['close'].pct_change()
        
        # Statistiques de base
        self.Log("=== Statistiques sur 1 an ===")
        self.Log(f"Prix moyen: ${df['close'].mean():.2f}")
        self.Log(f"Prix médian: ${df['close'].median():.2f}")
        self.Log(f"Écart-type: ${df['close'].std():.2f}")
        self.Log(f"Min: ${df['close'].min():.2f}")
        self.Log(f"Max: ${df['close'].max():.2f}")
        
        # Statistiques sur les retours
        self.Log("\n=== Retours journaliers ===")
        self.Log(f"Retour moyen: {df['returns'].mean():.4%}")
        self.Log(f"Volatilité journalière: {df['returns'].std():.4%}")
        self.Log(f"Meilleur jour: {df['returns'].max():.2%}")
        self.Log(f"Pire jour: {df['returns'].min():.2%}")
        
        # Volatilité annualisée
        annual_volatility = df['returns'].std() * (252 ** 0.5)
        self.Log(f"\nVolatilité annualisée: {annual_volatility:.2%}")
        
        # Sharpe ratio (simplifié, assume risk-free rate = 0)
        sharpe = (df['returns'].mean() / df['returns'].std()) * (252 ** 0.5)
        self.Log(f"Sharpe Ratio: {sharpe:.2f}")

Résultats du Backtest QC Cloud

Algorithme : PandasAnalysisDemo BT ID : e112425279b0a68d8e081af466988e13 Dates tradeables : 2864 Ordres executes : 0 Equity finale : $100,000.00 Note : SPY pandas analysis (returns, volatility, Sharpe)

6.3 Analyse avec rolling windows

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
from AlgorithmImports import *

class RollingAnalysisDemo(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetCash(100000)
        
        self.symbol = self.AddEquity("AAPL", Resolution.Daily).Symbol
        
        history = self.History(self.symbol, 252, Resolution.Daily)
        df = history.loc[self.symbol]
        
        # Calculer plusieurs moyennes mobiles
        df['sma_20'] = df['close'].rolling(window=20).mean()
        df['sma_50'] = df['close'].rolling(window=50).mean()
        df['sma_200'] = df['close'].rolling(window=200).mean()
        
        # Volatilité roulante (20 jours)
        df['returns'] = df['close'].pct_change()
        df['rolling_vol_20'] = df['returns'].rolling(window=20).std() * (252 ** 0.5)
        
        # Volume moyen roulant
        df['avg_volume_20'] = df['volume'].rolling(window=20).mean()
        
        # Dernier jour
        latest = df.iloc[-1]
        
        self.Log("=== Analyse Rolling (dernier jour) ===")
        self.Log(f"Prix: ${latest['close']:.2f}")
        self.Log(f"SMA 20: ${latest['sma_20']:.2f}")
        self.Log(f"SMA 50: ${latest['sma_50']:.2f}")
        self.Log(f"SMA 200: ${latest['sma_200']:.2f}")
        self.Log(f"Volatilité 20j: {latest['rolling_vol_20']:.2%}")
        self.Log(f"Volume moyen 20j: {latest['avg_volume_20']:.0f}")
        
        # Analyse de tendance
        self.Log("\n=== Analyse de tendance ===")
        if latest['close'] > latest['sma_20'] > latest['sma_50'] > latest['sma_200']:
            self.Log("Signal: FORTE TENDANCE HAUSSIÈRE (Golden Cross)")
        elif latest['close'] < latest['sma_20'] < latest['sma_50'] < latest['sma_200']:
            self.Log("Signal: FORTE TENDANCE BAISSIÈRE (Death Cross)")
        else:
            self.Log("Signal: TENDANCE MIXTE (Range)")

Résultats du Backtest QC Cloud

Algorithme : RollingAnalysisDemo
Dates tradeables : 2863
Ordres executes : 0
Equity finale : $100,000.00
Note : AAPL rolling SMA 20/50/200 + trend analysis

6.4 Analyse de corrélations

Les corrélations sont utiles pour: - Diversification de portefeuille (chercher corrélations faibles) - Pair trading (chercher corrélations fortes) - Hedging (chercher corrélations négatives)

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
from AlgorithmImports import *

class CorrelationAnalysisDemo(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetCash(100000)
        
        # Plusieurs symboles pour analyse de corrélation
        self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol  # S&P 500
        self.tlt = self.AddEquity("TLT", Resolution.Daily).Symbol  # Bonds long terme
        self.gld = self.AddEquity("GLD", Resolution.Daily).Symbol  # Or
        self.qqq = self.AddEquity("QQQ", Resolution.Daily).Symbol  # Nasdaq
        
        symbols = [self.spy, self.tlt, self.gld, self.qqq]
        
        # Récupérer 252 jours de données pour tous les symboles
        history = self.History(symbols, 252, Resolution.Daily)
        
        # Construire un DataFrame avec les retours de chaque symbole
        returns_dict = {}
        for symbol in symbols:
            df = history.loc[symbol]
            returns_dict[symbol.Value] = df['close'].pct_change()
        
        # Créer un DataFrame avec tous les retours
        import pandas as pd
        returns_df = pd.DataFrame(returns_dict)
        
        # Calculer la matrice de corrélation
        corr_matrix = returns_df.corr()
        
        self.Log("=== Matrice de Corrélation (retours journaliers) ===")
        self.Log(corr_matrix.to_string())
        
        # Interpréter les corrélations clés
        self.Log("\n=== Interprétations clés ===")
        spy_tlt_corr = corr_matrix.loc['SPY', 'TLT']
        spy_gld_corr = corr_matrix.loc['SPY', 'GLD']
        spy_qqq_corr = corr_matrix.loc['SPY', 'QQQ']
        
        self.Log(f"SPY-TLT: {spy_tlt_corr:.2f} (Stocks-Bonds: corrélation négative = bon hedge)")
        self.Log(f"SPY-GLD: {spy_gld_corr:.2f} (Stocks-Or: corrélation faible = diversification)")
        self.Log(f"SPY-QQQ: {spy_qqq_corr:.2f} (S&P-Nasdaq: corrélation forte = même secteur)")

Résultats du Backtest QC Cloud

Algorithme : CorrelationAnalysisDemo
Dates tradeables : 2863
Ordres executes : 0
Equity finale : $100,000.00
Note : SPY/TLT/GLD/QQQ correlation matrix

Exercice 3 - Tableau de bord multi-actifs avec pandas

Vous avez vu comment analyser les corrélations entre plusieurs actifs avec pandas. L’objectif de cet exercice est de construire un tableau de bord synthetique qui calcule, pour chaque actif d’un univers de 5 ETFs (SPY, TLT, GLD, QQQ, IWM), les metriques cles : rendement annualise, volatilite annualisee, ratio de Sharpe et drawdown maximum en pourcentage.

Objectif : Implementer la méthode BuildDashboard() dans le squelette ci-dessous. Le résultat attendu est un log formatant les 4 metriques pour chaque symbole, permettant une comparaison rapide.

Indices : - # Étape 1 : Recuperer 252 jours de données avec self.History(symbols, 252, Resolution.Daily) - # Étape 2 : Pour chaque symbole, extraire le DataFrame et calculer les retours avec df['close'].pct_change() - # Étape 3 : Rendement annualise = returns.mean() * 252, volatilite = returns.std() * sqrt(252) - # Étape 4 : Sharpe = rendement annualise / volatilite (risk-free = 0) - # Étape 5 : Max drawdown (%) : calculer cumulative = (1 + returns).cumprod(), puis running_max = cumulative.cummax(), et drawdown = (running_max - cumulative) / running_max, prendre le max

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
# Exercice 3 - Tableau de bord multi-actifs avec pandas
from AlgorithmImports import *
from math import sqrt

class MultiAssetDashboard(QCAlgorithm):
    """Construire un tableau de bord de performance pour un univers d'ETFs."""

    def Initialize(self):
        self.SetStartDate(2023, 1, 1)
        self.SetCash(100000)

        # Univers de 5 ETFs representant differentes classes d'actifs
        self.etfs = ["SPY", "TLT", "GLD", "QQQ", "IWM"]
        self.symbols = []
        for ticker in self.etfs:
            symbol = self.AddEquity(ticker, Resolution.Daily).Symbol
            self.symbols.append(symbol)

        # TODO etudiant : implementer BuildDashboard() ci-dessous
        self.BuildDashboard()

    def BuildDashboard(self):
        """Construire et logger un tableau de bord multi-actifs.

        Pour chaque symbole dans self.symbols :
          1. Recuperer 252 jours de donnees historiques
          2. Calculer les retours journaliers
          3. Calculer : rendement annualise, volatilite annualisee, Sharpe, max drawdown
          4. Logger les resultats sous forme de tableau comparatif
        """
        # TODO etudiant : implementer l'analyse multi-actifs
        print("Exercice a completer")

7. Exemple Complet: Multi-Timeframe Trend Following (10 min)

7.1 Objectif

Combiner tout ce que nous avons appris pour créer une stratégie de trend following multi-timeframe:

  • Daily trend: SMA 50 jours (tendance globale)
  • Weekly trend: SMA 10 semaines via consolidator (tendance moyen terme)
  • Signal: Acheter seulement si tendance haussière sur les deux timeframes

7.2 Implémentation complète

# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)
from AlgorithmImports import *

class MultiTimeframeTrendFollowing(QCAlgorithm):
    """Stratégie de trend following avec confirmation multi-timeframe."""
    
    def Initialize(self):
        # Configuration
        self.SetStartDate(2015, 1, 1)
        self.SetEndDate(2024, 12, 31)
        self.SetCash(100000)
        
        # Symbole
        self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
        
        # --- TIMEFRAME 1: DAILY ---
        # SMA 50 jours (tendance court terme)
        self.sma_daily_50 = self.SMA(self.symbol, 50, Resolution.Daily)
        
        # --- TIMEFRAME 2: WEEKLY ---
        # Consolidator pour barres hebdomadaires (5 jours de trading)
        self.weekly_consolidator = TradeBarConsolidator(timedelta(days=7))
        self.weekly_consolidator.DataConsolidated += self.OnWeeklyBar
        self.SubscriptionManager.AddConsolidator(self.symbol, self.weekly_consolidator)
        
        # SMA 10 semaines (tendance moyen terme)
        self.sma_weekly_10 = IndicatorExtensions.Of(
            SimpleMovingAverage(10), 
            self.weekly_consolidator
        )
        
        # Variables de tracking
        self.last_weekly_price = 0
        self.last_weekly_sma = 0
        
        # Warm-up: 70 jours pour que les indicateurs soient prêts
        # (10 semaines * 7 jours = 70 jours)
        self.SetWarmUp(70, Resolution.Daily)
    
    def OnWeeklyBar(self, sender, bar):
        """Callback pour les barres hebdomadaires."""
        self.last_weekly_price = bar.Close
        if self.sma_weekly_10.IsReady:
            self.last_weekly_sma = self.sma_weekly_10.Current.Value
    
    def OnData(self, data):
        """Logic principale de trading."""
        if self.IsWarmingUp:
            return
        
        if not data.ContainsKey(self.symbol):
            return
        
        # Attendre que tous les indicateurs soient prêts
        if not (self.sma_daily_50.IsReady and self.sma_weekly_10.IsReady):
            return
        
        # Prix actuel
        price = data[self.symbol].Close
        
        # --- ANALYSE MULTI-TIMEFRAME ---
        
        # Tendance daily: Prix > SMA 50
        daily_bullish = price > self.sma_daily_50.Current.Value
        
        # Tendance weekly: Dernier prix weekly > SMA 10 weekly
        weekly_bullish = self.last_weekly_price > self.last_weekly_sma
        
        # --- SIGNAUX DE TRADING ---
        
        # BUY: Si tendance haussière sur DAILY ET WEEKLY
        if daily_bullish and weekly_bullish:
            if not self.Portfolio.Invested:
                self.SetHoldings(self.symbol, 1.0)
                self.Debug(f"{self.Time} | BUY: Double bullish signal")
                self.Debug(f"  Price: ${price:.2f}")
                self.Debug(f"  Daily SMA50: ${self.sma_daily_50.Current.Value:.2f}")
                self.Debug(f"  Weekly SMA10: ${self.last_weekly_sma:.2f}")
        
        # SELL: Si au moins une tendance devient baissière
        elif not (daily_bullish and weekly_bullish):
            if self.Portfolio.Invested:
                self.Liquidate(self.symbol)
                self.Debug(f"{self.Time} | SELL: Trend weakening")
                if not daily_bullish:
                    self.Debug(f"  Daily trend broke: ${price:.2f} < ${self.sma_daily_50.Current.Value:.2f}")
                if not weekly_bullish:
                    self.Debug(f"  Weekly trend broke: ${self.last_weekly_price:.2f} < ${self.last_weekly_sma:.2f}")
    
    def OnEndOfAlgorithm(self):
        """Afficher les statistiques finales."""
        self.Log("\n=== Statistiques Finales ===")
        self.Log(f"Capital Final: ${self.Portfolio.TotalPortfolioValue:.2f}")
        self.Log(f"Retour Total: {(self.Portfolio.TotalPortfolioValue / 100000 - 1):.2%}")

7.3 Pourquoi cette stratégie fonctionne?

Cette stratégie multi-timeframe évite plusieurs pièges:

Problème Solution
Faux signaux court terme Confirmation hebdomadaire filtre le bruit
Retard des signaux Timeframe daily permet d’entrer rapidement
Whipsaw (aller-retours) Double confirmation réduit les faux signaux
Suroptimisation Paramètres simples (SMA 50, SMA 10)

Performance attendue (SPY 2020-2023): - Capture les grandes tendances haussières - Évite une partie des grandes baisses (2020 COVID, 2022 inflation) - Drawdown réduit vs buy-and-hold

Amélioration possible: Ajouter un filtre de volatilité (ATR) pour ajuster la position size.

Conclusion et Récapitulatif (5 min)

8.1 Ce que vous avez appris

Dans ce notebook, vous avez maîtrisé:

1. History API

  • Récupérer des données historiques (par nombre de périodes, timedelta, dates)
  • Accéder aux colonnes OHLCV
  • Gérer plusieurs symboles

2. Normalisation de Données

  • Comprendre les splits et dividendes
  • Utiliser DataNormalizationMode.Adjusted (recommandé)
  • Éviter les pièges des prix bruts (Raw)

3. Consolidators

  • Créer des barres multi-timeframe (15-min, 1-hour, weekly)
  • Lier des indicateurs aux consolidators
  • Implémenter des stratégies multi-timeframe

4. Données Personnalisées

  • Créer une classe PythonData personnalisée
  • Intégrer des données alternatives (sentiment, économie)

5. Analyse avec pandas

  • Calculer des statistiques (mean, std, Sharpe ratio)
  • Utiliser rolling windows (SMA, volatilité)
  • Analyser des corrélations

8.2 Comparaison des approches

Approche Avantages Inconvénients Usage
History() simple Facile, rapide Consomme beaucoup de données Initialisation, backtesting
Indicators natifs Performants, warm-up automatique Limité aux indicateurs built-in Production
Consolidators Multi-timeframe flexible Plus complexe Stratégies avancées
Custom Data Données alternatives Requires external source Alpha research
Pandas analysis Puissant, flexible Pas optimisé pour live trading Research mode

8.3 Best Practices

À FAIRE: - ✅ Toujours utiliser Adjusted pour normalisation - ✅ Vérifier que les indicateurs sont IsReady avant de trader - ✅ Utiliser SetWarmUp() pour préparer les indicateurs - ✅ Combiner plusieurs timeframes pour robustesse

À ÉVITER: - ❌ Utiliser des prix Raw sans raison spécifique - ❌ Trader pendant la période de warm-up - ❌ Créer des consolidators sur des consolidators (performance) - ❌ Charger des custom data non vérifiées

8.4 Prochaines étapes

Notebook suivant: QC-Py-04-Research-Workflow - Utiliser QuantBook pour recherche interactive - Analyser les données avec Jupyter - Prototyper des stratégies avant backtesting

Autres notebooks suggérés: - QC-Py-05: Portfolio Construction & Risk Management - QC-Py-06: Universe Sélection (screening d’actions) - QC-Py-07: Alpha Models & Framework

8.5 Exercice pratique

Challenge: Modifier la stratégie MultiTimeframeTrendFollowing pour:

  1. Ajouter un troisième timeframe (monthly SMA 3)
  2. N’acheter que si les 3 timeframes sont haussiers
  3. Comparer les performances avec la version 2-timeframes

Bonus: Ajouter un filtre de volatilité: - Ne pas entrer si la volatilité annualisée > 30% - Utiliser StandardDeviation indicator sur 20 jours

8.6 Ressources supplémentaires

Documentation: - QuantConnect Data Library - History API Reference - Consolidators Guide - Custom Data Tutorial

Exemples dans la communauté: - Multi-timeframe stratégies - Custom data examples - Alternative data integration


Félicitations! Vous maîtrisez maintenant la gestion de données dans QuantConnect. C’est la fondation de toute stratégie robuste.

Retour au sommet