# [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}")<< 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 dansmain.pyde votre projet QC LabLes 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).
Résultats du Backtest QC Cloud
Algorithme :
DataExplorationAlgoBT 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 :
HistorySignaturesDemoBT 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:
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)
Structure du DataFrame: Les données sont indexées par date, avec une colonne par métrique (open, high, low, close, volume)
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:
- Splits: Division d’actions (ex: 1 action à $100 devient 2 actions à $50)
- 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 :
DataNormalizationDemoBT 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:
- Callback DataConsolidated: Appelé chaque fois qu’une nouvelle barre est consolidée (ex: toutes les 15 minutes)
- IndicatorExtensions.Of(): Lie un indicateur à un consolidator pour qu’il se mette à jour automatiquement
- 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 exercice5. 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)
passRé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].TenYearRé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 :
PandasAnalysisDemoBT 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
PythonDatapersonnalisé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:
- Ajouter un troisième timeframe (monthly SMA 3)
- N’acheter que si les 3 timeframes sont haussiers
- 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.