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.
Style de code attendu :
x, df2, temp)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
coint())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é).
"""
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).
"""
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
"""
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
"""
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) :
Sidebar (paramètres ajustables) :
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.
spread(t) = price_A(t) - beta * price_B(t)
beta vient de la régression linéaire (OLS) de A sur Bz(t) = (spread(t) - mean(spread, window)) / std(spread, window)
pip install, streamlit run, et ça marche.# 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."
st.metric()src/data.py + test rapide dans un scriptsrc/cointegration.py + vérifier avec KO/PEPsrc/signals.py + visualiser les signauxsrc/backtest.py + calculer les métriquessrc/visualization.py + graphiques Plotlyapp.py + assembler le dashboard StreamlitToujours tester chaque module individuellement avant de passer au suivant.