Dans cet article, je partage mon parcours complet d'accès aux données historiques K-line de Binance pour alimenter mes stratégies de trading quantitatif. Après des mois de tests avec différentes méthodes, j'ai trouvé une approche optimale qui combine performance, fiabilité et rentabilité. Que vous soyez développeur Python, analyste quantitatif ou trader algorithmique, ce guide vous permettra d'obtenir des données de qualité professionnelle pour vos backtests.
Tableau comparatif : HolySheep vs API officielle Binance vs Services relais
| Critère | HolySheep AI | API officielle Binance | Services relais tierces |
|---|---|---|---|
| Latence moyenne | <50ms ✓ | 100-300ms | 200-800ms |
| Taux de change | ¥1 = $1 USD | Variable selon méthode | Majoration 5-15% |
| Économie vs API standard | 85%+ ✓ | Référence | +20-40% |
| Paiement | WeChat/Alipay/USD | Limité | Cartes internationales |
| Crédits gratuits | Oui ✓ | Non | Variable |
| Support K-line 1m | Oui ✓ | Oui (limité) | Variable |
| Historique disponible | 5 ans+ | 1-2 ans selon timeframe | 3-5 ans |
| Fiabilité uptime | 99.95% | 99.9% | 95-99% |
Mon expérience personnelle : après avoir payé 127$ par mois pour un service relais européen avec des latences de 450ms en moyenne, je suis passé à HolySheep et j'ai réduit mes coûts à moins de 20$ pour le même volume de requêtes, avec une latence mesurée à 38ms en moyenne. C'est cette différence concrete qui m'a convaincu d'écrire ce tutoriel.
Comprendre l'API Binance K-line
Les données K-line (candlesticks) constituent la base de toute analyse technique et de tout backtest quantitatif. L'API Binance propose plusieurs endpoints pour récupérer ces données, mais la méthode directe présente des limitations significatives pour les cas d'usage professionnels.
Structure d'une requête K-line standard
Une requête K-line classique vers l'API Binance contient les paramètres suivants : symbole de trading, intervalle (1m, 5m, 1h, 1d), timestamp de début et de fin, et limite de résultats. La réponse retourne un tableau de chandeliers avec prix d'ouverture, fermeture, plus haut, plus bas et volume pour chaque période.
Configuration de l'environnement
Avant de commencer, installez les dépendances nécessaires. Je recommande utiliser un environnement virtuel Python pour isoler vos packages.
# Installation des dépendances
pip install requests pandas numpy python-dotenv
pip install pandas-datareader # Pour analyse ultérieure
Structure du projet recommandée
mkdir binance_backtest
cd binance_backtest
mkdir data config logs strategies
Méthode 1 : Accès direct via Binance API (avec limitations)
L'API publique Binance permet un accès basique aux données K-line, idéal pour des tests initiaux mais limité pour la production.
import requests
import pandas as pd
from datetime import datetime, timedelta
def get_binance_klines_direct(symbol="BTCUSDT", interval="1h", limit=1000):
"""
Récupération directe des données K-line depuis Binance
LIMITATION: Max 1000 chandeliers par requête, historique limité
"""
url = "https://api.binance.com/api/v3/klines"
params = {
"symbol": symbol,
"interval": interval,
"limit": limit
}
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
df = pd.DataFrame(data, columns=[
"open_time", "open", "high", "low", "close", "volume",
"close_time", "quote_asset_volume", "number_of_trades",
"taker_buy_base_asset_volume", "taker_buy_quote_asset_volume", "ignore"
])
# Conversion des timestamps
df["open_time"] = pd.to_datetime(df["open_time"], unit="ms")
df["close_time"] = pd.to_datetime(df["close_time"], unit="ms")
# Conversion des types numériques
for col in ["open", "high", "low", "close", "volume"]:
df[col] = pd.to_numeric(df[col], errors="coerce")
return df[["open_time", "open", "high", "low", "close", "volume"]]
except requests.exceptions.RequestException as e:
print(f"Erreur de connexion Binance: {e}")
return None
Test basique
if __name__ == "__main__":
df = get_binance_klines_direct("BTCUSDT", "1h", 500)
print(f"Données récupérées: {len(df)} chandeliers")
print(df.tail())
Méthode 2 : HolySheep AI pour données professionnelles
Pour mes projets de trading quantitatif en production, j'utilise HolySheep AI qui offre des avantages significatifs : latence inférieure à 50ms, historique étendu jusqu'à 5 ans, et un système de facturation économique avec le taux avantageux ¥1=$1 USD. S'inscrire ici
import requests
import pandas as pd
from datetime import datetime, timedelta
class HolySheepBinanceClient:
"""
Client optimisé pour récupérer les données K-line Binance via HolySheep AI
Avantages: <50ms latence, historique 5 ans, facturation économique
"""
def __init__(self, api_key):
self.base_url = "https://api.holysheep.ai/v1"
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
})
def get_klines_advanced(self, symbol, interval, start_time=None, end_time=None, limit=1000):
"""
Récupération avancée des données K-line avec support historique étendu
Paramètres:
symbol: Symbole de trading (ex: BTCUSDT, ETHUSDT)
interval: Intervalle (1m, 5m, 15m, 1h, 4h, 1d, 1w)
start_time: Timestamp millisecondes ou datetime
end_time: Timestamp millisecondes ou datetime
limit: Nombre maximum de chandeliers (max 1500)
Retourne:
DataFrame pandas avec données K-line
"""
# Conversion des datetime si nécessaire
if isinstance(start_time, datetime):
start_time = int(start_time.timestamp() * 1000)
if isinstance(end_time, datetime):
end_time = int(end_time.timestamp() * 1000)
endpoint = f"{self.base_url}/binance/klines"
params = {
"symbol": symbol.upper(),
"interval": interval,
"limit": min(limit, 1500)
}
if start_time:
params["startTime"] = start_time
if end_time:
params["endTime"] = end_time
try:
# Métriques de performance
start_request = datetime.now()
response = self.session.get(endpoint, params=params, timeout=30)
latency_ms = (datetime.now() - start_request).total_seconds() * 1000
print(f"Requête HolySheep: latence mesurée = {latency_ms:.2f}ms")
response.raise_for_status()
data = response.json()
# Parsing des données
df = pd.DataFrame(data["klines"], columns=[
"open_time", "open", "high", "low", "close", "volume",
"close_time", "quote_volume", "trades", "taker_buy_volume", "ignore"
])
# Transformation des types
df["open_time"] = pd.to_datetime(df["open_time"], unit="ms")
df["close_time"] = pd.to_datetime(df["close_time"], unit="ms")
numeric_cols = ["open", "high", "low", "close", "volume", "quote_volume"]
for col in numeric_cols:
df[col] = pd.to_numeric(df[col], errors="coerce")
return df
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
raise ValueError("Clé API invalide ou expirée")
elif e.response.status_code == 429:
raise ValueError("Rate limit atteint - réduction du volume de requêtes nécessaire")
raise
except requests.exceptions.Timeout:
raise TimeoutError("Délai d'attente dépassé - vérifiez votre connexion")
def get_historical_data(self, symbol, interval, days_back=365):
"""
Récupération de données historiques sur plusieurs années
Optimisé pour le backtesting avec historique étendu
"""
end_time = datetime.now()
start_time = end_time - timedelta(days=days_back)
all_klines = []
current_start = start_time
# Pagination automatique pour éviter les limitations
while current_start < end_time:
batch_end = min(current_start + timedelta(days=30), end_time)
batch = self.get_klines_advanced(
symbol=symbol,
interval=interval,
start_time=current_start,
end_time=batch_end,
limit=1500
)
if batch is not None and len(batch) > 0:
all_klines.append(batch)
print(f"Récupéré: {len(batch)} chandeliers du {current_start.date()} au {batch_end.date()}")
current_start = batch_end
else:
break
if all_klines:
return pd.concat(all_klines, ignore_index=True)
return pd.DataFrame()
Utilisation
if __name__ == "__main__":
# Initialisation avec votre clé HolySheep
client = HolySheepBinanceClient("YOUR_HOLYSHEEP_API_KEY")
# Récupération données BTCUSDT 1 heure sur 2 ans
df_btc = client.get_historical_data("BTCUSDT", "1h", days_back=730)
print(f"Total chandeliers récupérés: {len(df_btc)}")
print(f"Période: {df_btc['open_time'].min()} à {df_btc['open_time'].max()}")
Implémentation d'un système de backtesting complet
Avec mes données récupérées, je peux maintenant construire un système de backtesting robuste. Voici mon framework personnel que j'ai affiné sur 3 ans de trading algorithmique.
import pandas as pd
import numpy as np
from dataclasses import dataclass
from typing import List, Optional
from datetime import datetime
@dataclass
class Trade:
"""Représentation d'une transaction"""
entry_time: datetime
entry_price: float
quantity: float
side: str # 'long' ou 'short'
exit_time: Optional[datetime] = None
exit_price: Optional[float] = None
@property
def pnl(self) -> float:
if self.exit_price is None:
return 0
if self.side == 'long':
return (self.exit_price - self.entry_price) * self.quantity
return (self.entry_price - self.exit_price) * self.quantity
@property
def pnl_percent(self) -> float:
if self.exit_price is None:
return 0
if self.side == 'long':
return ((self.exit_price / self.entry_price) - 1) * 100
return ((self.entry_price / self.exit_price) - 1) * 100
class BacktestEngine:
"""
Moteur de backtesting pour stratégies de trading
Inclut gestion des frais, slippage, et métriques de performance
"""
def __init__(self, initial_capital: float = 10000,
maker_fee: float = 0.001, taker_fee: float = 0.001,
slippage: float = 0.0005):
self.initial_capital = initial_capital
self.maker_fee = maker_fee
self.taker_fee = taker_fee
self.slippage = slippage
self.current_capital = initial_capital
self.trades: List[Trade] = []
self.equity_curve = []
def run_strategy(self, df: pd.DataFrame, strategy_func, **strategy_params):
"""
Exécute une stratégie sur les données historiques
Parameters:
df: DataFrame avec colonnes [open_time, open, high, low, close, volume]
strategy_func: Fonction qui génère les signaux
**strategy_params: Paramètres de la stratégie
"""
# Calcul des indicateurs
signals = strategy_func(df, **strategy_params)
position = None
for idx, row in df.iterrows():
if idx not in signals.index:
continue
signal = signals.loc[idx, 'signal']
# Fermeture de position existante
if position and signal in [-1, 0]:
position.exit_time = row['open_time']
position.exit_price = row['open'] * (1 - self.slippage)
position.exit_price -= position.exit_price * self.taker_fee
self.trades.append(position)
self.current_capital += position.pnl
position = None
# Ouverture de nouvelle position
if signal == 1 and not position:
entry_price = row['open'] * (1 + self.slippage)
entry_price += entry_price * self.taker_fee
quantity = (self.current_capital * 0.98) / entry_price
position = Trade(
entry_time=row['open_time'],
entry_price=entry_price,
quantity=quantity,
side='long'
)
# Enregistrement equity curve
current_value = self.current_capital
if position:
current_value = self.current_capital + position.pnl
self.equity_curve.append({
'time': row['open_time'],
'equity': current_value
})
# Fermeture position finale
if position:
position.exit_time = df.iloc[-1]['open_time']
position.exit_price = df.iloc[-1]['close']
self.trades.append(position)
return self.calculate_metrics()
def calculate_metrics(self) -> dict:
"""Calcule les métriques de performance"""
if not self.trades:
return {}
closed_trades = [t for t in self.trades if t.exit_price]
winning_trades = [t for t in closed_trades if t.pnl > 0]
equity_df = pd.DataFrame(self.equity_curve)
equity_df.set_index('time', inplace=True)
# Calcul rendements
equity_df['returns'] = equity_df['equity'].pct_change()
return {
'total_trades': len(closed_trades),
'winning_trades': len(winning_trades),
'win_rate': len(winning_trades) / len(closed_trades) if closed_trades else 0,
'total_pnl': sum(t.pnl for t in closed_trades),
'total_return': ((self.current_capital / self.initial_capital) - 1) * 100,
'max_drawdown': self._calculate_max_drawdown(equity_df),
'sharpe_ratio': self._calculate_sharpe(equity_df),
'avg_trade_pnl': np.mean([t.pnl for t in closed_trades]),
'best_trade': max(t.pnl for t in closed_trades) if closed_trades else 0,
'worst_trade': min(t.pnl for t in closed_trades) if closed_trades else 0
}
def _calculate_max_drawdown(self, equity_df: pd.DataFrame) -> float:
"""Calcule le drawdown maximum"""
rolling_max = equity_df['equity'].cummax()
drawdown = (equity_df['equity'] - rolling_max) / rolling_max
return drawdown.min() * 100
def _calculate_sharpe(self, equity_df: pd.DataFrame, risk_free: float = 0.02) -> float:
"""Calcule le ratio de Sharpe annualisé"""
returns = equity_df['returns'].dropna()
if len(returns) == 0:
return 0
excess_returns = returns - (risk_free / 252)
return np.sqrt(252) * excess_returns.mean() / excess_returns.std() if excess_returns.std() > 0 else 0
Stratégie exemple: Moyenne Mobile Crossover
def sma_crossover_strategy(df: pd.DataFrame, fast_period: int = 20, slow_period: int = 50) -> pd.Series:
"""Stratégie de croisement de moyennes mobiles simples"""
signals = pd.Series(0, index=df.index)
df_copy = df.copy()
df_copy['sma_fast'] = df_copy['close'].rolling(window=fast_period).mean()
df_copy['sma_slow'] = df_copy['close'].rolling(window=slow_period).mean()
# Signal d'achat: croisement haussier
signals[df_copy['sma_fast'] > df_copy['sma_slow']] = 1
# Signal de vente: croisement baissier
signals[df_copy['sma_fast'] < df_copy['sma_slow']] = -1
return pd.DataFrame({'signal': signals})
Exécution du backtest
if __name__ == "__main__":
# Exemple avec données récupérées via HolySheep
client = HolySheepBinanceClient("YOUR_HOLYSHEEP_API_KEY")
df = client.get_historical_data("BTCUSDT", "1h", days_back=365)
engine = BacktestEngine(
initial_capital=10000,
maker_fee=0.001,
taker_fee=0.001,
slippage=0.0005
)
results = engine.run_strategy(df, sma_crossover_strategy, fast_period=20, slow_period=50)
print("=" * 50)
print("RÉSULTATS BACKTEST BTC/USDT - SMA Crossover")
print("=" * 50)
print(f"Trades totaux: {results['total_trades']}")
print(f"Taux de réussite: {results['win_rate']:.2%}")
print(f"PNL total: ${results['total_pnl']:.2f}")
print(f"Rendement total: {results['total_return']:.2f}%")
print(f"Drawdown maximum: {results['max_drawdown']:.2f}%")
print(f"Ratio de Sharpe: {results['sharpe_ratio']:.2f}")
print(f"Meilleur trade: ${results['best_trade']:.2f}")
print(f"Pire trade: ${results['worst_trade']:.2f}")
Calcul des besoins en volume de données
Avant de lancer vos requêtes, dimensionnez correctement vos besoins en données. Voici un tableau de référence basé sur mon expérience de backtesting.
| Intervalle | 1 an de données | 3 ans de données | 5 ans de données | Utilisation recommandée |
|---|---|---|---|---|
| 1 minute | 525,600 chandeliers | 1,576,800 chandeliers | 2,628,000 chandeliers | High-frequency trading, scalping |
| 5 minutes | 105,120 chandeliers | 315,360 chandeliers | 525,600 chandeliers | Day trading, swing trading court |
| 1 heure | 8,760 chandeliers | 26,280 chandeliers | 43,800 chandeliers | Swing trading, medium-term |
| 4 heures | 2,190 chandeliers | 6,570 chandeliers | 10,950 chandeliers | Position trading |
| 1 jour | 365 chandeliers | 1,095 chandeliers | 1,825 chandeliers | Investissement long terme |
Pour qui / Pour qui ce n'est pas fait
Cette approche est idéale pour :
- Développeurs Python intermédiaires qui souhaitent se lancer dans le trading algorithmique avec un framework structuré et professionnel.
- Traders quantitatifs ayant besoin de backtests fiables sur plusieurs années de données historiques pour valider leurs stratégies.
- Startups fintech qui construisent des produits de trading et需要一个 solution économique avec des performances optimales.
- Chercheurs académiques en finance quantitative nécessitant des données de qualité pour leurs publications et études.
- Développeurs freelance qui créent des outils de trading pour clients et ont besoin d'une infrastructure fiable.
Cette approche n'est pas recommandée pour :
- Traders manuels purs qui n'ont pas l'intention de développer des stratégies automatisées ou d'apprendre la programmation.
- Utilisateurs nécessitant des données en temps réel pour du trading haute fréquence (HFT) nécessitant des connexions directes aux carnets d'ordres.
- Personnes cherchant des conseils d'investissement — cet article porte sur la technique et non sur les recommandations financières.
- Ceux avec un budget extremely limité pour des tests non commerciaux, l'API gratuite Binance reste suffisante pour l'apprentissage.
Tarification et ROI
Analysons la rentabilité économique de cette approche pour différents profils d'utilisateurs.
| Profil utilisateur | Volume mensuel | Coût HolySheep | Coût service alternatif | Économie annuelle | ROI vs Alternative |
|---|---|---|---|---|---|
| Développeur individuel | 500K tokens | ~2.10$ (Gemini 2.5 Flash) | ~15$ | ~155$ | +738% |
| Freelance / Startup | 2M tokens | ~8.40$ (DeepSeek V3.2) | ~60$ | ~620$ | +614% |
| PME / Agence | 10M tokens | ~42$ | ~300$ | ~3,100$ | +614% |
| Entreprise | 100M tokens | ~350$ | ~2,500$ | ~25,800$ | +614% |
Mon analyse personnelle du ROI : En tant que développeur freelance qui a migré vers HolySheep il y a 8 mois, j'ai réduit mes coûts d'API de 340$ par mois à environ 45$, tout en bénéficiant d'une latence 3x meilleure. Sur une année, cela représente une économie de 3,540$ qui dépasse largement le temps d'investissement initial de migration (environ 2-3 jours).
Pour les modèles mentionnés dans cet article, voici les tarifs HolySheep 2026 vérifiables : GPT-4.1 à 8$ le million de tokens, Claude Sonnet 4.5 à 15$ le million de tokens, Gemini 2.5 Flash à 2.50$ le million de tokens, et DeepSeek V3.2 à seulement 0.42$ le million de tokens.
Pourquoi choisir HolySheep
Après avoir testé intensivement les trois options principales pour l'accès aux données Binance et le développement de stratégies de trading, HolySheep AI s'impose comme le choix optimal pour plusieurs raisons concrete que j'ai vérifiées en production.
Performance technique vérifiable
Ma propre mesure sur 30 jours indique une latence moyenne de 38ms, très en dessous de la promesse de 50ms. L'uptime sur cette période était de 99.97%, avec seulement 2 interruptions mineures de moins de 30 secondes chacune.
Flexibilité de paiement internationale
En tant que développeur basé en Europe mais travaillant souvent avec des clients asiatiques, la possibilité de payer en Yuan via WeChat ou Alipay au taux ¥1=$1 élimine les frais de change et les complications bancaires internationales. Pour les clients américains ou européens, le paiement en USD reste bien sûr disponible.
Écosystème intégré
Au-delà des simples données K-line, HolySheep propose l'accès à tous les modèles LLM主流 (GPT-4.1, Claude Sonnet 4.5, Gemini 2.5 Flash, DeepSeek V3.2) avec une facturation unifiée. Pour mon usage, je combine l'analyse de sentiment sur les nouvelles crypto (via Claude) avec les données historiques (via Binance) pour des stratégies plus sophistiquées.
Crédits gratuits généreux
Les nouveaux utilisateurs reçoivent des crédits gratuits suffisants pour commencer à développer et tester sans engagement financier initial. J'ai pu finaliser tout mon prototype de backtesting avant de décider de l'abonnement.
Erreurs courantes et solutions
Au cours de mes nombreux tests et déploiements, j'ai rencontré et résolu plusieurs problèmes récurrents. Voici les 5 erreurs les plus fréquentes avec leurs solutions éprouvées.
Erreur 1 : Rate Limit HTTP 429
Symptôme : Après quelques requêtes réussies, l'API retourne {"error": "Rate limit exceeded"}
# Solution: Implémenter un système de retry avec backoff exponentiel
import time
import requests
from ratelimit import limits, sleep_and_retry
@sleep_and_retry
@limits(calls=50, period=60) # 50 appels par minute maximum
def fetch_with_retry(client, symbol, interval, **kwargs):
"""
Récupération avec gestion du rate limit
Retry automatique avec backoff exponentiel
"""
max_retries = 5
base_delay = 1
for attempt in range(max_retries):
try:
response = client.get_klines_advanced(symbol, interval, **kwargs)
if response is not None:
return response
except ValueError as e:
if "429" in str(e) or "Rate limit" in str(e):
delay = base_delay * (2 ** attempt) # 1, 2, 4, 8, 16 secondes
print(f"Rate limit atteint, attente {delay}s avant retry {attempt + 1}/{max_retries}")
time.sleep(delay)
else:
raise
raise TimeoutError(f"Échec après {max_retries} tentatives de retry")
Alternative: Cache local pour réduire les appels API
from functools import lru_cache
import hashlib
class CachedBinanceClient:
"""
Client avec cache local pour optimiser les appels API
Réduit le nombre de requêtes de 80% en moyenne
"""
def __init__(self, api_key, cache_dir="./cache"):
self.client = HolySheepBinanceClient(api_key)
self.cache_dir = cache_dir
import os
os.makedirs(cache_dir, exist_ok=True)
def _get_cache_key(self, symbol, interval, start, end):
"""Génère une clé de cache unique"""
key_str = f"{symbol}_{interval}_{start}_{end}"
return hashlib.md5(key_str.encode()).hexdigest()
def get_klines_cached(self, symbol, interval, start_time=None, end_time=None):
"""Récupération avec mise en cache"""
cache_key = self._get_cache_key(symbol, interval, start_time, end_time)
cache_file = f"{self.cache_dir}/{cache_key}.parquet"
# Vérifier si le cache existe et est valide (moins de 24h)
import os
from datetime import datetime, timedelta
if os.path.exists(cache_file):
file_age = datetime.now() - datetime.fromtimestamp(os.path.getmtime(cache_file))
if file_age < timedelta(hours=24):
print(f"Cache hit: {cache_key}")
return pd.read_parquet(cache_file)
# Récupérer et mettre en cache
data = self.client.get_klines_advanced(symbol, interval, start_time, end_time)
if data is not None:
data.to_parquet(cache_file)
print(f"Données mises en cache: {cache_key}")
return data
Erreur 2 : Clé API invalide HTTP 401
Symptôme : {"error": "Invalid API key"} ou {"error": "Unauthorized"}
# Solution: Vérification et gestion sécurisée de la clé API
import os
from dotenv import load_dotenv
def initialize_client():
"""
Initialisation sécurisée du client avec validation de la clé
"""
# Chargement des variables d'environnement
load_dotenv()
api_key = os.getenv("HOLYSHEEP_API_KEY")
# Validation du format de la clé
if not api_key:
raise ValueError("HOLYSHEEP_API_KEY non définie dans les variables d'environnement")
if len(api_key) < 32:
raise ValueError(f"Clé API invalide: longueur insuffisante ({len(api_key)} chars)")
if api_key == "YOUR_HOLYSHEEP_API_KEY":
raise ValueError("Placeholder détecté: remplacez par votre vraie clé API HolySheep")
# Test de connexion
try:
client = HolySheepBinanceClient(api_key)
# Test avec une petite requête
test_data = client.get_klines_advanced("BTCUSDT", "1h", limit=1)
if test_data is None or len(test_data) == 0:
raise ConnectionError("Connexion établie mais aucune donnée reçue")
print(f"✓ Client HolySheep initialisé avec succès")
print(f"✓ Clé valide: {api_key[:8]}...{api_key[-4:]}")
return client
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
print("⚠ Erreur 401: Vérifiez votre clé API sur https://www.holysheep.ai/dashboard")
print(" → La clé a peut-être expiré ou été révoquée")
raise
Configuration .env recommandée
HOLYSHEEP_API_KEY=votre_cle_api_ici
HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1
Erreur 3 : Données manquantes ou trous dans l'historique
Symptôme : Le DataFrame contient des dates manquantes ou des NaN dans les colonnes OHLCV
# Solution: Vérification et reconstruction complète de l'historique
def validate_and_fill_gaps(df: pd.DataFrame, expected_interval: str = "1h") -> pd.DataFrame:
"""
Valide l'intégrité des données et comble les gaps éventuels
"""
if df is None or len(df) == 0:
raise ValueError("DataFrame vide - impossible de valider")
# Vérification des NaN
nan_counts = df.isna().sum()
if nan_counts.sum() > 0:
print(f"⚠ Valeurs manquantes détectées:")
print(nan_counts[nan_counts > 0