# Exercice 1 : Application de la PCA sur des series de prix
# TODO etudiant : simuler 10 series correlees et appliquer PCA
# Indice : sklearn.decomposition.PCA sur log-prix centres
import numpy as np
def simulate_correlated_prices(n_assets=10, n_days=200, seed=42):
# TODO etudiant : generer un facteur commun + bruit individuel
return None # TODO etudiant : retourner la matrice de prix (n_days x n_assets)
result_pca = None # TODO etudiant : appliquer PCA et afficher variance expliquee
print("Exercice a completer : PCA sur series de prix")<< Sommaire QC | Précédent : QC-Py-Cloud-11-RegimeSwitching << | Suivant : QC-Py-Cloud-07-TemporalCNN >>
QC-Py-Cloud-06 — PCA Statistical Arbitrage Mean Reversion
Module : Hands-On AI Trading, Ch.6 — Applied Machine Learning, Example 13
Objectif : Implementer une stratégie d’arbitrage statistique utilisant l’analyse en composantes principales (PCA) et la regression lineaire pour exploiter les ecarts de prix mean-reverting entre actions correlees.
Approche cloud-native : L’algorithme est execute sur QuantConnect Cloud. Les résultats sont presentes ci-dessous.
Note de conception : Ce notebook est un descriptif pedagogique. Le code executable se trouve dans le projet QuantConnect Cloud correspondant (voir le lien dans le notebook). L’absence d’outputs est intentionnelle.
Exercice 1 : Application de la PCA sur des series de prix
La PCA extrait les facteurs communs a partir de la matrice des log-prix. Les 3 premières composantes expliquent plus de 90% de la variance.
Objectif : Simulez 10 series de prix correlees (avec un facteur commun). Appliquez PCA et affichez la variance expliquee par chaque composante.
Indices : - Indice : Utilisez sklearn.decomposition.PCA sur les log-prix centres. - Indice : pca.explained_variance_ratio_ donne le pourcentage de variance par composante.
1. Concept : PCA Statistical Arbitrage
L’arbitrage statistique par PCA repose sur l’hypothese que les actions financieres sont entrainees par un petit nombre de facteurs communs (composantes principales). Quand une action s’ecarte significativement de la valeur predite par ces facteurs, elle a une probabilite elevee de revenir vers sa valeur attendue (mean reversion).
Pipeline de la stratégie
- Sélection de l’univers : Les 100 actions les plus liquides (dollar volume) avec prix > $5
- PCA : Extraction des 3 premières composantes principales des log-prix centres
- Regression OLS : Pour chaque action, regression de son log-prix sur les facteurs PCA
- Residus : Différence entre prix observe et prix predit par le modèle
- Z-scores : Standardisation des residus — les z-scores extremes signalent un ecart
- Trading : Long sur les actions dont le z-score < -1.5 (ecart negatif = sous-evaluation)
Pourquoi la PCA ?
Les marches financiers ont une structure factorielle : la majorite de la variance des prix peut etre expliquee par quelques facteurs (marche, secteur, taille). La PCA extrait ces facteurs de maniere non supervisee, sans necessiter de labels ou de connaissances prealables sur les secteurs.
Tension pedagogique : stat-arb vs autres stratégies
L’arbitrage statistique est fondamentalement différent du trend following et du momentum : - Trend following : acheter ce qui monte (momentum positif) - Mean reversion : acheter ce qui a baisse (anticipation de rebond) - Stat-arb PCA : acheter ce qui est sous-evalue par rapport aux facteurs communs
Le stat-arb PCA est plus sophistique que le mean reversion simple (RSI) car il utilise un modèle factoriel multivarie plutot qu’un signal univarie.
Exercice 2 : Calcul des z-scores de residus
Les z-scores mesurent l’ecart entre le prix observe et le prix predit par le modèle factoriel. Un z-score < -1.5 signale une sous-evaluation (opportunite d’achat).
Objectif : Implementez compute_zscores(residuals) qui standardise les residus. Identifiez les actions avec un z-score inferieur a -1.5 dans un echantillon simule.
Indices : - Indice : Z-score = (residu - moyenne) / ecart-type. - Indice : Un z-score de -2 signifie que le residu est 2 ecarts-types sous la moyenne.
# Exercice 2 : Calcul des z-scores de residus
# TODO etudiant : implementer compute_zscores(residuals) -> array
# Indice : z = (x - mean) / std
import numpy as np
def compute_zscores(residuals):
# TODO etudiant : standardiser les residus
return None # TODO etudiant : retourner les z-scores
# Simuler des residus
np.random.seed(42)
sim_residuals = np.random.normal(0, 1, 50)
sim_residuals[5] = -3.2 # outlier negatif
sim_residuals[20] = -2.1 # autre outlier
result_zscores = None # TODO etudiant : identifier les z-scores < -1.5
print("Exercice a completer : z-scores de residus")2. Paramètres de la stratégie
| Paramètre | Valeur | Justification |
|---|---|---|
num_components |
3 | Les 3 premières composantes expliquent >90% de la variance |
lookback_days |
60 | Environ 3 mois de données pour la PCA |
z_score_threshold |
1.5 | Seuil de deviation (93.32e percentile) |
universe_size |
100 | Top 100 actions les plus liquides |
| Rebalance | Mensuel | Recalcul PCA + poids chaque debut de mois |
| Capital initial | $1,000,000 | Taille suffisante pour un univers de 100 actions |
Analyse des paramètres (livre Broad)
- num_components : Minimum 2 (pour regression multiple), maximum 6 (composantes supplementaires n’apportent peu). Le Sharpe est maximal a 3 composantes.
- lookback_days : Minimum 21 (1 mois), maximum 126 (6 mois). Le Sharpe est maximise avec 126 jours de lookback.
- z_score_threshold : 1.5 est le seuil optimal identifie. Un seuil plus bas genere plus de trades mais moins selectif.
3. Résultats QC Cloud
Projet QC Cloud : 29463543 (HandsOn-Ex13-PCA-StatArbitrage) Periode : 2019-01-01 au 2024-01-01 (5 ans) Capital initial : $1,000,000
Résultat principal (PCA-StatArb-v2-HandsOn)
| Metrique | Valeur |
|---|---|
| Sharpe Ratio | 0.399 |
| CAGR | 12.652% |
| Max Drawdown | 31.8% |
| Net Profit | +81.3% |
| Total Orders | 1,544 |
| Win Rate | 48% |
| Loss Rate | 52% |
| Profit-Loss Ratio | 1.30 |
| Alpha | 0.005 |
| Beta | 0.902 |
| Sortino Ratio | 0.476 |
| Frais totaux | $12,424.87 |
| Portfolio Turnover | 6.08% |
4. Analyse des performances
4.1 Le paradoxe du win rate
La stratégie a un win rate de seulement 48% (moins d’une chance sur deux de gagner sur un trade individuel). Pourtant, elle genere un CAGR de 12.65% et un profit net de +81.3%. Comment est-ce possible ?
Reponse : Le profit-loss ratio de 1.30. Quand la stratégie gagne, elle gagne en moyenne 0.80%. Quand elle perd, elle perd 0.61%. Ce ratio > 1 compense le win rate < 50%.
C’est une caractéristique commune des stratégies mean-reversion : elles prennent beaucoup de petits paris sur des retours a la moyenne. La plupart echouent, mais les gains sur les trades reussis sont plus importants que les pertes.
4.2 Le drawdown eleve (31.8%)
Un MaxDD de 31.8% est eleve pour une stratégie d’arbitrage. La cause principale : pendant les periodes de stress du marche (COVID-2020, 2022 bear), les relations factorielles entre actions se decomposent temporairement. Les actions qui etaient sous-evaluees par le modèle PCA continuent de baisser.
Le beta de 0.902 confirme que la stratégie reste exposee au risque marche : quand le marche baisse fortement, la stratégie baisse aussi.
4.3 Alpha faible (0.005)
L’alpha annualise de 0.5% est marginal. La majorite du rendement (12.65%) vient de l’exposition au marche (beta 0.902), pas de la capacite du modèle PCA a identifier des ecarts.
C’est une limitation fondamentale du stat-arb sur actions US liquides : les inefficiences de prix sont rapidement corrigees par les joueurs institutionnels, ne laissant que des traces d’alpha.
5. Comparaison avec les autres stratégies Cloud
| Stratégie | Sharpe | CAGR | MaxDD | Edge |
|---|---|---|---|---|
| Risk Parity v4 (Cloud-01) | 0.278 | 6.17% | 20.4% | Allocation diversifiee |
| Sector Rotation v3 (Cloud-02) | 0.614 | 10.76% | 15.5% | Trend following multi-asset |
| Dual Momentum v2 (Cloud-03) | 0.392 | 8.79% | 23.6% | Momentum + univers diversifie |
| Mean Reversion v2 (Cloud-04) | 0.307 | 5.89% | 14.6% | RSI + regime filter |
| PCA Stat Arb (Cloud-06) | 0.399 | 12.65% | 31.8% | PCA + OLS residuals |
Observations
CAGR le plus eleve : 12.65%, superieur a toutes les autres stratégies. Mais c’est un leurre — la majorite vient du beta, pas de l’alpha.
Sharpe mediocre : 0.399, inferieur au Sector Rotation (0.614) et comparable au Dual Momentum (0.392). Le ratio rendement/risque n’est pas exceptionnel.
MaxDD catastrophique : 31.8%, le plus eleve de toutes les stratégies. En periode de stress, les positions stat-arb aggrave les pertes au lieu de les amortir.
Sharpe-adjusted, le PCA stat-arb est inferieur au trend following : Le Sector Rotation offre un Sharpe de 0.614 avec un MaxDD de 15.5%. Le PCA stat-arb offre un Sharpe de 0.399 avec un MaxDD de 31.8%. Le rendement ajuste au risque est nettement en faveur du trend following.
6. Algorithme detaille
Le code est deploye sur QC Cloud (projet 29463543). Le notebook local presente l’analyse des résultats, conformement au workflow cloud-native.
Étape 1 : Sélection de l’univers
def _select_assets(self, fundamental):
# Top 100 actions les plus liquides, prix > $5
return [
f.symbol
for f in sorted(
[f for f in fundamental if f.price > 5],
key=lambda f: f.dollar_volume
)[-self._universe_size:]
]Étape 2 : Calcul des poids via PCA + OLS
def _get_weights(self, history):
# 1. Log-transformer et centrer les prix
sample = np.log(history.dropna(axis=1))
sample -= sample.mean()
# 2. Ajuster la PCA
pca_model = PCA().fit(sample)
# 3. Projeter sur les composantes principales
factors = np.dot(sample, pca_model.components_.T)[:, :3]
# 4. Regression lineaire pour chaque action
residuals = {}
for ticker in sample.columns:
model = LinearRegression()
model.fit(factors, sample[ticker])
residuals[ticker] = sample[ticker] - model.predict(factors)
# 5. Z-scores des residus
resids = pd.DataFrame(residuals)
zscores = ((resids - resids.mean()) / resids.std()).iloc[-1]
# 6. Sélectionner les actions sous-evaluees (z-score < -1.5)
selected = zscores[zscores < -1.5]
weights = selected * (1 / selected.abs().sum())
return weights.sort_values()Étape 3 : Exécution des positions
# Poids negatifs inverses pour le mean reversion
self.set_holdings(
[PortfolioTarget(symbol, -weight) for symbol, weight in weights.items()],
True
)Note sur l’inversion des poids : les z-scores selectionnes sont negatifs (< -1.5). Les poids sont proportionnels aux z-scores, donc negatifs. L’inversion (-weight) rend les positions longues. Plus le z-score est negatif, plus la position est importante.
7. Lecons principales
7.1 Le stat-arb PCA ne bat pas le trend following sur actions US
Malgre un CAGR eleve (12.65%), la stratégie PCA stat-arb offre un ratio rendement/risque mediocre (Sharpe 0.399) et un drawdown catastrophique (31.8%). Le trend following multi-asset (Cloud-02) reste superieur avec un Sharpe de 0.614 et un MaxDD de 15.5%.
7.2 Le win rate n’est pas tout
Un win rate de 48% est compatible avec une stratégie profitable si le profit-loss ratio est > 1. Le ratio de 1.30 compense les pertes frequentes par des gains plus importants.
7.3 Le regime de marche est critique
Les relations factorielles PCA se decomposent en periode de stress. Un filtre de regime (SMA200 ou VIX) pourrait reduire le drawdown, au prix d’un CAGR plus faible.
7.4 L’alpha est faible sur actions liquides
Sur un univers de 100 actions US liquides, les inefficiences sont rapidement exploitees. L’alpha de 0.5% confirme que le rendement provient principalement du beta. Des univers moins liquides (small caps, marches emergents) pourraient offrir plus d’alpha.
Exercice 3 : Profit factor et win rate
La stratégie PCA stat-arb a un win rate de 48% mais reste profitable grace a un profit-loss ratio de 1.30. Le profit factor (somme des gains / somme des pertes) est une metrique complementaire.
Objectif : Simulez 200 trades avec un win rate de 48% et un gain moyen de 0.80% vs perte moyenne de 0.61%. Calculez le profit factor, le rendement cumule et le nombre de trades gagnants vs perdants.
Indices : - Indice : Profit factor = total_gains / total_pertes. Un PF > 1 est profitable. - Indice : Le rendement cumule = produit des (1 + r_i) pour chaque trade.
# Exercice 3 : Profit factor et win rate
# TODO etudiant : simuler 200 trades et calculer profit factor
# Indice : PF = sum(gains) / sum(pertes), un PF > 1 est profitable
import numpy as np
def simulate_trades(n_trades=200, win_rate=0.48, avg_win=0.008, avg_loss=0.0061, seed=42):
# TODO etudiant : generer les trades
# TODO etudiant : calculer PF, rendement cumule, win/loss counts
return None # TODO etudiant : retourner les metriques
result_pf = None # TODO etudiant : lancer la simulation
print("Exercice a completer : profit factor et win rate")8. Pour aller plus loin
Regime filter : Ajouter un filtre VIX ou SMA200 pour reduire l’exposition en bear market. Comparer avec le Mean Reversion Cloud-04 ou le regime filter reduit le MaxDD de 42% a 15%.
Ridge/LASSO regression : Remplacer l’OLS par une regression regularisee pour reduire le surapprentissage sur les residus.
Half-life weighting : Utiliser la demi-vie du mean reversion (duree moyenne de retour a la moyenne) pour ajuster la taille des positions.
Univers dynamique : Augmenter l’univers a 200-300 actions pour capturer plus d’inefficiencies, ou reduire a 50 actions plus liquides pour diminuer les couts de transaction.
PCA alternative : clustering sectoriel : Remplacer la PCA non supervisee par un clustering sectoriel explicite (GICS) pour une interpretation plus claire des facteurs.
Reference : Avellaneda, M. & Lee, J.H. (2010) — “Statistical Arbitrage in the US Equities Market”, Quantitative Finance. Broad, J. (2025) — Hands-On AI Trading with Python, QuantConnect, and AWS, Chapter 6, Example 13.