# 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`) ```python 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`) ```python 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`) ```python 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`) ```python 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 ```markdown # 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.**