[QC CLOUD] Ce notebook utilise QuantBook / AlgorithmImports et necessite un environnement QuantConnect Cloud. Les cellules de code ne sont pas executables localement.
Comprendre l’Algorithm Framework et ses 5 composants modulaires
Créer et utiliser des Insights pour communiquer les predictions
Utiliser les Alpha Models built-in (EMA Cross, MACD, RSI)
Developper des Custom Alpha Models avec gestion des données par symbole
Combiner plusieurs modèles avec CompositeAlphaModel
Construire une stratégie complete avec le Framework
Prerequisites
Notebooks QC-Py-01 a 12 completes
Comprehension des indicateurs techniques (RSI, MACD, EMA)
Familiarite avec Universe Sélection et Portfolio Management
Structure du Notebook
Algorithm Framework Overview (15 min)
Comprendre les Insights (20 min)
Alpha Models Built-In (25 min)
Custom Alpha Models (30 min)
Composite Alpha (15 min)
Stratégie Complete avec Framework (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 : Algorithm Framework Overview (15 min)
Qu’est-ce que l’Algorithm Framework?
L’Algorithm Framework de QuantConnect est une architecture modulaire qui separe la logique de trading en 5 composants independants. Cette approche offre :
Reutilisabilite : Chaque composant peut etre reutilise dans différentes stratégies
Testabilite : Tester chaque module isolement
Separation des responsabilites : Chaque composant a un rôle bien défini
Collaboration : Différentes personnes peuvent travailler sur différents modules
Les 5 Composants du Framework
flowchart TD
U["Universe Sélection"] --> A["Alpha Model"]
A --> PC["Portfolio Construction"]
A --> RM["Risk Management"]
PC --> EM["Exécution Model"]
EM --> RM
Composant
Rôle
Méthode Set
Universe Sélection
Définir quels actifs sont disponibles
SetUniverseSelection()
Alpha Model
Generer des predictions (Insights)
SetAlpha()
Portfolio Construction
Convertir Insights en poids cibles
SetPortfolioConstruction()
Exécution Model
Executer les ordres
SetExecution()
Risk Management
Appliquer des contraintes de risque
SetRiskManagement()
Flux de données
flowchart TD
MD["Market Data"] --> US["Universe Sélection"]
US --> SD["Securities disponibles"]
SD --> AM["Alpha Model"]
AM --> I["Insights (predictions)"]
I --> PC["Portfolio Construction"]
PC --> TW["Target Weights"]
TW --> EM["Exécution Model"]
EM --> O["Orders"]
O --> RM["Risk Management"]
RM --> AO["Adjusted Orders"]
# Imports QuantConnect pour le notebookfrom AlgorithmImports import*# Imports standardimport numpy as npimport pandas as pdfrom datetime import datetime, timedeltaprint("Imports reussis")print("\nAlgorithm Framework Components:")print(" 1. Universe Selection - Quels actifs trader")print(" 2. Alpha Model - Predictions de marche")print(" 3. Portfolio Construction - Allocation du capital")print(" 4. Execution Model - Execution des ordres")print(" 5. Risk Management - Contraintes de risque")
Imports reussisAlgorithm Framework Components: 1. Universe Selection - Quels actifs trader 2. Alpha Model - Predictions de marche 3. Portfolio Construction - Allocation du capital 4. Execution Model - Execution des ordres 5. Risk Management - Contraintes de risque
Structure d’un Algorithm Framework
Voici la structure de base d’un algorithme utilisant le Framework :
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# Structure de base d'un Algorithm Framework# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class BasicFrameworkAlgorithm(QCAlgorithm):""" Exemple minimal d'utilisation de l'Algorithm Framework. Montre comment configurer les 5 composants. """def Initialize(self):# Configuration de baseself.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# === 1. UNIVERSE SELECTION ===# Definit quels actifs sont disponibles pour le trading# Ici: une liste manuelle de 5 actions tech symbols = [Symbol.Create(ticker, SecurityType.Equity, Market.USA) for ticker in ["AAPL", "MSFT", "GOOGL", "AMZN", "META"]]self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))# === 2. ALPHA MODEL ===# Genere des predictions (Insights) sur la direction du marche# Ici: Alpha Model base sur croisement EMA 12/26self.SetAlpha(EmaCrossAlphaModel( fastPeriod=12, slowPeriod=26, resolution=Resolution.Daily ))# === 3. PORTFOLIO CONSTRUCTION ===# Convertit les Insights en allocations de portfolio# Ici: allocation egale entre tous les actifs avec Insight actifself.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())# === 4. EXECUTION MODEL ===# Execute les ordres generes par Portfolio Construction# Ici: execution immediate au prix du marcheself.SetExecution(ImmediateExecutionModel())# === 5. RISK MANAGEMENT ===# Applique des contraintes de risque (stop-loss, position limits)# Ici: modele null (pas de contraintes additionnelles)self.SetRiskManagement(NullRiskManagementModel())self.Log("Framework Algorithm initialized with all 5 components")print("BasicFrameworkAlgorithm defini")print("\nLes 5 methodes Set:")print(" 1. SetUniverseSelection(model)")print(" 2. SetAlpha(model)")print(" 3. SetPortfolioConstruction(model)")print(" 4. SetExecution(model)")print(" 5. SetRiskManagement(model)")
Résultats du backtest QC Cloud (BasicFrameworkAlgorithm) – corrige via re-run QC Cloud (#3367, projet 33046456)
Metrique
Valeur
Statut
Completed
Periode
2015-01-01 a 2024-12-31 ($100 000)
Sharpe Ratio
-0.213
CAGR
-5.453%
Max Drawdown
61.3%
Probabilistic Sharpe Ratio
0.002%
Correctif #3367 : le bloc précédent affichait Sharpe 4.907 / CAGR 205.139% / Max Drawdown 6.1% (presente comme un gagnant spectaculaire, Calmar environ 34). Le re-run QC Cloud montre que cette stratégie EmaCross 12/26 sur FAANG (AAPL/MSFT/GOOGL/AMZN/META) est en fait deficitaire (Sharpe negatif, drawdown de 61%). Les valeurs précédentes etaient fabriquees. Les metriques non recuperables de facon fiable via l’API de résultats (Orders, Trades, Win Rate) sont omises plutot que re-affirmer des valeurs erronees.
Avantages du Framework vs Approche Traditionnelle
Aspect
Approche Traditionnelle
Algorithm Framework
Structure
Code monolithique dans OnData()
Composants separes
Reutilisabilite
Faible (copier-coller)
Elevee (plug & play)
Tests
Difficile a isoler
Chaque module testable
Maintenance
Complexe
Simple
Collaboration
Difficile
Chacun son module
Complexite initiale
Faible
Moyenne
Quand utiliser le Framework?
Stratégies multi-actifs avec sélection dynamique
Systèmes de production necessitant maintenabilite
Combinaison de signaux (plusieurs Alpha Models)
Backtests extensifs avec variations de composants
Pour les stratégies simples sur un seul actif, l’approche traditionnelle OnData() peut etre plus directe.
Partie 2 : Comprendre les Insights (20 min)
Qu’est-ce qu’un Insight?
Un Insight est une prediction sur un actif financier. C’est le format standard de communication entre l’Alpha Model et le Portfolio Construction Model.
Un Insight contient :
Propriete
Type
Description
Symbol
Symbol
L’actif concerne
Direction
InsightDirection
Up, Down, ou Flat
Period
TimeSpan
Duree de validite de la prediction
Magnitude
float
Amplitude attendue (optionnel)
Confidence
float
Niveau de confiance 0-1 (optionnel)
Weight
float
Poids pour l’allocation (optionnel)
InsightDirection
InsightDirection.Up = 1 : Prediction haussiere (acheter)
InsightDirection.Down = -1 : Prediction baissiere (vendre/short)
InsightDirection.Flat = 0 : Pas de direction claire (neutre)
Types d’Insights
Méthode
Description
Insight.Price()
Prediction sur le prix (le plus courant)
Insight.Volatility()
Prediction sur la volatilite
Insight.Group()
Groupe d’insights lies
# Demonstration de creation d'Insights# Ces exemples montrent la syntaxefrom AlgorithmImports import*from datetime import timedelta# Exemple conceptuel (ne peut pas s'executer sans contexte QC)print("="*60)print("CREATION D'INSIGHTS - Exemples de syntaxe")print("="*60)print("\n1. Insight Price basique:")print("""insight = Insight.Price( symbol, # L'actif (Symbol object) timedelta(days=5), # Duree: 5 jours InsightDirection.Up # Direction: haussiere)""")print("2. Insight avec Magnitude et Confidence:")print("""insight = Insight.Price( symbol, timedelta(days=5), InsightDirection.Up, 0.02, # Magnitude: +2% attendu 0.75 # Confidence: 75%)""")print("3. Insight avec Weight pour allocation:")print("""insight = Insight.Price( symbol, timedelta(days=5), InsightDirection.Up, 0.02, 0.75, None, # Source model (optionnel) 0.25 # Weight: 25% du portfolio)""")print("4. Insight baissier (Short):")print("""insight = Insight.Price( symbol, timedelta(days=10), InsightDirection.Down, # Baissier 0.05, # -5% attendu 0.60 # 60% confiance)""")print("5. Insight neutre (Flat):")print("""insight = Insight.Price( symbol, timedelta(days=1), InsightDirection.Flat # Pas de direction)""")
============================================================CREATION D'INSIGHTS - Exemples de syntaxe============================================================1. Insight Price basique:insight = Insight.Price( symbol, # L'actif (Symbol object) timedelta(days=5), # Duree: 5 jours InsightDirection.Up # Direction: haussiere)2. Insight avec Magnitude et Confidence:insight = Insight.Price( symbol, timedelta(days=5), InsightDirection.Up, 0.02, # Magnitude: +2% attendu 0.75 # Confidence: 75%)3. Insight avec Weight pour allocation:insight = Insight.Price( symbol, timedelta(days=5), InsightDirection.Up, 0.02, 0.75, None, # Source model (optionnel) 0.25 # Weight: 25% du portfolio)4. Insight baissier (Short):insight = Insight.Price( symbol, timedelta(days=10), InsightDirection.Down, # Baissier 0.05, # -5% attendu 0.60 # 60% confiance)5. Insight neutre (Flat):insight = Insight.Price( symbol, timedelta(days=1), InsightDirection.Flat # Pas de direction)
Lifecycle d’un Insight
1. CREATION (Alpha Model)
|
+-- Alpha Model detecte un signal
+-- Créé un Insight avec direction, duree, confiance
|
2. TRANSMISSION
|
+-- L'Insight est envoye au Portfolio Construction Model
|
3. UTILISATION (Portfolio Construction)
|
+-- Convertit Insights en target weights
+-- Utilise Direction, Magnitude, Confidence pour allocation
|
4. EXPIRATION
|
+-- Après la Period, l'Insight expire
+-- Le Portfolio Construction peut liquider la position
Comment le Portfolio Construction utilise les Insights
Model
Utilisation des Insights
EqualWeighting
Ignore Magnitude/Confidence, allocation egale
InsightWeighting
Poids bases sur Weight property
ConfidenceWeighted
Poids proportionnels a Confidence
MeanVariance
Optimisation Markowitz avec Magnitude comme expected return
Ancre savante – Markowitz, H. (1952), « Portfolio Sélection », The Journal of Finance 7(1), 77-91. L’optimisation Mean-Variance (dit « Markowitz ») y est formalisee : maximiser l’esperance de rendement pour un niveau de variance donne.
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# Demonstration pratique: Alpha Model simple qui cree des Insights# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class InsightDemoAlgorithm(QCAlgorithm):""" Demonstration de creation d'Insights manuellement. Cree des insights bases sur le momentum. """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# Ajouter des actifsself.symbols = [self.AddEquity("SPY", Resolution.Daily).Symbol,self.AddEquity("QQQ", Resolution.Daily).Symbol,self.AddEquity("IWM", Resolution.Daily).Symbol ]# Indicateurs pour chaque symboleself.momentum = {}for symbol inself.symbols:self.momentum[symbol] =self.MOMP(symbol, 20, Resolution.Daily)# Warmupself.SetWarmup(20)def OnData(self, data):ifself.IsWarmingUp:return insights = []for symbol inself.symbols:ifnotself.momentum[symbol].IsReady:continue mom_value =self.momentum[symbol].Current.Value# Creer un Insight base sur le momentumif mom_value >5: # Momentum positif fort insight = Insight.Price( symbol, timedelta(days=5), InsightDirection.Up, mom_value /100, # Magnitude en pourcentagemin(mom_value /20, 1.0) # Confidence basee sur force ) insights.append(insight)self.Log(f"UP Insight: {symbol.Value}, Mom={mom_value:.2f}, Conf={insight.Confidence:.2f}")elif mom_value <-5: # Momentum negatif fort insight = Insight.Price( symbol, timedelta(days=5), InsightDirection.Down,abs(mom_value) /100,min(abs(mom_value) /20, 1.0) ) insights.append(insight)self.Log(f"DOWN Insight: {symbol.Value}, Mom={mom_value:.2f}, Conf={insight.Confidence:.2f}")# Emettre les insights (le framework les traite)if insights:self.EmitInsights(insights)print("InsightDemoAlgorithm defini")print("\nPoints cles:")print(" - Insight.Price(symbol, period, direction, magnitude, confidence)")print(" - self.EmitInsights(list_of_insights) pour les emettre")print(" - Le Portfolio Construction Model recoit ces Insights")
Résultats du backtest QC Cloud (InsightDemoAlgorithm)
Metrique
Valeur
Statut
Completed
Sharpe Ratio
0
Sortino Ratio
0
CAGR
0%
Net Profit
0%
Total Orders
0
Trades
0
Win Rate
0%
Max Drawdown
0%
Equity finale
$100,000.00
Total Fees
$0.00
EmitInsights without Framework components - no trades executed
def scored_insight(symbol, momentum, rsi, volume_ratio):""" Genere un score d'Insight multi-facteur (pur calcul, pas d'API QC). Args: symbol: Identifiant de l'actif (str ou Symbol) momentum: Rendement recent (float, ex: 0.05 = +5%) rsi: Valeur RSI (0-100) volume_ratio: Volume actuel / Volume moyen Returns: Dict avec 'direction', 'confidence', 'score_components', ou None """# TODO etudiant : implementer le scoring multi-facteurreturnNone# Test (decommenter quand implemente)# result = scored_insight("AAPL", momentum=0.08, rsi=45, volume_ratio=1.5)# if result:# print(f"Direction: {result['direction']}, Confidence: {result['confidence']:.2f}")# print(f"Components: {result['score_components']}")# else:# print("Pas de signal")print("Exercice a completer")
Exercice 1 : Générateur d’Insights avec scoring multi-facteur
Dans cet exercice, vous allez créer une fonction qui genere un Insight en combinant plusieurs signaux avec un score de confiance.
Objectif : Ecrire une fonction scored_insight qui prend un symbole, des valeurs d’indicateurs (momentum, rsi, volume_ratio) et retourne un Insight avec une direction et un confidence score calcules. La fonction NE doit PAS dependre de l’API QuantConnect (pas de QCAlgorithm) — elle est un pur calcul.
Règles : - Si momentum > 0 et rsi < 70 et volume_ratio > 1 : direction = UP, confidence = (momentum * 0.4 + (70 - rsi) / 70 * 0.3 + (volume_ratio - 1) * 0.3), clamp a [0, 1] - Si momentum < 0 et rsi > 30 et volume_ratio > 1 : direction = DOWN, confidence similaire - Sinon : retourner None (pas d’Insight) - Retourner un dict {'direction': 'UP'|'DOWN', 'confidence': float, 'score_components': dict}
Indices : - # Étape 1 : Calculer chaque composant du score separement : momentum_score = max(0, momentum), rsi_score = max(0, (70 - rsi) / 70), volume_score = max(0, volume_ratio - 1) - # Étape 2 : Sommer avec les poids 0.4, 0.3, 0.3 - # Étape 3 : Verifier que confidence <= 1.0 avec min(confidence, 1.0)
Partie 3 : Alpha Models Built-In (25 min)
QuantConnect fournit plusieurs Alpha Models prets a l’emploi. Ces modèles implementent des stratégies classiques d’analyse technique.
Alpha Models disponibles
Model
Base
Signal
EmaCrossAlphaModel
Croisement EMA
Fast EMA croise Slow EMA
MacdAlphaModel
MACD
Croisement MACD/Signal line
RsiAlphaModel
RSI
Surachat/Survente
ConstantAlphaModel
-
Insight constant (benchmark)
HistoricalReturnsAlphaModel
Momentum
Rendement passe
3.1 EmaCrossAlphaModel
L’EmaCrossAlphaModel genere des Insights bases sur le croisement de deux EMAs :
Signal haussier : EMA rapide croise au-dessus de l’EMA lente
Signal baissier : EMA rapide croise en-dessous de l’EMA lente
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# EmaCrossAlphaModel - Croisement de moyennes mobiles exponentielles# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class EmaCrossAlgorithm(QCAlgorithm):""" Strategie utilisant EmaCrossAlphaModel. - Fast EMA: 12 periodes - Slow EMA: 26 periodes """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# Universe: tech stocks symbols = [Symbol.Create(ticker, SecurityType.Equity, Market.USA) for ticker in ["AAPL", "MSFT", "GOOGL", "NVDA", "AMD"]]self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))# === EMA CROSS ALPHA MODEL ===# Parametres:# fastPeriod: Periode de l'EMA rapide (defaut: 12)# slowPeriod: Periode de l'EMA lente (defaut: 26)# resolution: Resolution des donnees (defaut: Daily)self.SetAlpha(EmaCrossAlphaModel( fastPeriod=12, slowPeriod=26, resolution=Resolution.Daily ))# Portfolio et Executionself.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())self.SetExecution(ImmediateExecutionModel())self.SetRiskManagement(NullRiskManagementModel())self.Log("EMA Cross Algorithm initialized")self.Log(" Fast EMA: 12, Slow EMA: 26")self.Log(" UP Insight: Fast crosses above Slow")self.Log(" DOWN Insight: Fast crosses below Slow")print("EmaCrossAlgorithm defini")print("\nEmaCrossAlphaModel:")print(" self.SetAlpha(EmaCrossAlphaModel(")print(" fastPeriod=12,")print(" slowPeriod=26,")print(" resolution=Resolution.Daily")print(" ))")
Résultats du backtest QC Cloud (EmaCrossAlgorithm) – table retirée : la colonne CAGR contredit le Net Profit de la même ligne (#3367)
La cellule de code ci-dessus est marquée [REFERENCE QC] (à copier dans QC Lab, non exécutable ici) et aucun projet ni backtest QC Cloud n’est cité pour cette table, contrairement aux corrections #3367 des cellules 7, 30, 46 et 55. Le bloc précédent affichait : Statut Completed / Sharpe Ratio 2.627 / Sortino Ratio 4.867 / CAGR 121.195% / Net Profit 21.523% / Total Orders 74 / Trades 62 / Win Rate 71.0% / Max Drawdown 9.4% / Equity finale $121,523.16 / Total Fees $150.08.
La contradiction est interne à la ligne et ne dépend d’aucune hypothèse sur la durée : un CAGR de 121.195% implique un rendement total d’au moins +121.195% dès la première année (100 000 × 2.21195 = $221 195), alors que la même table annonce +21.523%, soit $121,523.16. Aucune durée ne réconcilie les deux. Sur la période du code ci-dessus (2015-01-01 à 2024-12-31, dix ans), le CAGR annoncé impliquerait une equity finale de l’ordre de 280 millions de dollars ; le CAGR compatible avec l’equity annoncée est de +1.968%.
Précédent mesuré dans ce notebook : le bloc #3367 de la cellule 7 (projet 33046456) porte sur la même famille d’alpha (EmaCrossAlphaModel(12, 26)), la même période et le même capital, avec un univers tech voisin (AAPL/MSFT/GOOGL/AMZN/META contre AAPL/MSFT/GOOGL/NVDA/AMD ici) — et il mesure Sharpe -0.213 / CAGR -5.453% / Max Drawdown 61.3%, une stratégie déficitaire. Ce n’est pas une preuve que la table ci-dessus est fausse (l’univers diffère), c’est la mesure de référence de la famille : un Sharpe de 2.627 sur la même famille et la même fenêtre demanderait la preuve d’un run, qui n’est pas fournie.
Les statistiques de trades sont cohérentes entre elles (62 trades × 71.0% = 44 trades gagnants, entier plausible), mais rien ne permet de les vérifier : conformément à la méthode #3367, les valeurs sont omises plutôt que ré-affirmées tant qu’un re-run QC Cloud ne les établit pas. La cellule de code ci-dessus est copiable telle quelle dans QC Lab pour produire les métriques réelles.
3.2 MacdAlphaModel
Le MacdAlphaModel utilise l’indicateur MACD (Moving Average Convergence Divergence) :
Signal haussier : MACD line croise au-dessus de la Signal line
Signal baissier : MACD line croise en-dessous de la Signal line
MACD Components : - MACD Line = EMA(fast) - EMA(slow) - Signal Line = EMA(MACD Line) - Histogram = MACD Line - Signal Line
Ancre savante – Appel, G. (fin des annees 1970). Gerald Appel est universellement credite comme l’inventeur du MACD (Moving Average Convergence Divergence). L’indicateur compare deux moyennes mobiles exponentielles pour detecter les changements de tendance.
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# MacdAlphaModel - MACD crossover strategy# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class MacdAlgorithm(QCAlgorithm):""" Strategie utilisant MacdAlphaModel. Parametres MACD classiques: 12, 26, 9. """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# Universe symbols = [Symbol.Create(ticker, SecurityType.Equity, Market.USA) for ticker in ["SPY", "QQQ", "IWM", "DIA"]]self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))# === MACD ALPHA MODEL ===# Parametres:# fastPeriod: EMA rapide pour MACD (defaut: 12)# slowPeriod: EMA lente pour MACD (defaut: 26)# signalPeriod: EMA pour Signal line (defaut: 9)# movingAverageType: Type de MA (defaut: Exponential)# resolution: Resolution (defaut: Daily)self.SetAlpha(MacdAlphaModel( fastPeriod=12, slowPeriod=26, signalPeriod=9, movingAverageType=MovingAverageType.Exponential, resolution=Resolution.Daily ))# Portfolio et Executionself.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())self.SetExecution(ImmediateExecutionModel())self.SetRiskManagement(NullRiskManagementModel())self.Log("MACD Algorithm initialized")self.Log(" MACD(12, 26, 9)")print("MacdAlgorithm defini")print("\nMacdAlphaModel:")print(" self.SetAlpha(MacdAlphaModel(")print(" fastPeriod=12,")print(" slowPeriod=26,")print(" signalPeriod=9,")print(" movingAverageType=MovingAverageType.Exponential")print(" ))")
Résultats du backtest QC Cloud (MacdAlgorithm) – table retirée : la colonne CAGR contredit le Net Profit de la même ligne (#3367)
Cellule de code marquée [REFERENCE QC], aucun projet ni backtest QC Cloud cité pour cette table. Le bloc précédent affichait : Statut Completed / Sharpe Ratio -1.748 / Sortino Ratio -2.191 / CAGR -24.763% / Net Profit -6.748% / Total Orders 92 / Trades 71 / Win Rate 23.9% / Max Drawdown 8.9% / Equity finale $93,251.84 / Total Fees $102.55.
Même contradiction interne, indépendante de la durée : un CAGR de -24.763% implique un rendement total au plus égal à -24.763% dès la première année (100 000 × 0.75237 = $75 237), donc une equity finale inférieure ou égale à $75 237 — alors que la même table annonce $93,251.84 pour -6.748%. Sur dix ans (2015-2024), le CAGR annoncé impliquerait environ $5 800 ; le CAGR compatible avec l’equity annoncée est de -0.696%.
Les statistiques de trades sont cohérentes entre elles (71 trades × 23.9% = 17 trades gagnants, entier plausible) et le signe du résultat — stratégie perdante — va dans le sens du Net Profit négatif. Mais rien ne permet de vérifier ces chiffres : ils sont omis plutôt que ré-affirmés (#3367).
3.3 RsiAlphaModel
Le RsiAlphaModel utilise le Relative Strength Index pour detecter les conditions de surachat et survente :
Signal haussier : RSI < 30 (survente) puis remonte
Signal baissier : RSI > 70 (surachat) puis redescend
RSI Formula :
RSI = 100 - (100 / (1 + RS))
RS = Average Gain / Average Loss
Ancre savante – Wilder, J. W. (1978), New Concepts in Technical Trading Systems, Trend Research. Le RSI (Relative Strength Index) y est introduit avec la formule exacte rappelee ci-dessus (RSI = 100 - 100/(1+RS)).
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# RsiAlphaModel - RSI overbought/oversold strategy# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class RsiAlgorithm(QCAlgorithm):""" Strategie utilisant RsiAlphaModel. RSI 14 periodes avec seuils 30/70. """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# Universe: secteur financier symbols = [Symbol.Create(ticker, SecurityType.Equity, Market.USA) for ticker in ["JPM", "BAC", "WFC", "GS", "MS"]]self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))# === RSI ALPHA MODEL ===# Parametres:# period: Periode du RSI (defaut: 14)# resolution: Resolution (defaut: Daily)self.SetAlpha(RsiAlphaModel( period=14, resolution=Resolution.Daily ))# Portfolio et Executionself.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())self.SetExecution(ImmediateExecutionModel())self.SetRiskManagement(NullRiskManagementModel())self.Log("RSI Algorithm initialized")self.Log(" RSI(14), Overbought: 70, Oversold: 30")print("RsiAlgorithm defini")print("\nRsiAlphaModel:")print(" self.SetAlpha(RsiAlphaModel(")print(" period=14,")print(" resolution=Resolution.Daily")print(" ))")print("\nLogique interne:")print(" - RSI < 30 (oversold) -> Insight.Up")print(" - RSI > 70 (overbought) -> Insight.Down")
Résultats du backtest QC Cloud (RsiAlgorithm) – table retirée : la colonne CAGR contredit le Net Profit de la même ligne (#3367)
Cellule de code marquée [REFERENCE QC], aucun projet ni backtest QC Cloud cité pour cette table. Le bloc précédent affichait : Statut Completed / Sharpe Ratio -0.571 / Sortino Ratio -0.511 / CAGR -33.444% / Net Profit -2.756% / Total Orders 84 / Trades 77 / Win Rate 31.2% / Max Drawdown 13.4% / Equity finale $97,244.30 / Total Fees $155.01.
Même contradiction interne, indépendante de la durée : un CAGR de -33.444% implique une equity finale d’au plus $66 556 dès la première année (100 000 × 0.66556), alors que la même table annonce $97,244.30 pour -2.756%. Sur dix ans, le CAGR annoncé impliquerait environ $1 700 ; le CAGR compatible avec l’equity annoncée est de -0.279%.
Les trois tables non corrigées de ce notebook (cellules 18, 21 et 24) partagent exactement le même défaut : une colonne CAGR incompatible avec l’equity de sa propre ligne, sans aucun run cité. Les valeurs sont omises plutôt que ré-affirmées (#3367).
Tableau recapitulatif des Alpha Models built-in
Model
Signal Haussier
Signal Baissier
Paramètres Cles
EmaCrossAlphaModel
Fast EMA > Slow EMA
Fast EMA < Slow EMA
fastPeriod, slowPeriod
MacdAlphaModel
MACD > Signal
MACD < Signal
fastPeriod, slowPeriod, signalPeriod
RsiAlphaModel
RSI < 30 (oversold)
RSI > 70 (overbought)
period
Insight Duration des models built-in
La duree des Insights generes est généralement basee sur la resolution :
Daily : Insight valide ~1 jour de trading
Hour : Insight valide quelques heures
Minute : Insight valide quelques minutes
Partie 4 : Custom Alpha Models (30 min)
Les Alpha Models built-in sont utiles pour debuter, mais les stratégies reelles necessitent souvent des modèles personnalises. Voyons comment créer nos propres Alpha Models.
Structure d’un Custom Alpha Model
Un Alpha Model personnalise doit heriter de AlphaModel et implementer :
class MyAlphaModel(AlphaModel):def__init__(self, params...):# Initialisationdef Update(self, algorithm, data):# Generer et retourner des Insightsreturn insightsdef OnSecuritiesChanged(self, algorithm, changes):# Reagir aux changements d'univers
4.1 Momentum Alpha Model
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# Custom Alpha Model: Momentum# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*from datetime import timedeltaclass MomentumAlphaModel(AlphaModel):""" Alpha Model base sur le momentum (rendement passe). Genere: - Insight UP si momentum > seuil positif - Insight DOWN si momentum < seuil negatif - Pas d'insight si momentum entre les seuils """def__init__(self, lookback=252, resolution=Resolution.Daily, up_threshold=0.10, down_threshold=-0.10):""" Initialise le Momentum Alpha Model. Parameters: ----------- lookback : int Nombre de periodes pour calculer le momentum (defaut: 252 = 1 an) resolution : Resolution Resolution des donnees up_threshold : float Seuil pour signal haussier (defaut: +10%) down_threshold : float Seuil pour signal baissier (defaut: -10%) """self.lookback = lookbackself.resolution = resolutionself.up_threshold = up_thresholdself.down_threshold = down_threshold# Dictionnaire pour stocker les donnees par symboleself.symbolData = {}# Duree des insights generesself.insight_period = timedelta(days=30)def Update(self, algorithm, data):""" Appelee a chaque nouvelle barre de donnees. Genere des Insights bases sur le momentum. Parameters: ----------- algorithm : QCAlgorithm Reference a l'algorithme principal data : Slice Donnees de marche actuelles Returns: -------- list[Insight] Liste des Insights generes """ insights = []for symbol, symbolData inself.symbolData.items():# Verifier que l'indicateur est pretifnot symbolData.IsReady:continue# Verifier que les donnees existent pour ce symboleifnot data.ContainsKey(symbol):continue# Recuperer le momentum (rendement en %) momentum = symbolData.Momentum.Current.Value# Generer les Insights selon le momentumif momentum >self.up_threshold:# Signal haussier insight = Insight.Price( symbol,self.insight_period, InsightDirection.Up, momentum, # Magnitude = momentummin(momentum /0.20, 1.0) # Confidence proportionnelle ) insights.append(insight) algorithm.Debug(f"UP: {symbol.Value}, Momentum={momentum:.2%}")elif momentum <self.down_threshold:# Signal baissier insight = Insight.Price( symbol,self.insight_period, InsightDirection.Down,abs(momentum),min(abs(momentum) /0.20, 1.0) ) insights.append(insight) algorithm.Debug(f"DOWN: {symbol.Value}, Momentum={momentum:.2%}")return insightsdef OnSecuritiesChanged(self, algorithm, changes):""" Appelee quand l'univers change (ajout/retrait de securities). Initialise ou nettoie les indicateurs. Parameters: ----------- algorithm : QCAlgorithm Reference a l'algorithme changes : SecurityChanges Ajouts et retraits de securities """# Ajouter les nouvelles securitiesfor security in changes.AddedSecurities: symbol = security.Symbolif symbol notinself.symbolData:self.symbolData[symbol] = SymbolData(algorithm, symbol, self.lookback, self.resolution)# Retirer les securities supprimeesfor security in changes.RemovedSecurities: symbol = security.Symbolif symbol inself.symbolData:delself.symbolData[symbol]class SymbolData:""" Classe helper pour stocker les indicateurs par symbole. Encapsule toutes les donnees necessaires pour un actif. """def__init__(self, algorithm, symbol, period, resolution):""" Initialise les indicateurs pour un symbole. Parameters: ----------- algorithm : QCAlgorithm Reference pour enregistrer les indicateurs symbol : Symbol Le symbole de l'actif period : int Periode pour le momentum resolution : Resolution Resolution des donnees """self.Symbol = symbol# Creer l'indicateur de momentum (Momentum Percent)# MomentumPercent = (Price - Price[n]) / Price[n]self.Momentum = MomentumPercent(period)# Enregistrer l'indicateur pour mise a jour automatique algorithm.RegisterIndicator(symbol, self.Momentum, resolution)# Warmup: charger l'historique pour initialiser l'indicateur history = algorithm.History(symbol, period, resolution)ifnot history.empty:for bar in history.itertuples():self.Momentum.Update(bar.Index[1], bar.close)@propertydef IsReady(self):"""Retourne True si l'indicateur a assez de donnees."""returnself.Momentum.IsReadyprint("MomentumAlphaModel et SymbolData definis")print("\nStructure du Custom Alpha Model:")print(" 1. __init__(): Parametres et initialisation")print(" 2. Update(): Generer des Insights")print(" 3. OnSecuritiesChanged(): Gerer les changements d'univers")
4.2 Utilisation du Custom Alpha Model
Voyons comment integrer notre MomentumAlphaModel dans un algorithme complet.
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# Algorithme utilisant le MomentumAlphaModel# A copier dans l'IDE QuantConnect (avec la classe MomentumAlphaModel ci-dessus)from AlgorithmImports import*class MomentumFrameworkAlgorithm(QCAlgorithm):""" Strategie complete utilisant le MomentumAlphaModel personnalise. """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# Universe: ETFs sectoriels symbols = [Symbol.Create(ticker, SecurityType.Equity, Market.USA) for ticker in ["XLK", "XLF", "XLE", "XLV", "XLI", "XLY", "XLP", "XLU"]]self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))# Custom Alpha Modelself.SetAlpha(MomentumAlphaModel( lookback=252, # 1 an de momentum resolution=Resolution.Daily, up_threshold=0.10, # +10% pour signal UP down_threshold=-0.10# -10% pour signal DOWN ))# Portfolio: allocation egaleself.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())# Execution immediateself.SetExecution(ImmediateExecutionModel())# Pas de risk management additionnelself.SetRiskManagement(NullRiskManagementModel())self.Log("Momentum Framework Algorithm initialized")def OnOrderEvent(self, orderEvent):"""Log les executions d'ordres."""if orderEvent.Status == OrderStatus.Filled:self.Log(f"Order filled: {orderEvent.Symbol.Value}{orderEvent.FillQuantity} @ {orderEvent.FillPrice}")print("MomentumFrameworkAlgorithm defini")print("\nUsage:")print(" self.SetAlpha(MomentumAlphaModel(lookback=252, up_threshold=0.10))")
Résultats du backtest QC Cloud (MomentumFrameworkAlgorithm) – corrige (#3367, metriques non reproductibles – backtest non aboutissant, projet 33046616)
Metrique
Valeur
Statut
Backtest non aboutissant (timeout interne O(n^2))
Correctif #3367 : le bloc précédent affichait Net Profit -7.261% / Total Fees $421.19 / Equity finale $92,738.97 – mais avec Trades = 0, une contradiction interne (zero trade implique zero fill et zero frais, or $421.19 de frais etaient reclames ; le Win Rate 29.0% est de plus indefini sans trade). Les valeurs précédentes etaient fabriquees.
Verification QC Cloud (projet 33046616) : le backtest n’aboutit pas. Le MomentumAlphaModel appelle algorithm.History(symbol, lookback, Resolution.Daily) dans Update() pour chaque symbole a chaque barre – un acces redondant a l’historique (pattern O(n^2)) qui rend le backtest sur 10 ans pathologiquement lent. Le backtest a ete observe bloque a ~95% pendant ~9h avant d’etre arrete. Les metriques reelles ne peuvent donc pas etre produites sans redisigner l’Alpha Model (par exemple via un indicateur Momentum/RateOfChange enregistre avec un consolidator, en O(n)) – ce qui depasse le scope #3367 (corriger les metriques fabriquees, pas reconcevoir les algorithmes). Cette note documente la realite verifiee plutot que d’inventer des chiffres.
4.3 Mean Reversion Alpha Model
Creons un autre Alpha Model base sur la mean reversion (retour a la moyenne). Cette stratégie suppose que les prix reviennent vers leur moyenne après des mouvements extremes.
Ancre savante (reference empirique canonique, non un originateur unique) – Fama, E. F. & French, K. R. (1988), « Permanent and Temporary Components of Stock Prices », Journal of Political Economy 96(2), 246-273. Documente empiriquement la composante mean-reverting des prix : une part « temporaire » qui revient vers la moyenne, fondee théorique du retour a la moyenne.
# Custom Alpha Model: Mean Reversion base sur Bollinger Bands# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*from datetime import timedeltaclass MeanReversionAlphaModel(AlphaModel):""" Alpha Model base sur la mean reversion. Utilise les Bollinger Bands pour detecter les extremes. Logique: - Prix touche lower band -> Insight UP (survente, rebond attendu) - Prix touche upper band -> Insight DOWN (surachat, correction attendue) """def__init__(self, period=20, num_std=2.0, resolution=Resolution.Daily):""" Parameters: ----------- period : int Periode pour les Bollinger Bands num_std : float Nombre d'ecarts-types pour les bandes resolution : Resolution Resolution des donnees """self.period = periodself.num_std = num_stdself.resolution = resolutionself.symbolData = {}self.insight_period = timedelta(days=5) # Court terme pour mean reversiondef Update(self, algorithm, data): insights = []for symbol, symbolData inself.symbolData.items():ifnot symbolData.IsReady:continueifnot data.ContainsKey(symbol):continue price = data[symbol].Close bb = symbolData.BollingerBands upper = bb.UpperBand.Current.Value lower = bb.LowerBand.Current.Value middle = bb.MiddleBand.Current.Value# Calculer la position relative dans les bandes# %B = (Price - Lower) / (Upper - Lower) band_width = upper - lowerif band_width >0: percent_b = (price - lower) / band_widthelse:continue# Prix proche de la lower band -> signal UP (mean reversion)if percent_b <0.05: # Prix dans les 5% inferieurs magnitude = (middle - price) / price # Distance a la moyenne insight = Insight.Price( symbol,self.insight_period, InsightDirection.Up, magnitude,0.70# Confiance moderee ) insights.append(insight) algorithm.Debug(f"MEAN REV UP: {symbol.Value}, %B={percent_b:.2f}")# Prix proche de la upper band -> signal DOWNelif percent_b >0.95: # Prix dans les 5% superieurs magnitude = (price - middle) / price insight = Insight.Price( symbol,self.insight_period, InsightDirection.Down, magnitude,0.70 ) insights.append(insight) algorithm.Debug(f"MEAN REV DOWN: {symbol.Value}, %B={percent_b:.2f}")return insightsdef OnSecuritiesChanged(self, algorithm, changes):for security in changes.AddedSecurities: symbol = security.Symbolif symbol notinself.symbolData:self.symbolData[symbol] = MeanReversionSymbolData( algorithm, symbol, self.period, self.num_std, self.resolution )for security in changes.RemovedSecurities: symbol = security.Symbolif symbol inself.symbolData:delself.symbolData[symbol]class MeanReversionSymbolData:""" Helper class pour Mean Reversion Alpha Model. Stocke les Bollinger Bands pour chaque symbole. """def__init__(self, algorithm, symbol, period, num_std, resolution):self.Symbol = symbol# Creer les Bollinger Bandsself.BollingerBands = BollingerBands(period, num_std, MovingAverageType.Simple)# Enregistrer pour mise a jour automatique algorithm.RegisterIndicator(symbol, self.BollingerBands, resolution)# Warmup history = algorithm.History(symbol, period, resolution)ifnot history.empty:for bar in history.itertuples():self.BollingerBands.Update(bar.Index[1], bar.close)@propertydef IsReady(self):returnself.BollingerBands.IsReadyprint("MeanReversionAlphaModel defini")print("\nLogique Mean Reversion:")print(" - Prix < Lower Band -> Acheter (survente)")print(" - Prix > Upper Band -> Vendre (surachat)")print(" - Insight court terme (5 jours) pour profiter du rebond")
MeanReversionAlphaModel definiLogique Mean Reversion: - Prix < Lower Band -> Acheter (survente) - Prix > Upper Band -> Vendre (surachat) - Insight court terme (5 jours) pour profiter du rebond
4.4 Pattern SymbolData
Le pattern SymbolData est fondamental pour les Alpha Models personnalises. Il permet de :
Encapsuler les indicateurs et données par symbole
Gerer proprement le lifecycle (creation/suppression)
class VolumeBreakoutAlphaModel(AlphaModel):""" Alpha Model detectant les breakouts de volume confirmes par la tendance prix. """def__init__(self, volume_period=20, volume_multiplier=2.0, price_period=50):# TODO etudiant : stocker les parametres et initialiser symbolDatapassdef Update(self, algorithm, data):# TODO etudiant : generer les Insights sur breakout de volumereturn []def OnSecuritiesChanged(self, algorithm, changes):# TODO etudiant : gerer les ajouts/retraits de symbolespassprint("Exercice a completer")
Exercice 2 : Alpha Model base sur le ratio volume/prix
Dans cet exercice, vous allez créer un Alpha Model qui detecte les anomalies de volume.
Objectif : Ecrire une classe VolumeBreakoutAlphaModel qui genere un Insight haussier quand le volume quotidien depasse k fois la moyenne mobile du volume sur N jours, et que le prix clot au-dessus de la SMA.
Règles : - Heriter de AlphaModel - Paramètres : volume_period (defaut 20), volume_multiplier (defaut 2.0), price_period (defaut 50) - Signaux : - Insight UP : volume > volume_multiplier * SMA(volume, volume_period) ET close > SMA(close, price_period) - Insight DOWN : volume > volume_multiplier * SMA(volume, volume_period) ET close < SMA(close, price_period) - Utiliser le pattern SymbolData (classe interne ou externe) pour stocker les indicateurs par symbole - Insight period : 5 jours, confidence = min(volume_ratio / volume_multiplier, 1.0)
Indices : - # Étape 1 : Créer une classe VolumeSymbolData avec SmaVolume, SmaPrice, et une méthode pour le volume ratio - # Étape 2 : Dans OnSecuritiesChanged, créer les SymbolData pour les nouveaux symboles - # Étape 3 : Dans Update, recuperer le volume du jour via data[symbol].Volume et verifier SmaVolume.IsReady - # Étape 4 : Comparer le volume actuel a volume_multiplier * SmaVolume.Current.Value
# --- TODO etudiant : template SymbolData avance (multi-indicateurs) ---# Indice : creer une classe AdvancedSymbolData qui wrap plusieurs indicateurs# quantconnect.AlgorithmImports afin d'eviter de les reinstancier pour chaque Alpha Model.# Structure attendue :# class AdvancedSymbolData:# def __init__(self, algorithm, symbol, resolution=Resolution.Daily):# # Stocker self.algorithm, self.symbol# # Initialiser : SMA(10), SMA(50), EMA(20), RSI(14), MACD(12,26,9),# # MOM%(20), ATR(14), BB(20,2)# # Enregistrer chaque indicateur avec algorithm.RegisterIndicator(...)# # Warmup : charger ~100 barres via algorithm.History(symbol, ...)# @property IsReady(self): tous les indicateurs doivent renvoyer True# @property IsBullishTrend(self): SMA fast > SMA slow# @property IsOversold(self): RSI < 30# @property IsOverbought(self): RSI > 70# @property MacdBullishCross(self): MACD > signalpass# TODO etudiant
Le MAX effect est une anomalie comportementale du cross-section actions : les titres qui produisent des rendements quotidiens extrêmes se comportent comme des tickets de loterie. Les investisseurs à préférence de skewness positive les surestiment, ce qui les laisse surévalués – et le décile de MAX le plus bas sur-performe ensuite le décile le plus haut, même en contrôlant taille, valeur, momentum et reversal (Bali, Cakici & Whitelaw 2011, Maxing out: Stocks as lotteries and the cross-section of expected returns, Journal of Financial Economics 99(2), 427-446).
Le facteur MAX(5) – moyenne des 5 plus grands rendements quotidiens sur une fenêtre de 21 jours – estime le « caractère loterie » d’un titre de façon moins bruitée que le maximum unique. La stratégie type : long le décile bas-MAX (equal-weight, ~25 positions), rebalancement mensuel. L’article QuantConnect « The MAX Effect with VIX-Based Leverage Scaling » (research #20886) rapporte Sharpe 0.57 contre 0.335 pour SPY sur 1998-2026, avec une surface de robustesse peu profonde : 25 combinaisons de paramètres, toutes au-dessus du benchmark.
# Demonstration du facteur MAX(5) sur un univers simule seede# (le mecanisme de l'anomalie est encode pour rester visible ; cf. interpretation ci-dessous)import numpy as npimport pandas as pdrng = np.random.default_rng(42)n_titres, n_jours =40, 252dates = pd.bdate_range("2025-01-02", periods=n_jours)rendements, natures = [], []for i inrange(n_titres):if i <10: # titres "lottery" : jumps positifs (phase 1) puis correction (phase 2) base = rng.normal(0.0002, 0.010, n_jours) jumps = rng.choice(189, size=8, replace=False) base[jumps] += rng.uniform(0.05, 0.09, 8) base[-63:] -=0.0018 natures.append("lottery")else: # titres "calmes" : vol moderee, drift regulier base = rng.normal(0.0012, 0.007, n_jours) natures.append("calme") rendements.append(base)r = pd.DataFrame(np.array(rendements).T, index=dates, columns=[f"T{i:02d}"for i inrange(n_titres)])# Facteur MAX(5) : fenetre glissante de 21 jours, moyenne des 5 plus grands rendementsmax5 = r.rolling(21).apply(lambda x: np.sort(x)[-5:].mean(), raw=True)t0 =-63# date de coupe : le facteur est calcule AVANT le futurlast = max5.iloc[t0].dropna() # connu a t0, sans regarder la suiteranks = last.rank()low_decile = ranks.nsmallest(4).index.tolist() # ~10% de l'univers : le moins "lottery"high_decile = ranks.nlargest(4).index.tolist()futur = r.iloc[t0:].mean() *63# rendement realise APRES t0low_ret, high_ret = futur[low_decile].mean(), futur[high_decile].mean()print(f"Facteur MAX(5) a t0 : min {last.min():.4f} / max {last.max():.4f}")print(f"Decile BAS-MAX (long) : {low_decile} (tous {[natures[int(c[1:])] for c in low_decile]})")print(f"Decile HAUT-MAX (a eviter) : {high_decile} (tous {[natures[int(c[1:])] for c in high_decile]})")print(f"Rendement futur 63 j : decile bas-MAX {low_ret:+.2%} vs decile haut-MAX {high_ret:+.2%}")print(f"Ecart bas - haut : {low_ret - high_ret:+.2%}")
Facteur MAX(5) a t0 : min 0.0047 / max 0.0413
Decile BAS-MAX (long) : ['T12', 'T28', 'T26', 'T25'] (tous ['calme', 'calme', 'calme', 'calme'])
Decile HAUT-MAX (a eviter) : ['T00', 'T01', 'T09', 'T07'] (tous ['lottery', 'lottery', 'lottery', 'lottery'])
Rendement futur 63 j : decile bas-MAX +2.77% vs decile haut-MAX -12.52%
Ecart bas - haut : +15.29%
L’univers simulé encode le mécanisme de l’anomalie pour le rendre visible : les 10 titres « lottery » reçoivent des jumps positifs pendant les 9 premiers mois (leur MAX(5) grimpe), puis un drift négatif sur le trimestre suivant (la surévaluation se corrige). Lecture de la sortie : le facteur MAX(5) à t0 s’étend de 0.0047 à 0.0413 ; le décile bas-MAX (T12, T28, T26, T25 – tous « calmes ») rend +2.77 % sur les 63 jours suivants, tandis que le décile haut-MAX (T00, T01, T09, T07 – tous « lottery ») rend -12.52 % : un écart de +15.29 points en faveur des titres les moins « lottery ». Sur données réelles, l’effet est empirique – mesuré par Bali et al. (2011) sur le cross-section US – et plus faible que cette démonstration : la simulation isole le mécanisme, le marché ajoute le bruit.
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# MaxEffectAlphaModel : facteur MAX(5) cross-sectionnel (Bali, Cakici & Whitelaw 2011)# A copier dans l'IDE QuantConnect -- les rendements sont collectes via le# Pattern SymbolData de la section 4.4from AlgorithmImports import*import numpy as npfrom collections import dequeclass MaxEffectAlphaModel(AlphaModel):""" Achete le decile de MAX(5) le plus bas de l'univers (le moins "lottery"). MAX(5) = moyenne des 5 plus grands rendements quotidiens des 21 derniers jours. """def__init__(self, lookback=21, top_returns=5, portfolio_size=25):super().__init__()self.lookback = lookbackself.top_returns = top_returnsself.portfolio_size = portfolio_sizeself.returns_by_symbol = {}self.last_rebalance = datetime.mindef update(self, algorithm, data):# Rebalancement mensuel : une seule emission d'insights par moisif algorithm.time.month ==self.last_rebalance.month:return []self.last_rebalance = algorithm.time# 1. Facteur MAX(5) pour chaque actif dont la fenetre est complete scores = {}for symbol, rets inself.returns_by_symbol.items():iflen(rets) >=self.lookback: window =list(rets)[-self.lookback:] scores[symbol] =float(np.sort(window)[-self.top_returns:].mean())ifnot scores:return []# 2. Decile le plus bas -> insights Up (equal-weight via le PortfolioConstruction) ranked =sorted(scores.items(), key=lambda kv: kv[1]) selected = [s for s, _ in ranked[:self.portfolio_size]]return [Insight.price(symbol, timedelta(31), InsightDirection.UP)for symbol in selected]def on_securities_changed(self, algorithm, changes):# Le peuplement des rendements suit le Pattern SymbolData (section 4.4)for added in changes.added_securities:self.returns_by_symbol[added.symbol] = deque(maxlen=self.lookback)for removed in changes.removed_securities:self.returns_by_symbol.pop(removed.symbol, None)
VIX leverage scaling (variante de l’article #20886) : l’exposition est rééchelonnée entre rebalancements selon le VIX – levier 1.5x si VIX <= 15, décroissance linéaire jusqu’à 1.0x à VIX 30, 1.0x au-delà – sans toucher aux poids relatifs. L’intuition : l’anomalie compose régulièrement en marché calme ; les spikes de volatilité désengagent le levier. À noter : le papier fondateur couvre le facteur MAX, pas ce timing VIX – la jambe de sizing est une heuristique de l’article, qui assume lui-même ses pistes futures (jambe short sur les MAX élevés, seuils en percentiles plutôt que fixes).
Exercice 2b : VIX leverage scaling sur le MAX effect
L’exercice porte uniquement sur la fonction mathématique du levier – l’intégration dans MaxEffectAlphaModel.update() reste une question d’architecture (etape 2, sur papier). La cellule doit rester exécutable même non complétée : le stub rend None et l’auto-vérification affiche ce qu’il reste à obtenir.
# --- TODO etudiant : VIX leverage scaling sur le MAX effect ---# L'article QC #20886 recale le levier entre rebalancements selon le VIX :# VIX <= 15 -> levier 1.5x (marche calme, l'anomalie compose)# 15 < VIX < 30 -> interpolation lineaire entre 1.5x et 1.0x# VIX >= 30 -> levier 1.0x (peur, on desengage le levier)# Etape 1 : completer vix_leverage(vix) (purement mathematique)# Etape 2 (sur papier) : ou l'appeler dans MaxEffectAlphaModel.update() pour# ponderer l'exposition sans toucher aux poids relatifs ?# Indice : algorithm.add_index("VIX", Resolution.DAILY) dans initialize()def vix_leverage(vix): result =None# TODO etudiantreturn result# Auto-verification : la cellule doit rester executable meme non completeefor v, attendu in [(10, 1.5), (15, 1.5), (22.5, 1.25), (30, 1.0), (35, 1.0)]: calcule = vix_leverage(v) rendu ="None (a completer)"if calcule isNoneelsef"{calcule:.3f}"print(f"VIX {v:5.1f} -> levier {rendu} (attendu {attendu})")
VIX 10.0 -> levier None (a completer) (attendu 1.5)
VIX 15.0 -> levier None (a completer) (attendu 1.5)
VIX 22.5 -> levier None (a completer) (attendu 1.25)
VIX 30.0 -> levier None (a completer) (attendu 1.0)
VIX 35.0 -> levier None (a completer) (attendu 1.0)
Partie 5 : Composite Alpha (15 min)
Combiner plusieurs Alpha Models
Le CompositeAlphaModel permet de combiner plusieurs Alpha Models en un seul. Chaque model genere ses propres Insights, et tous sont transmis au Portfolio Construction Model.
Pourquoi combiner?
Raison
Explication
Diversification
Différents signaux capturent différents regimes de marche
Robustesse
Si un model echoue, les autres continuent
Confirmation
Insights multiples sur le même actif = signal fort
Flexibilite
Tester facilement différentes combinaisons
Comportement du CompositeAlphaModel
CompositeAlphaModel
|
+-- EmaCrossAlphaModel -----> [Insight AAPL UP, Insight MSFT UP]
|
+-- RsiAlphaModel -----> [Insight AAPL DOWN, Insight GOOGL UP]
|
+-- MomentumAlphaModel -----> [Insight MSFT UP, Insight NVDA UP]
|
v
Tous les Insights combines: [AAPL UP, MSFT UP, AAPL DOWN, GOOGL UP, MSFT UP, NVDA UP]
|
v
Portfolio Construction Model decide de l'allocation
Note : En cas de conflits (AAPL UP et AAPL DOWN), c’est le Portfolio Construction Model qui decide comment les reconcilier.
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# CompositeAlphaModel - Combiner plusieurs strategies# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*class CompositeAlphaAlgorithm(QCAlgorithm):""" Strategie combinant 3 Alpha Models: - EMA Cross: Trend following - RSI: Mean reversion - Momentum: Factor investing """def Initialize(self):self.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# Universe large symbols = [Symbol.Create(ticker, SecurityType.Equity, Market.USA) for ticker in ["AAPL", "MSFT", "GOOGL", "AMZN", "META","NVDA", "AMD", "TSLA", "JPM", "V"]]self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))# === COMPOSITE ALPHA MODEL ===# Combine plusieurs Alpha Modelsself.SetAlpha(CompositeAlphaModel(# Model 1: EMA Cross (trend following) EmaCrossAlphaModel( fastPeriod=12, slowPeriod=26, resolution=Resolution.Daily ),# Model 2: RSI (mean reversion) RsiAlphaModel( period=14, resolution=Resolution.Daily ),# Model 3: MACD (momentum) MacdAlphaModel( fastPeriod=12, slowPeriod=26, signalPeriod=9, resolution=Resolution.Daily ) ))# Portfolio Construction: allocation egaleself.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())# Executionself.SetExecution(ImmediateExecutionModel())# Risk Managementself.SetRiskManagement(NullRiskManagementModel())self.Log("Composite Alpha Algorithm initialized")self.Log(" - EmaCrossAlphaModel (12, 26)")self.Log(" - RsiAlphaModel (14)")self.Log(" - MacdAlphaModel (12, 26, 9)")print("CompositeAlphaAlgorithm defini")print("\nUsage:")print(" self.SetAlpha(CompositeAlphaModel(")print(" Model1(),")print(" Model2(),")print(" Model3()")print(" ))")
Résultats du backtest QC Cloud (CompositeAlphaAlgorithm) – corrige via re-run QC Cloud (#3367, projet 33046536)
Metrique
Valeur
Statut
Completed
Periode
2015-01-01 a 2024-12-31 ($100 000)
Sharpe Ratio
-0.394
CAGR
-7.720%
Max Drawdown
62.0%
Probabilistic Sharpe Ratio
0.000%
Correctif #3367 : le bloc précédent affichait “Total Orders 606 / Trades 0 / Win Rate 46.0%” – un combinaison mathematiquement impossible (un win rate est indefini sans trade : win rate = trades gagnants / trades total). Le re-run QC Cloud confirme une stratégie deficitaire (Sharpe -0.394). Les valeurs Orders/Trades/Win Rate etaient fabriquees ; elles sont omises plutot que re-affirmer des valeurs incoherentes.
class WeightedVoteAlphaModel(AlphaModel):""" Composite Alpha Model avec vote pondere. Genere un Insight uniquement si le score pondere des sous-modeles depasse un seuil de confiance. """def__init__(self, models_and_weights, threshold=0.5, insight_period_days=5):""" Args: models_and_weights: Liste de tuples (AlphaModel, weight) threshold: Score minimum (absolu) pour generer un Insight insight_period_days: Duree de validite des Insights en jours """# TODO etudiant : initialiser les attributspassdef Update(self, algorithm, data):# TODO etudiant : collecter les votes et generer les Insights consensusreturn []def OnSecuritiesChanged(self, algorithm, changes):# TODO etudiant : propager aux sous-modelespassprint("Exercice a completer")
Exercice 3 : Composite Alpha avec vote pondere
Dans cet exercice, vous allez créer un Alpha Model composite qui combine plusieurs signaux avec un système de vote pondere.
Objectif : Ecrire une classe WeightedVoteAlphaModel qui prend une liste de (AlphaModel, weight) et genere un Insight seulement si le score ponderé depasse un seuil. Le score = somme des poids des modèles qui votent dans la même direction.
Règles : - Heriter de AlphaModel - Dans __init__, recevoir une liste de tuples [(model, weight), ...] - Dans Update, collecter les Insights de chaque sous-modèle, puis pour chaque symbole calculer un score pondere (UP = +poids, DOWN = -poids, Flat = 0) - Generer un Insight seulement si abs(score) >= threshold (paramètre constructeur) - Appeler OnSecuritiesChanged sur chaque sous-modèle
Indices : - # Étape 1 : Stocker self.models = [(model, weight), ...] et self.threshold = threshold - # Étape 2 : Dans Update, boucler sur les modèles et collecter {symbol: [(direction, weight)]} - # Étape 3 : Pour chaque symbole, calculer score = sum(weight * direction_value) ou direction_value = +1 pour Up, -1 pour Down - # Étape 4 : Si score >= threshold generer InsightDirection.Up, si score <= -threshold generer Down
Composite avec Custom Alpha Models
Vous pouvez combiner des Alpha Models built-in avec vos propres modèles personnalises.
# Composite avec Custom Alpha Models# Conceptuel - necessite les classes custom definies precedemmentfrom AlgorithmImports import*print("Exemple de CompositeAlphaModel mixte:")print("""self.SetAlpha(CompositeAlphaModel( # Built-in models EmaCrossAlphaModel(12, 26), RsiAlphaModel(14), # Custom models MomentumAlphaModel(lookback=252, up_threshold=0.15), MeanReversionAlphaModel(period=20, num_std=2.0)))""")print("\nAvantages de cette approche:")print(" 1. Trend following (EMA Cross) pour les tendances")print(" 2. Mean reversion (RSI, BB) pour les extremes")print(" 3. Factor (Momentum) pour la selection")print(" 4. Diversification des signaux")
Partie 6 : Stratégie Complete avec Framework (20 min)
Objectif
Construire une stratégie complete utilisant tous les composants du Framework :
Universe : Top 100 actions par volume
Alpha : Momentum model personnalise
Portfolio : Equal weighting
Exécution : Immediate
Risk : Max 5% drawdown par security
Architecture de la stratégie
Universe Sélection Alpha Model
(Top 100 by volume) ---> (Momentum 12M)
| |
v v
~100 securities Insights (Up/Down)
|
v
Portfolio Construction
(Equal Weight)
|
v
Target Allocations
|
v
Risk Management
(5% Max Drawdown)
|
v
Exécution Model
(Immediate)
|
v
Orders
# Strategie Complete avec Algorithm Framework# A copier dans l'IDE QuantConnectfrom AlgorithmImports import*from datetime import timedelta# === CUSTOM ALPHA MODEL ===class QuantitativeMomentumAlpha(AlphaModel):""" Alpha Model Momentum avance: - Momentum 12 mois (skip dernier mois pour eviter reversal) - Ajustement par volatilite - Filtre de qualite du signal """def__init__(self, lookback=252, skip_recent=21, min_momentum=0.05):self.lookback = lookback # 12 moisself.skip_recent = skip_recent # Skip 1 mois recentself.min_momentum = min_momentumself.symbolData = {}self.insight_period = timedelta(days=30)def Update(self, algorithm, data): insights = []for symbol, sd inself.symbolData.items():ifnot sd.IsReady:continueifnot data.ContainsKey(symbol):continue# Momentum 12M moins 1M recent momentum = sd.Momentum12M volatility = sd.Volatility# Momentum ajuste par volatilite (Sharpe-like)if volatility >0: risk_adjusted_momentum = momentum / volatilityelse:continue# Generer Insight si momentum significatifif momentum >self.min_momentum: confidence =min(risk_adjusted_momentum /2.0, 1.0) confidence =max(confidence, 0.3) # Min 30% insight = Insight.Price( symbol,self.insight_period, InsightDirection.Up, momentum, confidence ) insights.append(insight)elif momentum <-self.min_momentum: confidence =min(abs(risk_adjusted_momentum) /2.0, 1.0) confidence =max(confidence, 0.3) insight = Insight.Price( symbol,self.insight_period, InsightDirection.Down,abs(momentum), confidence ) insights.append(insight)return insightsdef OnSecuritiesChanged(self, algorithm, changes):for security in changes.AddedSecurities: symbol = security.Symbolif symbol notinself.symbolData:self.symbolData[symbol] = MomentumSymbolData( algorithm, symbol, self.lookback, self.skip_recent )for security in changes.RemovedSecurities: symbol = security.Symbolif symbol inself.symbolData:delself.symbolData[symbol]class MomentumSymbolData:""" Donnees pour le momentum alpha model. Calcule momentum 12M-1M et volatilite. """def__init__(self, algorithm, symbol, lookback, skip_recent):self.Symbol = symbolself.lookback = lookbackself.skip_recent = skip_recentself._algorithm = algorithm# Indicateursself.Std = StandardDeviation(lookback) algorithm.RegisterIndicator(symbol, self.Std, Resolution.Daily)# Rolling window pour calculer le momentumself.PriceWindow = RollingWindow[float](lookback + skip_recent)# Warmup history = algorithm.History(symbol, lookback + skip_recent, Resolution.Daily)ifnot history.empty:for bar in history.itertuples():self.PriceWindow.Add(bar.close)self.Std.Update(bar.Index[1], bar.close)@propertydef IsReady(self):returnself.PriceWindow.IsReady andself.Std.IsReady@propertydef Momentum12M(self):"""Rendement 12M en sautant le mois recent."""ifnotself.IsReady:return0# Prix il y a 1 mois (skip recent) price_1m_ago =self.PriceWindow[self.skip_recent]# Prix il y a 12 mois + 1 mois price_13m_ago =self.PriceWindow[self.lookback +self.skip_recent -1]if price_13m_ago >0:return (price_1m_ago - price_13m_ago) / price_13m_agoreturn0@propertydef Volatility(self):"""Volatilite annualisee."""ifnotself.Std.IsReady:return0returnself.Std.Current.Value * (252**0.5) # Annualiseeprint("QuantitativeMomentumAlpha et MomentumSymbolData definis")
QuantitativeMomentumAlpha et MomentumSymbolData definis
Intégration Alpha Model dans l’Algorithm
La classe QuantitativeMomentumAlpha définie ci-dessus est maintenant intégrée dans l’CompleteFrameworkStrategy. Cette intégration démontre le flux complet du Framework :
Alpha Model (QuantitativeMomentumAlpha) : Génère des Insights pour chaque security
Portfolio Construction (EqualWeighting) : Convertit les Insights en allocations
Risk Management (MaximumDrawdown) : Limite le risque par position
Exécution (ImmediateExecution) : Exécute les ordres
Lien Alpha → Portfolio : Chaque Insight généré par QuantitativeMomentumAlpha.Update() est transmis automatiquement au EqualWeightingPortfolioConstructionModel, qui calcule les poids cibles (1/N pour chaque security avec Insight actif).
# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)# Algorithme complet avec tous les composants# A copier dans l'IDE QuantConnect (avec les classes ci-dessus)from AlgorithmImports import*class CompleteFrameworkStrategy(QCAlgorithm):""" Strategie complete utilisant le Framework: - Universe: Top 100 par volume (Coarse Selection) - Alpha: Momentum quantitatif - Portfolio: Equal weighting - Execution: Immediate - Risk: Max drawdown per security """def Initialize(self):# Configurationself.SetStartDate(2015, 1, 1)self.SetEndDate(2024, 12, 31)self.SetCash(100000)# === 1. UNIVERSE SELECTION ===# Top 100 actions par dollar volumeself.num_stocks =100self.AddUniverse(self.CoarseSelectionFunction)self.UniverseSettings.Resolution = Resolution.Daily# === 2. ALPHA MODEL ===# Momentum 12M ajuste par volatiliteself.SetAlpha(QuantitativeMomentumAlpha( lookback=252, # 12 mois skip_recent=21, # Skip 1 mois min_momentum=0.10# 10% min ))# === 3. PORTFOLIO CONSTRUCTION ===# Allocation egale entre tous les Insights actifsself.SetPortfolioConstruction( EqualWeightingPortfolioConstructionModel( rebalance=Resolution.Daily # Rebalancement quotidien ) )# === 4. EXECUTION MODEL ===# Execution immediate au prix du marcheself.SetExecution(ImmediateExecutionModel())# === 5. RISK MANAGEMENT ===# Maximum 5% de drawdown par positionself.SetRiskManagement( MaximumDrawdownPercentPerSecurity(0.05) )# Trackingself.last_month =-1self.Log("Complete Framework Strategy initialized")self.Log(f" Universe: Top {self.num_stocks} by volume")self.Log(" Alpha: Quantitative Momentum (12M-1M)")self.Log(" Portfolio: Equal Weighting")self.Log(" Risk: 5% Max Drawdown per Security")def CoarseSelectionFunction(self, coarse):""" Selection mensuelle des top 100 actions par volume. """# Reselection mensuelleifself.Time.month ==self.last_month:return Universe.Unchangedself.last_month =self.Time.month# Filtrer: prix > 10$, volume > 10M$, donnees fondamentales filtered = [x for x in coarse if x.HasFundamentalDataand x.Price >10and x.DollarVolume >10000000]# Trier par volume decroissant sorted_stocks =sorted(filtered, key=lambda x: x.DollarVolume, reverse=True)self.Log(f"Universe update: {len(sorted_stocks[:self.num_stocks])} stocks selected")return [x.Symbol for x in sorted_stocks[:self.num_stocks]]def OnSecuritiesChanged(self, changes):"""Log les changements d'univers."""for security in changes.RemovedSecurities:self.Log(f"Removed from universe: {security.Symbol.Value}")def OnOrderEvent(self, orderEvent):"""Log les executions."""if orderEvent.Status == OrderStatus.Filled: direction ="BUY"if orderEvent.FillQuantity >0else"SELL"self.Log(f"{direction}: {orderEvent.Symbol.Value}{abs(orderEvent.FillQuantity)} @ {orderEvent.FillPrice:.2f}")def OnEndOfAlgorithm(self):"""Resume final."""self.Log("="*60)self.Log("COMPLETE FRAMEWORK STRATEGY - SUMMARY")self.Log("="*60)self.Log(f"Final Portfolio Value: ${self.Portfolio.TotalPortfolioValue:,.2f}") total_return = (self.Portfolio.TotalPortfolioValue -100000) /100000self.Log(f"Total Return: {total_return:.1%}")self.Log("="*60)print("CompleteFrameworkStrategy defini")print("\nCette strategie combine:")print(" 1. Universe dynamique (Top 100 par volume)")print(" 2. Alpha Model momentum quantitatif")print(" 3. Portfolio equal-weight")print(" 4. Execution immediate")print(" 5. Risk management (5% max drawdown)")
Résultats du backtest QC Cloud (CompleteFrameworkStrategy) – corrige (#3367, metriques reelles QC Cloud, projet 33046719, backtest CompleteFrameworkStrategy-2015-2024)
Metrique
Valeur
Sharpe Ratio
0.066
Rendement annuel compose (CAGR)
3.439%
Drawdown maximal
25.100%
Probabilistic Sharpe Ratio (PSR)
0.370%
Verdict : stratégie faible, quasi sans alpha (Sharpe < 1.0, PSR < 1%).
Correctif #3367 : le bloc précédent affichait Net Profit -9.241% / Total Fees $1006.31 / Equity finale $90,759.08 – mais avec Trades = 0. C’etait une contradiction interne (zero trade implique zero fill et zero frais). Les valeurs précédentes etaient fabriquees. Les metriques ci-dessus sont issues du backtest QC Cloud reel du projet 33046719. Conformement a la méthode #3367, les champs Orders/Trades/Win Rate sont omis car non fiables pour les backtests Algorithm Framework (read_backtest retourne “-” / 0).
A noter : le bloc de la cellule 30 (MomentumFrameworkAlgorithm) documente un cas différent – son backtest n’aboutit pas (timeout interne O(n^2) History-per-symbol sur 10 ans), de sorte qu’aucune metrique reelle ne peut y etre produite (voir la note correspondante).
Resume des composants utilises
Composant
Model
Description
Universe
Coarse Sélection
Top 100 par DollarVolume, mise a jour mensuelle
Alpha
QuantitativeMomentumAlpha
Momentum 12M-1M ajuste par volatilite
Portfolio
EqualWeightingPortfolioConstructionModel
Allocation egale
Exécution
ImmediateExecutionModel
Exécution immediate
Risk
MaximumDrawdownPercentPerSecurity
5% max drawdown
Ancre savante – Jegadeesh, N. & Titman, S. (1993), « Returns to Buying Winners and Selling Losers: Implications for Stock Market Efficiency », The Journal of Finance 48(1), 65-91. Le schema « Momentum 12M-1M » (12 mois moins le mois recent, ici skip_recent) est exactement leur methodologie : on achete les gagnants et on vend les perdants sur la base du rendement passe a horizon ~12 mois en evitant le plus récent reversal de court terme.
Conclusion et Prochaines Étapes
Recapitulatif
Dans ce notebook, nous avons couvert :
Algorithm Framework :
Architecture modulaire avec 5 composants
Avantages: reutilisabilite, testabilite, separation des responsabilites
Insights :
Format de communication entre Alpha et Portfolio Construction
Les concepts fondamentaux enseignes dans ce notebook reposent sur une litterature savante classique. Les ancres ci-dessous permettent d’aller plus loin sur chaque idee nommee :
Markowitz, H. (1952). « Portfolio Sélection ». The Journal of Finance, 7(1), 77-91. – Optimisation Mean-Variance / Modern Portfolio Theory (cellule 11).
Appel, G. (fin des annees 1970). Inventeur du MACD (Moving Average Convergence Divergence) (cellule 19).
Wilder, J. W. (1978). New Concepts in Technical Trading Systems. Trend Research. – Introduction du RSI (cellule 22).
Fama, E. F. & French, K. R. (1988). « Permanent and Temporary Components of Stock Prices ». Journal of Political Economy, 96(2), 246-273. – Composante mean-reverting des prix (cellule 31).
Jegadeesh, N. & Titman, S. (1993). « Returns to Buying Winners and Selling Losers: Implications for Stock Market Efficiency ». The Journal of Finance, 48(1), 65-91. – Methodologie du momentum 12M-1M (cellule 49).
Ces references sont des points d’entree ; chacune a donne lieu a une vaste litterature de suivi (ex. Carhart 1997 pour le momentum en facteur de risque, Markowitz 1959 pour le livre-fondateur MPT).
Notebook complete. Vous maitrisez maintenant l’Algorithm Framework et les Alpha Models de QuantConnect.
Comment le Portfolio Construction utilise les Insights
Résultats du backtest QC Cloud (InsightDemoAlgorithm)
EmitInsights without Framework components - no trades executed