import pandas as pd
import yfinance as yf
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é).
Parameters
----------
ticker_a : str
Premier ticker (ex: "KO").
ticker_b : str
Second ticker (ex: "PEP").
period : str
Période historique à récupérer (ex: "3y", "5y", "10y").
Returns
-------
pd.DataFrame
DataFrame avec colonnes [ticker_a, ticker_b] et index DatetimeIndex.
Raises
------
ValueError
Si un ticker est invalide ou si les données sont insuffisantes.
"""
ticker_a = ticker_a.strip().upper()
ticker_b = ticker_b.strip().upper()
# Téléchargement des données via yfinance
donnees_brutes = yf.download(
tickers=[ticker_a, ticker_b],
period=period,
auto_adjust=True,
progress=False,
)
# Vérification que le téléchargement a fonctionné
if donnees_brutes.empty:
raise ValueError(
f"Aucune donnée récupérée pour {ticker_a} et/ou {ticker_b}. "
"Vérifiez que les tickers sont valides."
)
# Extraction des prix de clôture
prix_cloture = donnees_brutes["Close"]
# Vérification que les deux tickers sont présents dans les colonnes
colonnes_presentes = prix_cloture.columns.tolist()
for ticker in [ticker_a, ticker_b]:
if ticker not in colonnes_presentes:
raise ValueError(
f"Le ticker '{ticker}' n'a retourné aucune donnée. "
"Vérifiez qu'il est valide sur Yahoo Finance."
)
# Sélection et renommage des colonnes
prix_paire = prix_cloture[[ticker_a, ticker_b]].copy()
# Suppression des lignes avec des NaN (jours où un seul ticker a coté)
nb_lignes_avant = len(prix_paire)
prix_paire = prix_paire.dropna()
nb_lignes_supprimees = nb_lignes_avant - len(prix_paire)
if nb_lignes_supprimees > 0:
print(
f"Info : {nb_lignes_supprimees} lignes supprimées (données manquantes)."
)
# Vérification qu'il reste assez de données pour une analyse significative
nb_jours_minimum = 252 # ~1 an de trading
if len(prix_paire) < nb_jours_minimum:
raise ValueError(
f"Seulement {len(prix_paire)} jours de données disponibles "
f"(minimum requis : {nb_jours_minimum}). "
"Essayez une période plus longue ou vérifiez les tickers."
)
return prix_paire