<< 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.

# 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")

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

  1. Sélection de l’univers : Les 100 actions les plus liquides (dollar volume) avec prix > $5
  2. PCA : Extraction des 3 premières composantes principales des log-prix centres
  3. Regression OLS : Pour chaque action, regression de son log-prix sur les facteurs PCA
  4. Residus : Différence entre prix observe et prix predit par le modèle
  5. Z-scores : Standardisation des residus — les z-scores extremes signalent un ecart
  6. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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

  1. 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%.

  2. Ridge/LASSO regression : Remplacer l’OLS par une regression regularisee pour reduire le surapprentissage sur les residus.

  3. Half-life weighting : Utiliser la demi-vie du mean reversion (duree moyenne de retour a la moyenne) pour ajuster la taille des positions.

  4. 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.

  5. 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.

Retour au sommet