pairs-cointegration / CLAUDE.md
CLAUDE.md
Raw

CLAUDE.md — Pairs Trading & Cointegration Dashboard

🎯 Objectif du projet

Construire un dashboard Streamlit de pairs trading basé sur la cointégration statistique. L'utilisateur entre deux tickers → l'outil teste si la paire est cointégrée → si oui, affiche les signaux de trading historiques et les performances backtest.

Ce projet fait partie d'un portfolio GitHub "Finance x Data" destiné à des recruteurs en finance/quant/fintech.


👤 Contexte développeur

  • Étudiant ingénieur 3ème année (ECE Lyon), spécialisation en 4ème année (l'année prochaine), en spé finance.
  • Niveau Python : intermédiaire (pandas, numpy OK ; statsmodels et backtest = nouveau)
  • Expérience trading crypto légère
  • Objectif : apprendre en faisant, le code doit être clair et commenté

Style de code attendu :

  • Commentaires en francais dans le code
  • README en francais
  • Noms de variables explicites (pas de x, df2, temp)
  • Fonctions modulaires, pas un seul fichier monolithique
  • Docstrings sur les fonctions principales
  • Type hints quand c'est simple

🏗️ Architecture du projet

pairs-trading-cointegration/
├── CLAUDE.md
├── README.md
├── requirements.txt
├── app.py                     # Point d'entrée Streamlit
├── src/
│   ├── __init__.py
│   ├── data.py                # Récupération des données (yfinance)
│   ├── cointegration.py       # Tests statistiques (Engle-Granger)
│   ├── signals.py             # Génération des signaux (z-score)
│   ├── backtest.py            # Simulation des trades & métriques
│   └── visualization.py       # Graphiques Plotly
├── notebooks/
│   └── exploration.ipynb      # Notebook d'exploration (optionnel)
└── tests/
    └── test_cointegration.py  # Tests unitaires basiques

📦 Stack technique

  • Python 3.11+
  • yfinance — données historiques (prix daily adjusted close, 5 ans min)
  • pandas / numpy — manipulation de données
  • statsmodels — test de cointégration Engle-Granger (coint())
  • scipy — calculs statistiques complémentaires
  • plotly — graphiques interactifs
  • streamlit — dashboard web

🔧 Fonctionnalités à implémenter (par phase)

Phase 1 — Data Fetching (src/data.py)

def fetch_pair_data(ticker_a: str, ticker_b: str, period: str = "5y") -> pd.DataFrame:
    """
    Récupère les prix adjusted close des deux tickers via yfinance.
    Retourne un DataFrame avec colonnes [ticker_a, ticker_b], index = date.
    Gère les cas d'erreur : ticker invalide, données manquantes.
    Supprime les lignes avec NaN (jours où un seul ticker a coté).
    """

Phase 2 — Tests de cointégration (src/cointegration.py)

def test_cointegration(prices_a: pd.Series, prices_b: pd.Series) -> dict:
    """
    Effectue le test de cointégration d'Engle-Granger.
    
    Étapes :
    1. Régression linéaire : prices_a = alpha + beta * prices_b + residuals
    2. Test ADF (Augmented Dickey-Fuller) sur les résidus
    3. Si p-value < 0.05 → la paire est cointégrée
    
    Retourne : {
        "is_cointegrated": bool,
        "p_value": float,
        "beta": float,          # coefficient de hedge
        "alpha": float,         # intercept
        "test_statistic": float,
        "critical_values": dict  # 1%, 5%, 10%
    }
    
    Utiliser : from statsmodels.tsa.stattools import coint
    """

def compute_spread(prices_a: pd.Series, prices_b: pd.Series, beta: float) -> pd.Series:
    """
    Calcule le spread : spread = prices_a - beta * prices_b
    """

def compute_zscore(spread: pd.Series, window: int = 60) -> pd.Series:
    """
    Calcule le z-score rolling du spread.
    z = (spread - rolling_mean) / rolling_std
    window = 60 jours par défaut (environ 3 mois de trading).
    """

Phase 3 — Signaux de trading (src/signals.py)

def generate_signals(zscore: pd.Series, entry_threshold: float = 2.0, exit_threshold: float = 0.5) -> pd.DataFrame:
    """
    Génère les signaux de trading basés sur le z-score.
    
    Règles :
    - LONG spread (buy A, short B) quand z-score < -entry_threshold
    - SHORT spread (short A, buy B) quand z-score > +entry_threshold
    - EXIT quand |z-score| < exit_threshold
    - STOP LOSS si |z-score| > 3.5 (le spread diverge trop, couper les pertes)
    
    Retourne un DataFrame avec colonnes :
    - 'zscore': le z-score
    - 'signal': 1 (long spread), -1 (short spread), 0 (no position)
    - 'trade_type': 'entry_long', 'entry_short', 'exit', 'stop_loss', None
    """

Phase 4 — Backtest (src/backtest.py)

def run_backtest(prices_a: pd.Series, prices_b: pd.Series, signals: pd.DataFrame, beta: float, transaction_cost: float = 0.001) -> dict:
    """
    Simule les trades et calcule les performances.
    
    Pour chaque trade :
    - Long spread = acheter A, shorter B (pondéré par beta)
    - P&L = delta(spread) * position - frais de transaction
    
    Métriques à calculer :
    - total_return: rendement total (%)
    - annualized_return: rendement annualisé
    - sharpe_ratio: (rendement - risk_free) / volatilité, annualisé
    - max_drawdown: pire perte depuis un pic (%)
    - win_rate: % de trades gagnants
    - num_trades: nombre total de trades
    - avg_trade_duration: durée moyenne d'un trade (jours)
    - profit_factor: gains bruts / pertes brutes
    
    Retourne : dict avec toutes les métriques + Series de la courbe d'equity
    """

Phase 5 — Dashboard Streamlit (app.py)

Layout du dashboard :

┌─────────────────────────────────────────────────┐
│  PAIRS TRADING — Cointegration Dashboard        │
├─────────────────────────────────────────────────┤
│                                                 │
│  [Ticker A: ____]  [Ticker B: ____]  [Analyze]  │
│                                                 │
│  Suggested pairs:  KO/PEP  XOM/CVX  JPM/BAC    │
│                    GOLD/NEM  GOOGL/MSFT         │
│                                                 │
├─────────────────────────────────────────────────┤
│  📊 COINTEGRATION TEST RESULTS                  │
│  Status: ✅ Cointegrated (p=0.003)              │
│  Beta (hedge ratio): 0.847                      │
│  Interpretation: [texte clair]                  │
├─────────────────────────────────────────────────┤
│  📈 CHART 1: Normalized Prices (base 100)       │
│  [Les deux prix normalisés superposés]          │
├─────────────────────────────────────────────────┤
│  📉 CHART 2: Spread & Z-Score                   │
│  [Spread en haut, Z-score en bas]               │
│  [Zones colorées: vert = entry, rouge = stop]   │
├─────────────────────────────────────────────────┤
│  📊 CHART 3: Equity Curve (backtest)            │
│  [Courbe du portefeuille vs buy-and-hold SPY]   │
├─────────────────────────────────────────────────┤
│  📋 PERFORMANCE METRICS                         │
│  Total Return | Sharpe | Max DD | Win Rate |... │
├─────────────────────────────────────────────────┤
│  📜 TRADE LOG                                   │
│  [Tableau des trades individuels]               │
└─────────────────────────────────────────────────┘

Paires pré-chargées (boutons cliquables) :

  • KO / PEP (Consumer Staples)
  • XOM / CVX (Energy)
  • JPM / BAC (Banking)
  • GOLD / NEM (Gold Mining)
  • GOOGL / MSFT (Big Tech)

Sidebar (paramètres ajustables) :

  • Période de données : 3y, 5y, 10y
  • Z-score entry threshold : slider 1.5 → 3.0 (défaut 2.0)
  • Z-score exit threshold : slider 0.0 → 1.0 (défaut 0.5)
  • Rolling window pour z-score : slider 20 → 120 jours (défaut 60)
  • Transaction costs : slider 0% → 0.5% (défaut 0.1%)

Quand la paire n'est PAS cointégrée : Afficher un message clair :

"⚠️ This pair is NOT cointegrated (p-value = 0.45). The spread does not show mean-reverting behavior, which means pairs trading signals would be unreliable. Try a different pair or adjust the time period."

Ne pas afficher le backtest dans ce cas, juste les prix normalisés et le résultat du test.


📐 Concepts clés (pour que Claude Code comprenne la logique)

Cointégration ≠ Corrélation

  • Corrélation = les prix bougent dans la même direction
  • Cointégration = l'ÉCART entre les prix est stable et revient vers une moyenne
  • Deux actifs peuvent être corrélés sans être cointégrés (et inversement)
  • C'est la cointégration qui nous intéresse pour le pairs trading

Le spread

spread(t) = price_A(t) - beta * price_B(t)
  • beta vient de la régression linéaire (OLS) de A sur B
  • Le spread doit être stationnaire (= il revient vers sa moyenne)
  • C'est le test ADF sur le spread qui confirme la stationnarité

Le z-score

z(t) = (spread(t) - mean(spread, window)) / std(spread, window)
  • z > 2 → le spread est anormalement haut → short le spread
  • z < -2 → le spread est anormalement bas → long le spread
  • |z| < 0.5 → le spread est revenu à la normale → fermer la position

Market-neutral

  • Long A + Short B = on ne parie PAS sur la direction du marché
  • On parie uniquement sur la convergence du spread
  • En théorie, si le marché crash, les pertes sur A sont compensées par les gains sur le short B

⚠️ Règles importantes

  1. Ne JAMAIS hardcoder des tickers dans la logique métier. Tout doit être paramétrable.
  2. Gérer les erreurs proprement : ticker invalide, pas assez de données, API down.
  3. Les graphiques doivent être interactifs (Plotly, pas matplotlib).
  4. Le code doit fonctionner end-to-end : un utilisateur clone le repo, pip install, streamlit run, et ça marche.
  5. Pas de fichiers CSV committés : les données sont fetchées dynamiquement via yfinance.
  6. Inclure un requirements.txt propre avec les versions pinnées.

📝 README.md — Structure attendue

# Pairs Trading — Cointegration Dashboard

One-line description.

## What is Pairs Trading?
2-3 paragraphes simples expliquant le concept (pour un recruteur non-quant).

## Features
- Cointegration testing (Engle-Granger)
- Z-score based signal generation  
- Historical backtesting with performance metrics
- Interactive Streamlit dashboard

## Quick Start
pip install + streamlit run

## Screenshots
(à ajouter après)

## Methodology
Explication du pipeline : data → test → signals → backtest

**IMPORTANT : inclure toutes les formules mathématiques du projet dans cette section :**
- Régression linéaire OLS : Price_A(t) = α + β × Price_B(t) + ε(t)
- Test de cointégration Engle-Granger (ADF sur les résidus)
- Spread : spread(t) = Price_A(t) - β × Price_B(t)
- Z-Score rolling : z(t) = (spread(t) - μ(t)) / σ(t)
- Règles de trading (entry/exit/stop loss basées sur le z-score)
- P&L quotidien : PnL(t) = position(t-1) × Δspread(t) - frais(t)
- Courbe d'equity : equity(t) = Σ PnL(i)
- Métriques : Sharpe Ratio, Max Drawdown, Win Rate, Profit Factor, prix normalisés base 100
Les formules doivent être écrites en LaTeX (syntaxe markdown) pour un rendu propre sur GitHub.

## Results
Tableau des paires testées avec leurs métriques.

## Tech Stack
Python, yfinance, statsmodels, plotly, streamlit

## Disclaimer
"This project is for educational purposes only. Not financial advice."

🎨 Style du dashboard

  • Thème sombre (Streamlit dark mode)
  • Couleurs : vert pour les signaux long, rouge pour short, gris pour neutre
  • Police propre, pas de fioritures
  • Les métriques clés en gros avec st.metric()
  • Layout épuré, professionnel, orienté finance

🚀 Ordre de développement

  1. src/data.py + test rapide dans un script
  2. src/cointegration.py + vérifier avec KO/PEP
  3. src/signals.py + visualiser les signaux
  4. src/backtest.py + calculer les métriques
  5. src/visualization.py + graphiques Plotly
  6. app.py + assembler le dashboard Streamlit
  7. README.md + nettoyage final
  8. Screenshots + push GitHub

Toujours tester chaque module individuellement avant de passer au suivant.