[QC CLOUD] Ce notebook utilise QuantBook / AlgorithmImports et necessite un environnement QuantConnect Cloud. Les cellules de code ne sont pas executables localement.
Données non-traditionnelles pour generer de l’alpha Duree: 75 minutes | Niveau: Intermediaire-Avance | Python + QuantConnect
Objectifs d’Apprentissage
A la fin de ce notebook, vous serez capable de :
Comprendre le concept d’Alternative Data et son rôle dans la generation d’alpha
Acceder aux Fundamentals Data de Morningstar (P/E, ROE, revenus)
Construire une stratégie Value basee sur les fondamentaux
Comprendre les SEC Filings (8-K) et leur utilisation
Créer des Custom Data Sources pour integrer des données externes
Importer des données CSV locales personnalisees
Utiliser les Economic Indicators (macro data)
Construire une stratégie Event-Driven basee sur les earnings
Prerequis
Notebooks QC-Py-01 a 15 completes
Comprehension du Framework Algorithm et des Alpha Models
Notions de base en analyse fondamentale
(Optionnel) Cle API NewsAPI pour les exemples avances
Structure du Notebook
Introduction aux Données Alternatives (15 min)
Fundamentals Data Morningstar (25 min)
SEC Filings (20 min)
Custom Data Sources (30 min)
Economic Indicators (15 min)
Stratégie Event-Driven: Earnings (20 min)
Note de conception : Ce notebook contient du code de reference a copier dans QuantConnect Lab (main.py). Les cellules ne sont pas prevues pour etre executees en tant que notebook Jupyter. L’absence d’outputs (execution_count: null) est intentionnelle.
[REFERENCE QC Cloud] Ce notebook illustre du code QuantConnect a executer dans l’IDE Cloud (https://www.quantconnect.com/research). L’environnement local ne dispose pas de QuantBook ni de l’historical data feed. Pour executer : cloner le projet QC associe, ouvrir research.ipynb, executer cellule par cellule.
Mode d’emploi : Ce notebook est un document de reference (non executable). Le code QCAlgorithm presente ici est a copier dans main.py de votre projet QuantConnect Lab. Ne tentez pas de faire “Run All” – les imports AlgorithmImports n’existent pas en Jupyter local.
Pour demarrer un projet QC : copiez un main.py depuis projects/ ou partner-course-quant-trading/templates/, puis adaptez-le.
Partie 1 : Introduction aux Données Alternatives (15 min)
Qu’est-ce que l’Alternative Data?
L’Alternative Data (données alternatives) designe toute source de données non-traditionnelle utilisee pour generer un avantage informationnel (alpha) dans les stratégies d’investissement.
Contrairement aux données de marche standard (prix, volume), les données alternatives offrent des insights uniques sur l’economie, les entreprises ou le sentiment des investisseurs.
Types de Données Alternatives
Catégorie
Exemples
Source
Satellite
Images de parkings, reservoirs, champs
Planet Labs, Orbital Insight
Social Media
Tweets, posts Reddit, avis
Twitter API, StockTwits
Web Traffic
Visites de sites, app downloads
SimilarWeb, App Annie
Sentiment
Analyse de news, NLP
Brain, Tiingo, NewsAPI
Fundamentals
Revenus, P/E, ROE
Morningstar, Bloomberg
SEC Filings
8-K, 10-Q, 13-F
SEC EDGAR
Geolocation
Trafic en magasin, mobilite
Foursquare, SafeGraph
Consumer
Transactions cartes, e-commerce
Second Measure, Earnest
Pourquoi l’Alternative Data?
Avantage Informationnel
|
+---------------------------+---------------------------+
| | |
v v v
Données de Marche Données Alt. Données Proprietaires
(Commoditise) (Competitive Edge) (Exclusif)
- Prix, Volume - Sentiment - Modèles internes
- Faible alpha - Web scraping - Research privee
- Tout le monde - Satellite - Avantage maximal
y a acces - Consumer data
Exemples d’Alpha Generation
Stratégie
Donnee Alternative
Signal
Retail Earnings
Images satellite des parkings
Plus de voitures = bonnes ventes
Oil Forecast
Niveaux des reservoirs petrole
Offre/demande avant publication officielle
Tech Sentiment
Tweets sur AAPL
Sentiment positif = acheter avant annonce
Value Investing
Fundamentals Morningstar
Low P/E + High ROE = sous-evalue
# Imports pour ce notebook# Ces imports fonctionnent dans l'environnement QuantConnectfrom AlgorithmImports import*import jsonfrom datetime import datetime, timedeltaprint("Alternative Data dans QuantConnect")print("="*50)print("\nSources de donnees couvertes dans ce notebook:")print(" 1. Fundamentals Morningstar (P/E, ROE, Revenue)")print(" 2. SEC Filings (8-K forms)")print(" 3. Custom Data Sources (APIs externes)")print(" 4. Donnees CSV locales")print(" 5. Economic Indicators (macro)")print(" 6. Earnings Calendar")
Alternative Data dans QuantConnect==================================================Sources de donnees couvertes dans ce notebook: 1. Fundamentals Morningstar (P/E, ROE, Revenue) 2. SEC Filings (8-K forms) 3. Custom Data Sources (APIs externes) 4. Donnees CSV locales 5. Economic Indicators (macro) 6. Earnings Calendar
Données Alternatives dans QuantConnect
QuantConnect offre plusieurs sources de données alternatives via son Data Library :
Dataset
Provider
Tier
Description
US Fundamentals
Morningstar
Free
P/E, ROE, Revenue, Market Cap
SEC 8-K Reports
SEC
Free
Corporate events filings
Tiingo News
Tiingo
Paid
News articles avec sentiment
Brain Sentiment
Brain
Paid
NLP sentiment sur 10K/10Q
Estimize
Estimize
Paid
Consensus earnings crowd-sourced
Note Free Tier : Ce notebook utilise NewsAPI (gratuit, 100 req/jour) comme alternative a Tiingo News (payant).
Architecture d’Integration
QuantConnect Algorithm
|
+---------------------------+---------------------------+
| | |
v v v
Built-in Data AddData() Custom Data Sources
- Fundamentals - SEC Filings - PythonData class
- Fine Sélection - News feeds - GetSource()
- Reader()
Partie 2 : Fundamentals Data Morningstar (25 min)
2.1 Acceder aux Fundamentals
Les données fondamentales Morningstar sont accessibles via le Fine Universe Sélection. Elles incluent :
Construisons une stratégie Value Investing basee sur les fondamentaux Morningstar.
Critères de sélection : - P/E Ratio entre 5 et 15 (pas trop bas pour eviter les value traps) - ROE > 15% (entreprise profitable) - Market Cap > $5B (large cap pour liquidite) - Rebalancement trimestriel
Théorie Value Investing
Warren Buffett Approach
|
+---------------------------+---------------------------+
| | |
v v v
Margin of Safety Quality Business Long-term Holding
- Low P/E - High ROE - Years, not days
- Low P/B - Moat (competitive) - Compound growth
- Below intrinsic - Consistent earnings
value
Ancre savante – Graham, B. (1949), « The Intelligent Investor: A Book of Practical Counsel », Harper & Brothers, New York (1re edition 1949 ; editions revises 1954, 1959, 1965, 1973). Origine canonique de l’investissement value : le chapitre 20 formalise la marge de securite (margin of safety) comme concept central – acheter un actif sensiblement en dessous de sa valeur intrinseque pour que la marge absorbe l’erreur d’estimation et les aleas defavorables. Graham y distingue aussi l’investisseur defensif de l’investisseur entreprenant et introduit l’allegorie de Mr. Market. Warren Buffett, eleve de Graham a Columbia, en a fait l’application moderne illustree par le schema ci-dessus (marge de securite + qualite de l’entreprise + detention long terme) – les critères P/E bas, ROE eleve et market cap liquide de la stratégie Value Complete materialisent ce principe.
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# Strategie Value complete avec Fundamentals# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class ValueInvestingStrategy(QCAlgorithm):""" Strategie Value Investing: - Selection: Low P/E (5-15) + High ROE (>15%) - Filtre: Large Cap (>5B), exclut financials - Rebalancement: Trimestriel - Allocation: Equal-weight """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# Universe settingsself.UniverseSettings.Resolution = Resolution.Dailyself.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)# Parametres de la strategieself.num_stocks =20self.min_pe =5self.max_pe =15self.min_roe =0.15# 15%self.min_market_cap =5e9# $5B# Tracking pour rebalancement trimestrielself.last_rebalance =Noneself.rebalance_days =90# ~trimestriel# Reference pour scheduleself.spy =self.AddEquity("SPY", Resolution.Daily).Symbolself.Log("Value Investing Strategy initialized")self.Log(f" P/E range: {self.min_pe} - {self.max_pe}")self.Log(f" Min ROE: {self.min_roe:.0%}")self.Log(f" Min Market Cap: ${self.min_market_cap/1e9:.0f}B")def CoarseSelectionFunction(self, coarse):"""Filtre initial: liquidite et prix."""# Verifier si c'est le moment de rebalancerifself.last_rebalance isnotNone: days_since = (self.Time -self.last_rebalance).daysif days_since <self.rebalance_days:return Universe.Unchanged# Filtrer filtered = [x for x in coarse if x.HasFundamentalDataand x.Price >10and x.DollarVolume >5000000] sorted_stocks =sorted(filtered, key=lambda x: x.DollarVolume, reverse=True)self.Log(f"Coarse: {len(coarse)} -> {len(filtered)} filtered -> 500 candidates")return [x.Symbol for x in sorted_stocks[:500]]def FineSelectionFunction(self, fine):"""Filtre Value: P/E, ROE, Market Cap."""self.last_rebalance =self.Time value_stocks = []for stock in fine:try:# Exclure le secteur financier (P/E non comparable)if stock.AssetClassification.MorningstarSectorCode == MorningstarSectorCode.FinancialServices:continue# Criteres de selection pe = stock.ValuationRatios.PERatio roe = stock.OperationRatios.ROE.Value market_cap = stock.MarketCap# Verifier que les donnees existentif pe isNoneor roe isNoneor market_cap isNone:continue# Appliquer les filtres Valueif (self.min_pe < pe <self.max_pe and roe >self.min_roe and market_cap >self.min_market_cap): value_stocks.append({'symbol': stock.Symbol,'pe': pe,'roe': roe,'market_cap': market_cap,# Composite score: low P/E + high ROE'value_score': (1/pe) * roe *100 })exceptException:continue# Trier par value score (plus eleve = meilleur) sorted_by_score =sorted(value_stocks, key=lambda x: x['value_score'], reverse=True)# Log les top picksself.Log(f"\nValue Selection {self.Time.strftime('%Y-%m-%d')}:")for i, stock inenumerate(sorted_by_score[:5]):self.Log(f" {i+1}. {stock['symbol'].Value}: P/E={stock['pe']:.1f}, ROE={stock['roe']:.1%}, Score={stock['value_score']:.2f}")self.Log(f"Fine: {len(fine)} -> {len(value_stocks)} value -> {self.num_stocks} selected")return [x['symbol'] for x in sorted_by_score[:self.num_stocks]]def OnSecuritiesChanged(self, changes):"""Rebalancer lors des changements d'univers."""# Vendre les sortantsfor security in changes.RemovedSecurities:if security.Invested:self.Liquidate(security.Symbol)self.Log(f"SELL: {security.Symbol.Value}")# Calculer le poids cible (exclure SPY) trading_symbols = [s for s inself.ActiveSecurities.Keys if s !=self.spy] count =len(trading_symbols)if count ==0:return weight =1.0/ count# Acheter les nouvelles positionsfor symbol in trading_symbols:self.SetHoldings(symbol, weight)self.Log(f"Rebalanced: {count} positions @ {weight:.1%}")def OnEndOfAlgorithm(self):self.Log("="*50)self.Log("VALUE INVESTING STRATEGY SUMMARY")self.Log("="*50)self.Log(f"Final Value: ${self.Portfolio.TotalPortfolioValue:,.2f}") total_return = (self.Portfolio.TotalPortfolioValue -100000) /100000self.Log(f"Total Return: {total_return:.1%}")print("ValueInvestingStrategy defini")print("\nFiltres Value:")print(" - P/E entre 5 et 15")print(" - ROE > 15%")print(" - Market Cap > $5B")print(" - Exclut secteur financier")
ValueInvestingStrategy — rebalance via OnSecuritiesChanged sur fenêtre 90 j ; 10 ans executes (2015-2024). Verdict #3801 : REAL - performance reelle verifiee. Le champ totalOrders n’est pas expose de facon fiable par l’API backtest, mais la courbe d’equity confirme l’activite (CAGR +8.2%/an).
Pour filtrer par secteur, utilisez MorningstarSectorCode :
Code
Secteur
Caractéristiques P/E
BasicMaterials
Materiaux
Cyclique
ConsumerCyclical
Conso. cyclique
Cyclique
FinancialServices
Finance
P/E non comparable
RealEstate
Immobilier
Utiliser FFO
ConsumerDefensive
Conso. defensive
Stable
Healthcare
Sante
Mix growth/value
Utilities
Services publics
Dividende
CommunicationServices
Communication
Mix
Energy
Energie
Très cyclique
Industrials
Industrie
Cyclique
Technology
Technologie
Growth, P/E eleve
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# Exemple: Filtrage par secteur dans Fine Selection# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class SectorFilterExample(QCAlgorithm):""" Exemple de filtrage par secteur Morningstar. Selectionne uniquement les actions du secteur Technology. """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)self.last_month =-1def CoarseSelectionFunction(self, coarse):ifself.Time.month ==self.last_month:return Universe.Unchangedself.last_month =self.Time.month filtered = [x for x in coarse if x.HasFundamentalData and x.Price >10and x.DollarVolume >10000000] sorted_stocks =sorted(filtered, key=lambda x: x.DollarVolume, reverse=True)return [x.Symbol for x in sorted_stocks[:500]]def FineSelectionFunction(self, fine):"""Filtrer par secteur Technology uniquement.""" tech_stocks = []for stock in fine:# Verifier le secteur sector = stock.AssetClassification.MorningstarSectorCodeif sector == MorningstarSectorCode.Technology: tech_stocks.append(stock)# Trier par Market Cap sorted_by_cap =sorted(tech_stocks, key=lambda x: x.MarketCap or0, reverse=True)self.Log(f"Tech sector: {len(tech_stocks)} stocks found")return [x.Symbol for x in sorted_by_cap[:20]]print("SectorFilterExample defini")print("\nCodes secteur disponibles:")print(" MorningstarSectorCode.Technology")print(" MorningstarSectorCode.Healthcare")print(" MorningstarSectorCode.FinancialServices")print(" MorningstarSectorCode.ConsumerCyclical")print(" etc.")
Les fundamentals Morningstar permettent de classer les secteurs par attractivite. Une stratégie de rotation sectorielle alloue plus de capital aux secteurs les plus attractifs.
Objectif : Implementer une stratégie qui selectionne les 2 meilleurs secteurs selon les fundamentals et sous-pondere les autres.
Règles : - Classez les 11 secteurs Morningstar par ratio P/E (croissant) et ROE (decroissant) - Score combine = rank(PE) * 0.4 + rank(ROE) * 0.6 - Allouez 30% aux 2 meilleurs secteurs, 5% aux 3 suivants, 0% aux autres - Rebalance mensuellement
Indices : - Indice : Fundamentals[symbol].ValuationRatios.PERatio pour le P/E - Indice : Fundamentals[symbol].OperationRatios.ROE.Value pour le ROE - Indice : FineFundamental dans OnFrameworkData donne acces aux fundamentals
# [REFERENCE QC] Exercice 1 : Rotation sectorielle basee sur les fundamentals# TODO etudiant : Implementer une allocation sectorielle dynamique# Indice : Scorer les secteurs par PE + ROE, allouer proportionnellement# Etape 1 : Selectionner les actifs dans chaque secteur Morningstar# Etape 2 : Calculer le score combine (PE + ROE) par secteur# Etape 3 : Classer et determiner les poids d'allocation# Etape 4 : Generer les insights d'allocation dans OnFrameworkDataresult =None# TODO etudiant : remplacer par la strategie de rotation sectorielleprint("Exercice a completer")
Partie 3 : SEC Filings (20 min)
3.1 Comprendre les SEC Filings
Les SEC Filings sont des documents officiels deposes aupres de la Securities and Exchange Commission. Ils contiennent des informations importantes sur les entreprises cotees.
Types de Filings
Form
Description
Frequence
8-K
Current Report - événements materiels
Ad hoc
10-K
Annual Report
Annuel
10-Q
Quarterly Report
Trimestriel
13-F
Institutional Holdings
Trimestriel
4
Insider Transactions
Ad hoc
8-K Events (Current Reports)
Les 8-K sont particulierement interessants car ils annoncent des événements importants :
Item
Événement
Impact Potentiel
1.01
Entry into Material Agreement
Positif
1.02
Termination of Agreement
Negatif
2.01
Completion of Acquisition
Variable
2.02
Results of Opérations (Earnings)
Majeur
5.02
Departure/Appointment of Directors
Variable
5.07
Shareholder Vote Results
Informatif
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# SEC 8-K Filings dans QuantConnect# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class SEC8KAlgorithm(QCAlgorithm):""" Strategie basee sur les SEC 8-K filings. Detecte les evenements materiels et trade en consequence. Note: L'acces aux donnees SEC peut necessiter un abonnement. Cet exemple montre la structure d'integration. """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# Ajouter les actions a surveillerself.symbols = [self.AddEquity("AAPL", Resolution.Daily).Symbol,self.AddEquity("MSFT", Resolution.Daily).Symbol,self.AddEquity("GOOGL", Resolution.Daily).Symbol, ]# Dictionnaire pour tracker les filingsself.recent_filings = {}self.Log("SEC 8-K Algorithm initialized")self.Log("Monitoring: "+", ".join([s.Value for s inself.symbols]))def OnData(self, data):""" Note: En production, vous utiliseriez AddData() avec SECReport8K pour recevoir les filings en temps reel. Exemple de structure: if data.ContainsKey(self.sec_symbol): report = data[self.sec_symbol] filing_date = report.Time form_type = report.FormType # Analyser le contenu if "acquisition" in report.Report.lower(): self.SetHoldings(symbol, 0.1) """passprint("SEC8KAlgorithm defini (structure d'exemple)")print("\nNote: L'integration complete des SEC filings dans QuantConnect")print("necessite l'acces au dataset SEC via le Data Library.")print("\nTypes d'analyses possibles:")print(" - Detection d'acquisitions (Item 2.01)")print(" - Earnings surprises (Item 2.02)")print(" - Changements de direction (Item 5.02)")
On connecte ici une source de données alternatives (sentiment, actualités, données satellitaires) à l’environnement QuantConnect.
# Exemple: Analyse NLP simple des SEC Filings# Demonstration de keyword detection# Liste de mots-cles pour analyse de filingsBULLISH_KEYWORDS = ['growth', 'expansion', 'increase', 'profit', 'beat', 'exceed','acquisition', 'partnership', 'innovation', 'dividend', 'buyback']BEARISH_KEYWORDS = ['decline', 'loss', 'decrease', 'investigation', 'lawsuit', 'layoff','restructuring', 'impairment', 'default', 'warning', 'miss']def analyze_filing_sentiment(text):""" Analyse simple du sentiment d'un filing SEC. Args: text: Contenu textuel du filing Returns: dict: score et classification """ text_lower = text.lower()# Compter les keywords bullish_count =sum(1for kw in BULLISH_KEYWORDS if kw in text_lower) bearish_count =sum(1for kw in BEARISH_KEYWORDS if kw in text_lower)# Calculer le score total = bullish_count + bearish_countif total ==0:return {'score': 0, 'classification': 'NEUTRAL', 'bullish': 0, 'bearish': 0} score = (bullish_count - bearish_count) / totalif score >0.2: classification ='BULLISH'elif score <-0.2: classification ='BEARISH'else: classification ='NEUTRAL'return {'score': score,'classification': classification,'bullish': bullish_count,'bearish': bearish_count }# Test avec exemplestest_texts = ["Company announces record profit and expansion into new markets","Investigation opened, significant loss expected, layoffs announced","Quarterly results in line with expectations"]print("Analyse de sentiment de filings SEC:")print("="*50)for text in test_texts: result = analyze_filing_sentiment(text)print(f"\nTexte: {text[:50]}...")print(f" Classification: {result['classification']}")print(f" Score: {result['score']:.2f}")print(f" Bullish keywords: {result['bullish']}, Bearish: {result['bearish']}")
Analyse de sentiment de filings SEC:==================================================Texte: Company announces record profit and expansion into... Classification: BULLISH Score: 1.00 Bullish keywords: 2, Bearish: 0Texte: Investigation opened, significant loss expected, l... Classification: BEARISH Score: -1.00 Bullish keywords: 0, Bearish: 3Texte: Quarterly results in line with expectations... Classification: NEUTRAL Score: 0.00 Bullish keywords: 0, Bearish: 0
3.2 Structure d’Integration SEC (Reference)
Voici comment integrer les SEC filings quand ils sont disponibles :
# Dans Initialize()# Ajouter les données SEC pour un symboleself.sec_symbol =self.AddData(SECReport8K, "AAPL").Symbol# Dans OnData()def OnData(self, data):if data.ContainsKey(self.sec_symbol): report = data[self.sec_symbol]# Informations disponibles filing_date = report.Time form_type = report.FormType # "8-K" report_text = report.Report # Contenu textuel# Analyser et traderself.ProcessFiling(report)
Analyse NLP des Filings :
Les traders quantitatifs utilisent souvent le NLP pour analyser le texte des filings : - Sentiment Analysis : Positif/negatif - Keyword Detection : “acquisition”, “termination”, “investigation” - Complexity Analysis : Readability scores
Ancre savante – Loughran, T. & McDonald, B. (2011), « When Is a Liability Not a Liability? Textual Analysis, Dictionaries, and 10-Ks », The Journal of Finance 66(1):35-65 (DOI 10.1111/j.1540-6261.2010.01625.x). Montre que les dictionnaires de sentiment generiques (notamment le Harvard IV-4) classent a tort comme negatifs pres de trois quarts des mots frequents en contexte financier (ex. « liability », « tax », « cost » n’y sont pas pejoratifs) ; les auteurs construisent donc un lexique finance-spécifique (Loughran-McDonald Master Dictionary, six listes : negative, positive, uncertainty, litigious, constraining, superfluous) calibre sur un large corpus de 10-Ks 1994-2008, qu’ils relient au rendement, au volume, a la volatilite et aux fraudes. C’est la reference methodologique du NLP sur filings enseigne dans cette section 3.2 (sentiment, keyword detection, readability) – la raison pour laquelle un lexique finance-dedie est preferable a un dictionnaire generaliste pour analyser le texte des SEC filings.
Partie 4 : Custom Data Sources (30 min)
4.1 Créer une Custom Data Source
QuantConnect permet d’integrer des sources de données externes via la classe PythonData. C’est essentiel pour :
APIs externes : NewsAPI, Twitter, Alpha Vantage
Fichiers CSV : Données historiques personnalisees
Web scraping : Données proprietaires
Structure d’une Custom Data Class
class MyCustomData(PythonData):def GetSource(self, config, date, isLiveMode):# Retourne l'URL ou le chemin de la sourcereturn SubscriptionDataSource(url, transport_medium)def Reader(self, config, line, date, isLiveMode):# Parse une ligne de données# Retourne un objet MyCustomData ou Nonereturn custom_data_object
Transport Mediums
Medium
Description
RemoteFile
Fichier distant (HTTP/HTTPS)
LocalFile
Fichier local
Rest
API REST
# Custom Data Source: NewsAPI# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*import jsonclass NewsAPIData(PythonData):""" Custom Data Source pour NewsAPI. NewsAPI offre un tier gratuit avec 100 requetes/jour. Documentation: https://newsapi.org/docs Note: En production, vous devriez: 1. Stocker la cle API de maniere securisee 2. Gerer le rate limiting 3. Cacher les resultats """# Cle API (remplacer par votre cle) API_KEY ="YOUR_NEWSAPI_KEY_HERE"def GetSource(self, config, date, isLiveMode):""" Retourne l'URL de la source de donnees. Parameters: config: Configuration de la subscription date: Date demandee isLiveMode: True si en mode live Returns: SubscriptionDataSource avec l'URL """# Construire l'URL NewsAPI# En mode backtest, on utiliserait des donnees historiques stockees# En mode live, on appelle l'API en temps reel ticker = config.Symbol.Value.replace(" ", "%20") date_str = date.strftime("%Y-%m-%d")# URL pour les articles sur le ticker url =f"https://newsapi.org/v2/everything?q={ticker}&from={date_str}&sortBy=publishedAt&apiKey={self.API_KEY}"return SubscriptionDataSource( url, SubscriptionTransportMedium.RemoteFile, FileFormat.UnfoldingCollection # Pour JSON avec plusieurs items )def Reader(self, config, line, date, isLiveMode):""" Parse les donnees JSON de NewsAPI. Parameters: config: Configuration line: Ligne de donnees (JSON string) date: Date demandee isLiveMode: True si live Returns: NewsAPIData object ou None """ifnot line.strip():returnNonetry: data = json.loads(line)# Verifier si c'est une reponse valideif'articles'notin data:returnNone# Creer un objet NewsAPIData pour chaque article articles = []for article in data['articles']: news = NewsAPIData() news.Symbol = config.Symbol# Parser la date de publication pub_date = article.get('publishedAt', '')if pub_date: news.Time = datetime.strptime(pub_date[:19], '%Y-%m-%dT%H:%M:%S')else: news.Time = date# Stocker les proprietes de l'article news['Title'] = article.get('title', '') news['Description'] = article.get('description', '') news['Source'] = article.get('source', {}).get('name', '') news['URL'] = article.get('url', '')# Value par defaut (peut etre utilise pour scoring) news.Value =1.0 articles.append(news)# Retourner le premier article (simplifie)return articles[0] if articles elseNoneexceptExceptionas e:returnNoneprint("NewsAPIData Custom Data Source defini")print("\nMethodes implementees:")print(" 1. GetSource(): Retourne l'URL de l'API")print(" 2. Reader(): Parse le JSON et cree des objets")print("\nProprietes disponibles apres parsing:")print(" - Title, Description, Source, URL")
NewsAPIData Custom Data Source definiMethodes implementees: 1. GetSource(): Retourne l'URL de l'API 2. Reader(): Parse le JSON et cree des objetsProprietes disponibles apres parsing: - Title, Description, Source, URL
On prétraite et normalise les données alternatives pour les rendre exploitables dans un modèle de trading.
# Exemple avance: Custom Data avec caching# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*import jsonclass CachedNewsData(PythonData):""" Version avec cache pour reduire les appels API. Utile en backtest pour ne pas depasser les quotas. """# Cache statique (partagee entre instances) _cache = {}def GetSource(self, config, date, isLiveMode):# En backtest, charger depuis fichier cacheifnot isLiveMode: cache_file =f"data/news_cache/{config.Symbol.Value}/{date.strftime('%Y-%m-%d')}.json"return SubscriptionDataSource( cache_file, SubscriptionTransportMedium.LocalFile )# En live, appeler l'API ticker = config.Symbol.Valuereturn SubscriptionDataSource(f"https://newsapi.org/v2/everything?q={ticker}&apiKey=YOUR_KEY", SubscriptionTransportMedium.RemoteFile )def Reader(self, config, line, date, isLiveMode):ifnot line.strip():returnNonetry: data = json.loads(line) news = CachedNewsData() news.Symbol = config.Symbol news.Time = date# Stocker les donneesif'title'in data: news['Title'] = data['title'] news['Sentiment'] = data.get('sentiment', 0) news.Value = data.get('sentiment', 0)return newsexceptException:returnNoneprint("CachedNewsData defini")print("\nPattern de caching:")print(" 1. Backtest: charger depuis fichiers JSON locaux")print(" 2. Live: appeler l'API en temps reel")print(" 3. Pre-generer le cache avant le backtest")
CachedNewsData definiPattern de caching: 1. Backtest: charger depuis fichiers JSON locaux 2. Live: appeler l'API en temps reel 3. Pre-generer le cache avant le backtest
On intègre le signal issu des données alternatives dans la logique de génération d’ordres de l’algorithme.
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# Utilisation de la Custom Data Source NewsAPI# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class NewsAPIAlgorithm(QCAlgorithm):""" Algorithme utilisant NewsAPI comme source de donnees. Detecte les news et trade en consequence. """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# Ajouter l'action principaleself.aapl =self.AddEquity("AAPL", Resolution.Hour).Symbol# Ajouter la Custom Data Source# self.news_symbol = self.AddData(NewsAPIData, "AAPL", Resolution.Hour).Symbol# Compteur de newsself.news_count =0self.Log("NewsAPI Algorithm initialized")def OnData(self, data):""" Traite les donnees de marche et les news. """# Verifier si des news sont disponibles# if data.ContainsKey(self.news_symbol):# news = data[self.news_symbol]# self.ProcessNews(news)passdef ProcessNews(self, news):""" Analyse une news et genere des signaux. En production, vous utiliseriez: - Sentiment analysis (VADER, TextBlob) - Keyword detection - NLP models """ title = news['Title'].lower()# Simple keyword detection bullish_keywords = ['beats', 'exceeds', 'growth', 'upgrade', 'positive'] bearish_keywords = ['misses', 'decline', 'downgrade', 'negative', 'lawsuit'] is_bullish =any(kw in title for kw in bullish_keywords) is_bearish =any(kw in title for kw in bearish_keywords)self.Log(f"News: {news['Title'][:50]}...")self.Log(f" Source: {news['Source']}, Bullish: {is_bullish}, Bearish: {is_bearish}")# Trading basique sur keywordsif is_bullish andnot is_bearish:ifnotself.Portfolio[self.aapl].Invested:self.SetHoldings(self.aapl, 0.5)self.Log("BUY signal on positive news")elif is_bearish andnot is_bullish:ifself.Portfolio[self.aapl].Invested:self.Liquidate(self.aapl)self.Log("SELL signal on negative news")print("NewsAPIAlgorithm defini")print("\nNote: L'exemple complet necessite une cle API NewsAPI valide.")print("Inscrivez-vous gratuitement sur https://newsapi.org/")
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# Custom Data Source: CSV Local# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class EconomicCSVData(PythonData):""" Custom Data Source pour donnees economiques en CSV. Format CSV attendu: date,gdp_growth,unemployment,inflation 2023-01-01,2.5,3.5,4.2 """def GetSource(self, config, date, isLiveMode):""" Retourne le chemin du fichier CSV. Note: Le fichier doit etre uploade dans QuantConnect via le panneau 'Data' ou place dans le dossier /data/ """# Chemin vers le fichier CSV# En production, utilisez le chemin dans votre projet QC path ="data/economic_indicators.csv"return SubscriptionDataSource( path, SubscriptionTransportMedium.LocalFile )def Reader(self, config, line, date, isLiveMode):""" Parse une ligne du fichier CSV. """# Ignorer les lignes vides et l'en-teteifnot line.strip() or line.startswith('date'):returnNonetry:# Parser les colonnes cols = line.split(',') data = EconomicCSVData() data.Symbol = config.Symbol# Parser la date (colonne 0) data.Time = datetime.strptime(cols[0].strip(), '%Y-%m-%d')# Stocker les indicateurs economiques data['GDP_Growth'] =float(cols[1]) data['Unemployment'] =float(cols[2]) data['Inflation'] =float(cols[3])# Value = GDP Growth (peut servir pour filtering) data.Value =float(cols[1])return dataexceptExceptionas e:returnNoneclass CSVDataAlgorithm(QCAlgorithm):""" Algorithme utilisant des donnees CSV economiques. Tactical allocation basee sur les indicateurs macro. """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# ETFs pour allocation tactiqueself.spy =self.AddEquity("SPY", Resolution.Daily).Symbol # Actionsself.tlt =self.AddEquity("TLT", Resolution.Daily).Symbol # Obligationsself.gld =self.AddEquity("GLD", Resolution.Daily).Symbol # Or# Ajouter les donnees CSV# self.econ_data = self.AddData(EconomicCSVData, "ECON", Resolution.Daily).Symbol# Derniers indicateurs connusself.last_gdp =Noneself.last_inflation =Noneself.Log("CSV Data Algorithm initialized")def OnData(self, data):""" Allocation tactique basee sur les indicateurs macro. """# En production avec les donnees CSV:# if data.ContainsKey(self.econ_data):# econ = data[self.econ_data]# self.last_gdp = econ['GDP_Growth']# self.last_inflation = econ['Inflation']# self.Rebalance()passdef Rebalance(self):""" Allocation tactique basee sur l'environnement macro. | GDP High | GDP Low | |----------|----------| | Inflation High: SPY 30%, TLT 20%, GLD 50% | | Inflation Low: SPY 70%, TLT 30%, GLD 0% | """ifself.last_gdp isNoneorself.last_inflation isNone:return# Seuils gdp_high =self.last_gdp >2.0 inflation_high =self.last_inflation >3.0if gdp_high andnot inflation_high:# Expansion sans inflation = risk-onself.SetHoldings(self.spy, 0.70)self.SetHoldings(self.tlt, 0.30)self.SetHoldings(self.gld, 0.00)elif gdp_high and inflation_high:# Expansion avec inflation = orself.SetHoldings(self.spy, 0.30)self.SetHoldings(self.tlt, 0.20)self.SetHoldings(self.gld, 0.50)elifnot gdp_high andnot inflation_high:# Recession sans inflation = bondsself.SetHoldings(self.spy, 0.20)self.SetHoldings(self.tlt, 0.70)self.SetHoldings(self.gld, 0.10)else:# Stagflation = defensiveself.SetHoldings(self.spy, 0.10)self.SetHoldings(self.tlt, 0.40)self.SetHoldings(self.gld, 0.50)self.Log(f"Rebalanced: GDP={self.last_gdp:.1f}%, Inflation={self.last_inflation:.1f}%")print("EconomicCSVData et CSVDataAlgorithm definis")print("\nPour utiliser en production:")print(" 1. Creer le fichier CSV avec vos donnees")print(" 2. Uploader dans QuantConnect (Data panel)")print(" 3. Decommentez les lignes AddData()")
Exercice 2 : Source de données personnalisee multi-format
QuantConnect accepte les CSV mais aussi les JSON et les données en temps reel via API. Cet exercice combine deux sources en une seule custom data source.
Objectif : Créer une custom data source qui fusionne des données CSV (historique) et JSON (temps reel simule).
Règles : - Definissez une classe MultiFormatData heritant de DynamicData - Méthode GetSource() : retourne différentes URL selon la date (avant 2020 = CSV, après = JSON) - Méthode Reader() : parsez selon le format et remplissez les proprietes - Proprietes : sentiment_score, volume_indicator, signal_strength
Indices : - Indice : SourceType peut etre Csv ou Rest selon le format - Indice : Utilisez self.SetField() pour les propriete dynamiques
# [REFERENCE QC] Exercice 2 : Custom data source multi-format (CSV + JSON)# TODO etudiant : Creer une source fusionnant CSV historique et JSON temps reel# Indice : Heriter de DynamicData, differencier par date dans GetSource()# Etape 1 : Definir la classe MultiFormatData(PythonData)# Etape 2 : Implementer GetSource() avec logique CSV/JSON selon la date# Etape 3 : Implementer Reader() avec parsing multi-format# Etape 4 : Utiliser les donnees dans OnData()result =None# TODO etudiant : remplacer par la source multi-formatprint("Exercice a completer")
5.2 FRED Data Integration
Pour integrer des données FRED (Federal Reserve Economic Data), vous pouvez créer une Custom Data Source qui charge les series economiques.
Series FRED utiles pour le trading :
Code FRED
Description
Usage
FEDFUNDS
Fed Funds Rate
Politique monetaire
UNRATE
Unemployment Rate
Sante economique
CPIAUCSL
CPI All Items
Inflation
T10Y2Y
10Y-2Y Spread
Yield curve, recession
VIXCLS
VIX Close
Volatilite, stress
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# Custom Data Source: FRED Economic Data# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class FREDData(PythonData):""" Custom Data Source pour FRED (Federal Reserve Economic Data). Note: Necessite un fichier CSV avec les donnees FRED pre-telechargees ou une API key pour Alpha Vantage / Quandl qui fournissent FRED. """def GetSource(self, config, date, isLiveMode):# Charger depuis fichier CSV local# Format: date,value series_code = config.Symbol.Value # ex: "FEDFUNDS" path =f"data/fred/{series_code}.csv"return SubscriptionDataSource( path, SubscriptionTransportMedium.LocalFile )def Reader(self, config, line, date, isLiveMode):ifnot line.strip() or line.startswith('date'):returnNonetry: cols = line.split(',') fred = FREDData() fred.Symbol = config.Symbol fred.Time = datetime.strptime(cols[0].strip(), '%Y-%m-%d') fred.Value =float(cols[1]) fred['Rate'] =float(cols[1])return fredexceptException:returnNoneclass YieldCurveAlgorithm(QCAlgorithm):""" Strategie basee sur la yield curve (10Y-2Y spread). - Spread positif: economie saine -> risk-on - Spread negatif (inversion): recession proche -> risk-off """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# ETFsself.spy =self.AddEquity("SPY", Resolution.Daily).Symbolself.tlt =self.AddEquity("TLT", Resolution.Daily).Symbol# FRED data (si disponible)# self.yield_spread = self.AddData(FREDData, "T10Y2Y", Resolution.Daily).Symbolself.last_spread =None# Schedule mensuelself.Schedule.On(self.DateRules.MonthStart("SPY"),self.TimeRules.AfterMarketOpen("SPY", 30),self.Rebalance )def Rebalance(self):# En production avec les donnees FRED:# if self.last_spread is not None:# if self.last_spread > 0:# # Yield curve normale -> risk-on# self.SetHoldings(self.spy, 0.80)# self.SetHoldings(self.tlt, 0.20)# else:# # Yield curve inversee -> risk-off# self.SetHoldings(self.spy, 0.30)# self.SetHoldings(self.tlt, 0.70)passprint("FREDData et YieldCurveAlgorithm definis")print("\nYield curve interpretation:")print(" - Spread > 0: Normal curve -> Risk-on (more equities)")print(" - Spread < 0: Inverted curve -> Risk-off (more bonds)")print(" - Inversion predicts recessions 12-18 months ahead")
Backtest QC Cloud 2015-2024 (projet 33177683, bt 70b5e8a1e5a851a7e430bcf4161fbfbe). Net Profit / Equity finale deduits du CAGR verifie (End = $100,000 x (1+CAGR)^10).
Partie 6 : Stratégie Event-Driven: Earnings (20 min)
6.1 Earnings Calendar Strategy
Les earnings announcements (résultats trimestriels) sont des événements majeurs qui créent de la volatilite. Les stratégies event-driven exploitent ces mouvements.
Patterns d’Earnings
Pattern
Description
Stratégie
Pre-earnings drift
Mouvement avant l’annonce
Position avant
Post-earnings drift
Mouvement après surprise
Position après
Volatility crush
IV baisse après l’annonce
Vendre options
Earnings momentum
Winners/losers persistants
Momentum
Données d’Earnings dans QuantConnect
QuantConnect fournit les dates d’earnings via les Fundamentals :
# Acceder aux données d'earningsearnings_date = stock.EarningReports.BasicEPS.ReportDatelast_eps = stock.EarningReports.BasicEPS.Value
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# Exemple combinant plusieurs sources de donnees alternatives# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class MultiSourceAlternativeDataStrategy(QCAlgorithm):""" Strategie combinant plusieurs sources de donnees alternatives: - Fundamentals Morningstar (Value screening) - Momentum technique (Price momentum) - Macro regime (SPY vs SMA) Exemple d'integration multi-source. """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# Universe avec Fundamentalsself.UniverseSettings.Resolution = Resolution.Dailyself.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)# Reference pour regime detectionself.spy =self.AddEquity("SPY", Resolution.Daily).Symbolself.spy_sma =self.SMA("SPY", 200, Resolution.Daily)# Parametresself.num_stocks =15self.momentum = {}self.last_month =-1# Warmupself.SetWarmup(200)self.Log("Multi-Source Alternative Data Strategy initialized")def CoarseSelectionFunction(self, coarse):ifself.Time.month ==self.last_month:return Universe.Unchangedself.last_month =self.Time.month filtered = [x for x in coarse if x.HasFundamentalData and x.Price >10and x.DollarVolume >10000000] sorted_stocks =sorted(filtered, key=lambda x: x.DollarVolume, reverse=True)return [x.Symbol for x in sorted_stocks[:300]]def FineSelectionFunction(self, fine):"""Selection multi-criteres: Value + Quality.""" candidates = []for stock in fine:try: pe = stock.ValuationRatios.PERatio roe = stock.OperationRatios.ROE.Value# Filtres Value + Qualityif pe and5< pe <20and roe and roe >0.12: candidates.append({'symbol': stock.Symbol,'pe': pe,'roe': roe,'score': (1/pe) * roe *100 })except:continue# Trier par score composite sorted_candidates =sorted(candidates, key=lambda x: x['score'], reverse=True)return [x['symbol'] for x in sorted_candidates[:self.num_stocks *2]]def OnSecuritiesChanged(self, changes):"""Initialiser momentum pour nouvelles securities."""for security in changes.AddedSecurities:if security.Symbol !=self.spy:self.momentum[security.Symbol] =self.MOMP(security.Symbol, 20, Resolution.Daily)for security in changes.RemovedSecurities:if security.Symbol inself.momentum:delself.momentum[security.Symbol]def OnData(self, data):ifself.IsWarmingUp:return# Executer une fois par moisifnotself.Time.day ==1:return# Determiner le regime spy_price =self.Securities[self.spy].Price is_bullish = spy_price >self.spy_sma.Current.Value# Selectionner les meilleures actions par momentum momentum_scores = []for symbol, mom inself.momentum.items():if mom.IsReady: momentum_scores.append({'symbol': symbol,'momentum': mom.Current.Value })# Trier par momentum sorted_by_momentum =sorted(momentum_scores, key=lambda x: x['momentum'], reverse=True) top_momentum = sorted_by_momentum[:self.num_stocks]# Allocation selon regimeif is_bullish:# Risk-on: full equity allocation weight =0.90/max(len(top_momentum), 1)else:# Risk-off: reduced allocation weight =0.50/max(len(top_momentum), 1)# Liquider positions non selectionneesfor symbol inlist(self.Portfolio.Keys):if symbol !=self.spy:if symbol notin [x['symbol'] for x in top_momentum]:self.Liquidate(symbol)# Acheter top momentum stocksfor stock in top_momentum:self.SetHoldings(stock['symbol'], weight)self.Log(f"Regime: {'BULLISH'if is_bullish else'BEARISH'}, Positions: {len(top_momentum)}, Weight: {weight:.1%}")print("MultiSourceAlternativeDataStrategy defini")print("\nCriteres combines:")print(" 1. Fundamentals: P/E 5-20, ROE > 12%")print(" 2. Momentum: Top 15 par momentum 20 jours")print(" 3. Regime: Allocation ajustee selon SPY vs SMA200")
Backtest QC Cloud 2015-2024 (projet 33177683, bt f782280c9fb5184328d1e7a182c7469a). Net Profit / Equity finale deduits du CAGR verifie (End = $100,000 x (1+CAGR)^10).
On évalue la contribution des données alternatives à la performance globale de la stratégie par une analyse d’attribution.
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# Strategie Earnings: Pre-Earnings Momentum# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*from datetime import timedeltaclass EarningsMomentumStrategy(QCAlgorithm):""" Strategie basee sur les earnings: - Acheter les actions avec momentum positif avant earnings - Vendre apres l'annonce (ou avant si momentum se retourne) Basee sur la recherche academique: - "Earnings Momentum and Earnings Management" (Chan et al., 2006) """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# Universe dynamiqueself.AddUniverse(self.CoarseSelectionFunction)self.UniverseSettings.Resolution = Resolution.Daily# Tracking des positions earningsself.earnings_positions = {} # symbol -> entry_dateself.momentum = {} # symbol -> MomentumPercent indicator# Parametresself.days_before_earnings =10# Entrer 10 jours avantself.days_after_earnings =2# Sortir 2 jours apresself.num_stocks =30self.last_month =-1self.Log("Earnings Momentum Strategy initialized")def CoarseSelectionFunction(self, coarse):"""Selection mensuelle des candidats."""ifself.Time.month ==self.last_month:return Universe.Unchangedself.last_month =self.Time.month# Filtrer filtered = [x for x in coarse if x.HasFundamentalDataand x.Price >10and x.DollarVolume >5000000]# Trier par volume sorted_stocks =sorted(filtered, key=lambda x: x.DollarVolume, reverse=True)return [x.Symbol for x in sorted_stocks[:200]]def OnSecuritiesChanged(self, changes):"""Initialiser les indicateurs de momentum."""for security in changes.AddedSecurities: symbol = security.Symbolif symbol notinself.momentum:self.momentum[symbol] =self.MOMP(symbol, 20, Resolution.Daily)for security in changes.RemovedSecurities: symbol = security.Symbolif symbol inself.momentum:delself.momentum[symbol]def OnData(self, data):"""Logique de trading basee sur les earnings."""# Verifier les positions existantesself.CheckExistingPositions()# Chercher de nouvelles opportunitesself.ScanForOpportunities(data)def CheckExistingPositions(self):"""Gerer les positions existantes."""for symbol inlist(self.earnings_positions.keys()): entry_date =self.earnings_positions[symbol] days_held = (self.Time - entry_date).days# Verifier si momentum se retourneif symbol inself.momentum andself.momentum[symbol].IsReady: momentum =self.momentum[symbol].Current.Value# Sortir si momentum devient negatif ou apres X joursif momentum <-5or days_held >self.days_before_earnings +self.days_after_earnings:self.Liquidate(symbol)delself.earnings_positions[symbol]self.Log(f"EXIT {symbol.Value}: Days={days_held}, Mom={momentum:.1f}%")def ScanForOpportunities(self, data):"""Scanner pour de nouvelles opportunites pre-earnings."""# Limiter le nombre de positionsiflen(self.earnings_positions) >=self.num_stocks:returnfor symbol inself.ActiveSecurities.Keys:# Ne pas doubler une positionif symbol inself.earnings_positions:continueifnot data.ContainsKey(symbol):continue# Verifier le momentumif symbol notinself.momentum ornotself.momentum[symbol].IsReady:continue momentum =self.momentum[symbol].Current.Value# Critere: momentum positif fortif momentum >5: # +5% sur 20 jours# Calculer la taille de position weight =1.0/self.num_stocksself.SetHoldings(symbol, weight)self.earnings_positions[symbol] =self.Timeself.Log(f"ENTER {symbol.Value}: Momentum={momentum:.1f}%")def OnEndOfAlgorithm(self):self.Log("="*50)self.Log("EARNINGS MOMENTUM STRATEGY SUMMARY")self.Log("="*50)self.Log(f"Final Value: ${self.Portfolio.TotalPortfolioValue:,.2f}") total_return = (self.Portfolio.TotalPortfolioValue -100000) /100000self.Log(f"Total Return: {total_return:.1%}")print("EarningsMomentumStrategy defini")print("\nLogique:")print(" 1. Scanner les actions avec momentum >5%")print(" 2. Entrer en position (equal-weight)")print(" 3. Sortir si momentum devient negatif ou apres earnings")
Résultat non verifiable (verdict #3801 : INTRINSIC / a diagnostiquer). Le backtest QC Cloud de cette stratégie de reference (universe Coarse, scanning de momentum quotidien sur jusqu’a 30 titres) sur 2015-2024 n’a produit AUCUNE journee negociable fiable : deux runs independants donnent tradeableDates = 0 avec des statistiques NON-DÉTERMINISTES (bt 6037134b : Sharpe 0.319 / CAGR 8.585% ; bt d6cb787b : Sharpe 0.298 / CAGR 7.829%). Un backtest déterministe doit donner des résultats identiques – ces statistiques sont donc inutilisables (probable timeout dû a l’intensite du trading quotidien sur 10 ans). La table fabriquee d’origine (“Q1 2023”, Sharpe -1.395, CAGR -32.6%) est supprimee. Aucune metrique de substitution degradee n’est commitee a la place.
Le PEAD est une anomalie de marche bien documentee : les actions qui surprennent positivement continuent de monter, et vice versa.
Implementation : 1. Detecter les earnings surprises (EPS actual vs consensus) 2. Acheter les surprises positives, vendre les negatives 3. Tenir pendant 60-90 jours (drift period)
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# Structure pour PEAD Strategy (Reference)# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class PEADStrategy(QCAlgorithm):""" Post-Earnings Announcement Drift Strategy. Note: Necessite des donnees de consensus (Estimize, Zacks) qui peuvent etre payantes. Cet exemple montre la structure. """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# Universe symbols = [Symbol.Create(t, SecurityType.Equity, Market.USA) for t in ["AAPL", "MSFT", "GOOGL", "AMZN", "META"]]self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))# Tracking des positions PEADself.pead_positions = {} # symbol -> (entry_date, direction)self.holding_period =60# joursself.Log("PEAD Strategy initialized")def OnData(self, data):""" En production: 1. Detecter les nouvelles annonces d'earnings 2. Comparer EPS actual vs consensus 3. Calculer la surprise standardisee (SUE) 4. Trader selon la direction de la surprise """# Gerer les positions existantesfor symbol inlist(self.pead_positions.keys()): entry_date, direction =self.pead_positions[symbol] days_held = (self.Time - entry_date).daysif days_held >=self.holding_period:self.Liquidate(symbol)delself.pead_positions[symbol]self.Log(f"PEAD Exit: {symbol.Value} after {days_held} days")def ProcessEarningsSurprise(self, symbol, actual_eps, expected_eps, std_dev):""" Traiter une surprise d'earnings. SUE = (Actual EPS - Expected EPS) / Std Dev SUE > 1: Positive surprise -> Buy SUE < -1: Negative surprise -> Short """if std_dev <=0:return sue = (actual_eps - expected_eps) / std_devif sue >1: # Positive surpriseself.SetHoldings(symbol, 0.10)self.pead_positions[symbol] = (self.Time, "LONG")self.Log(f"PEAD Long: {symbol.Value}, SUE={sue:.2f}")elif sue <-1: # Negative surpriseself.SetHoldings(symbol, -0.10) # Shortself.pead_positions[symbol] = (self.Time, "SHORT")self.Log(f"PEAD Short: {symbol.Value}, SUE={sue:.2f}")print("PEADStrategy defini (structure)")print("\nStandardized Unexpected Earnings (SUE):")print(" SUE = (Actual - Expected) / StdDev")print(" SUE > 1: Positive surprise -> Buy")print(" SUE < -1: Negative surprise -> Short")
Le PEAD classique achete après un earnings surprise positif. Une version multi-facteurs combine earnings surprise, volume anormal et volatilite implicite.
Objectif : Implementer un modèle PEAD qui filtre les signaux avec des conditions de volume et volatilite.
Règles : - Signal d’entree : earnings surprise > 5% ET volume du jour > 1.5x volume moyen 20j - Filtre de volatilite : ne pas entrer si volatilite 20j > 2x la volatilite 60j - Position sizing : min(surprise_pct / 20, 0.10) (max 10% du portfolio) - Sortie : après 10 jours ou si le prix revient au pre-earnings
Indices : - Indice : self.Securities[symbol].Volume pour le volume du jour - Indice : Utilisez une RollingWindow[TradeBar] pour calculer les volumes et volatilites glissantes
# [REFERENCE QC] Exercice 3 : Strategie PEAD multi-facteurs# TODO etudiant : Implementer un modele PEAD avec filtres volume et volatilite# Indice : Combinez earnings surprise + volume anormal + filtre volatilite# Etape 1 : Detecter les earnings surprises dans OnData# Etape 2 : Calculer le volume ratio (jour / moyenne 20j)# Etape 3 : Calculer le ratio de volatilite (20j / 60j)# Etape 4 : Appliquer les filtres et generer les ordresresult =None# TODO etudiant : remplacer par la strategie PEAD multi-facteursprint("Exercice a completer")
Conclusion et Prochaines Étapes
Recapitulatif
Dans ce notebook, nous avons couvert :
Introduction aux Données Alternatives :
Types: Satellite, social media, sentiment, fundamentals
Avantage competitif vs données de marche standard
Fundamentals Data Morningstar (Fama & French, 1992 - value/factor) :
QC-Py-18 : ML Features Engineering (combiner alt data + price data)
QC-Py-19 : ML Supervised Classification avec features alternatives
References academiques
Ball, R. & Brown, P. (1968). An Empirical Evaluation of Accounting Income Numbers. Journal of Accounting Research. Post-Earnings Announcement Drift (PEAD) - anomalie la plus anciennement documentee.
Bernard, V. L. & Thomas, J. K. (1989). Post-Earnings-Announcement Drift: Delayed Price Response or Risk Premium? Journal of Accounting Research. Mécanisme du drift post-annonce (sous-reaction du marche).
Fama, E. F. & French, K. R. (1992). The Cross-Section of Expected Stock Returns. Journal of Finance. Facteurs de valeur (HML, P/B) - fondamentaux comme facteur de risque.
Chan, W. S. (2003). Stock Price Reaction to News and No-News: Drift and Reversal after Headlines. Journal of Financial Economics. Momentum des news / données alternatives textuelles.
Tetlock, P. C. (2007). Giving Content to Investor Sentiment: The Rôle of Media in the Stock Market. Journal of Finance. Données media alternatives comme predicteur des rendements.