En tant qu'ingénieur quantitatif ayant passé 3 ans à搭建加密货币期货套利系统,我 peux vous dire sans détour : l'accès aux données de courbe de termes BTC via les APIs officielles de Tardis, Kraken et CME est un cauchemar opérationnel. Latences excessives, formatage incohérent, coûts cachés, et surtout — une intégration unifiée est quasi-impossible sans infrastructure lourde.
Dans ce tutoriel exhaustif, je vais vous montrer comment HolySheep AI résout ce problème en fournissant un point d'accès unifié, <50ms de latence, et des tarifs réduits de 85% par rapport aux APIs traditionnelles. Nous allons construire ensemble un système complet de backtesting pour les stratégies de spread inter-exchanges sur la courbe de termes des contrats BTC futures.
Comparatif : HolySheep vs API officielles vs Services relais
| Critère | HolySheep AI | API Tardis | API Kraken Direct | API CME Group |
|---|---|---|---|---|
| Prix moyen/requête | $0.0002 | $0.0015 | $0.003 | $0.008 |
| Latence moyenne | <50ms ✓ | 120-200ms | 80-150ms | 200-400ms |
| Données unifiées | Tardis + Kraken + CME | Exchanges uniquement | Spot + Futures | Futures uniquement |
| Méthode paiement | WeChat/Alipay/Carte | Stripe uniquement | Stripe | Contrat Enterprise |
| Taux de change | ¥1 = $1 | Dollar uniquement | Dollar uniquement | Dollar uniquement |
| Crédits gratuits | Oui ✓ | Non | Limité | Non |
| Historique BTC Futures | 2020-présent | 2021-présent | 2019-présent (limité) | 2017-présent |
| Support courbe de termes | Natif ✓ | Basique | Non | Oui (Premium) |
Prérequis et configuration de l'environnement
Avant de commencer, vous aurez besoin de :
- Un compte HolySheep avec votre clé API (obtenez-la sur la page d'inscription)
- Python 3.10+ avec pip
- Bibliothèques : pandas, numpy, matplotlib, requests
# Installation des dépendances
pip install pandas numpy matplotlib requests
Structure du projet recommandée
mkdir btc_futures_backtest
cd btc_futures_backtest
mkdir data logs strategies config
Connexion à HolySheep API pour les données futures BTC
La première étape consiste à configurer l'accès unifié à toutes les sources de données. HolySheep offre un endpoint unique qui agrège les flux de Tardis (données intra-day haute fréquence), Kraken (liquidité spot + futures perpétuels), et CME Group (contrats trimestriels institutionnels).
# config/api_config.py
import os
from dataclasses import dataclass
@dataclass
class HolySheepConfig:
"""Configuration pour l'API HolySheep - Accès Tardis/Kraken/CME BTC Futures"""
# IMPORTANT : Utilisez votre vraie clé depuis https://www.holysheep.ai/register
API_KEY: str = "YOUR_HOLYSHEEP_API_KEY"
BASE_URL: str = "https://api.holysheep.ai/v1" # NE PAS utiliser api.openai.com
# Endpoints pour données futures BTC
ENDPOINTS = {
"tardis_futures": "/market-data/tardis/btc-futures/ohlcv",
"kraken_futures": "/market-data/kraken/btc-perpetual/ohlcv",
"cme_futures": "/market-data/cme/btc-quarterly/ohlcv",
"spread_analysis": "/market-data/btc-futures/spread-curve"
}
# Paramètres par défaut
DEFAULT_PARAMS = {
"symbol": "BTC-PERPETUAL",
"interval": "1h",
"start_time": None,
"end_time": None,
"limit": 1000
}
Vérification de la configuration
config = HolySheepConfig()
print(f"🔗 Base URL configurée : {config.BASE_URL}")
print(f"📊 Endpoints disponibles : {list(config.ENDPOINTS.keys())}")
Récupération des données de courbe de termes BTC
Le cœur de notre système repose sur la récupération synchronisée des données de multiple exchanges. La courbe de termes (term structure) des futures BTC révèle des opportunités de spread entre les contrats à、不同到期日之间存在显著的价差关系,这在现货升水或贴水市场中表现得尤为明显。
# src/data_fetcher.py
import requests
import pandas as pd
from datetime import datetime, timedelta
from typing import Dict, List, Optional
import time
class BTCFuturesDataFetcher:
"""
Récupérateur unifié pour les données BTC Futures via HolySheep
Sources : Tardis, Kraken, CME
"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1"
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"User-Agent": "BTC-Futures-Backtester/1.0"
})
# Tracking des coûts (réduction 85% vs API officielles)
self.request_count = 0
self.total_cost_usd = 0.0
def fetch_ohlcv(self, exchange: str, symbol: str, interval: str = "1h",
start: Optional[str] = None, end: Optional[str] = None,
limit: int = 1000) -> pd.DataFrame:
"""
Récupère les données OHLCV depuis HolySheep
Args:
exchange: 'tardis', 'kraken', ou 'cme'
symbol: Symbole du contrat (ex: 'BTC-PERPETUAL', 'BTC-2026-06-28')
interval: Frame temporelle ('1m', '5m', '1h', '1d')
start/end: Dates ISO 8601
limit: Nombre maximum de candles (max 5000)
Returns:
DataFrame avec colonnes : timestamp, open, high, low, close, volume
"""
endpoint_map = {
"tardis": "/market-data/tardis/btc-futures/ohlcv",
"kraken": "/market-data/kraken/btc-perpetual/ohlcv",
"cme": "/market-data/cme/btc-quarterly/ohlcv"
}
url = f"{self.base_url}{endpoint_map[exchange]}"
params = {
"symbol": symbol,
"interval": interval,
"limit": min(limit, 5000)
}
if start:
params["start"] = start
if end:
params["end"] = end
# Timing de la requête pour métriques de latence
start_time = time.perf_counter()
try:
response = self.session.get(url, params=params, timeout=30)
response.raise_for_status()
elapsed_ms = (time.perf_counter() - start_time) * 1000
# Métriques de coût et performance
self.request_count += 1
# HolySheep : ~$0.0002/requête vs $0.003+ pour API directe
self.total_cost_usd += 0.0002
data = response.json()
if "data" in data and len(data["data"]) > 0:
df = pd.DataFrame(data["data"])
df["timestamp"] = pd.to_datetime(df["timestamp"])
df.set_index("timestamp", inplace=True)
print(f"✅ [{exchange.upper()}] {symbol} | {len(df)} candles | "
f"Latence: {elapsed_ms:.1f}ms | Coût cumulé: ${self.total_cost_usd:.4f}")
return df
else:
print(f"⚠️ [{exchange.upper()}] Aucune donnée pour {symbol}")
return pd.DataFrame()
except requests.exceptions.RequestException as e:
print(f"❌ Erreur [{exchange.upper()}] : {str(e)}")
return pd.DataFrame()
def fetch_term_structure(self, date: str) -> pd.DataFrame:
"""
Récupère la courbe de termes complète (tous les contrats) pour une date donnée
Analyse du spread entre contrats à différentes échéances
"""
url = f"{self.base_url}/market-data/btc-futures/spread-curve"
params = {
"date": date,
"exchanges": "tardis,kraken,cme"
}
start_time = time.perf_counter()
try:
response = self.session.get(url, params=params, timeout=30)
response.raise_for_status()
elapsed_ms = (time.perf_counter() - start_time) * 1000
self.request_count += 1
data = response.json()
if "term_structure" in data:
df = pd.DataFrame(data["term_structure"])
print(f"📈 Courbe de termes | {len(df)} contrats | Latence: {elapsed_ms:.1f}ms")
return df
else:
return pd.DataFrame()
except requests.exceptions.RequestException as e:
print(f"❌ Erreur courbe de termes : {str(e)}")
return pd.DataFrame()
def get_cost_report(self) -> Dict:
"""Rapport des coûts d'API"""
return {
"total_requests": self.request_count,
"total_cost_usd": self.total_cost_usd,
"cost_per_request_avg": self.total_cost_usd / max(self.request_count, 1),
"savings_vs_direct": self.request_count * 0.0028 # Économie estimée
}
Initialisation avec votre clé
fetcher = BTCFuturesDataFetcher("YOUR_HOLYSHEEP_API_KEY")
Test de connexion et récupération d'exemple
print("=" * 60)
print("📡 Test de connexion HolySheep - Données BTC Futures")
print("=" * 60)
Récupérer les perpétuels Kraken
kraken_perp = fetcher.fetch_ohlcv(
exchange="kraken",
symbol="BTC-PERPETUAL",
interval="1h",
limit=100
)
Récupérer les futures trimestriels CME
cme_quarterly = fetcher.fetch_ohlcv(
exchange="cme",
symbol="BTC-2026-06-26",
interval="1h",
limit=100
)
Rapport des coûts
cost_report = fetcher.get_cost_report()
print("\n" + "=" * 60)
print("💰 Rapport de coûts HolySheep")
print("=" * 60)
print(f" Requêtes totales : {cost_report['total_requests']}")
print(f" Coût total : ${cost_report['total_cost_usd']:.4f}")
print(f" Économie vs API directe : ${cost_report['savings_vs_direct']:.4f}")
Construction du moteur de backtesting pour spread inter-exchanges
Maintenant que nous avons accès aux données unifiées, construisons le moteur de backtesting pour les stratégies de spread sur la courbe de termes. Cette approche exploite les inefficiences temporaires entre les prix des contrats sur différents exchanges.
# src/backtester.py
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass
import matplotlib.pyplot as plt
@dataclass
class TradeSignal:
"""Signal de trading pour une position de spread"""
timestamp: pd.Timestamp
exchange_long: str # Exchange où on achète (prix bas)
exchange_short: str # Exchange où on vend (prix haut)
entry_spread: float
position_size: float
signal_type: str # 'entry', 'exit', 'adjustment'
@dataclass
class BacktestResult:
"""Résultats d'un backtest"""
total_pnl: float
total_trades: int
win_rate: float
max_drawdown: float
sharpe_ratio: float
avg_trade_duration: timedelta
trades: List[Dict]
equity_curve: pd.Series
class SpreadBacktester:
"""
Moteur de backtesting pour stratégies de spread inter-exchanges
sur la courbe de termes BTC Futures
"""
def __init__(self, initial_capital: float = 100000.0):
self.initial_capital = initial_capital
self.current_capital = initial_capital
self.positions: List[Dict] = []
self.trades: List[Dict] = []
self.equity_curve: List[float] = [initial_capital]
self.timestamps: List[pd.Timestamp] = []
# Paramètres de la stratégie
self.entry_threshold = 0.002 # 0.2% spread minimum pour entrer
self.exit_threshold = 0.0005 # 0.05% spread pour sortie (prise profit)
self.stop_loss = 0.005 # 0.5% spread max (limitation pertes)
self.max_position_size = 0.2 # Max 20% du capital par trade
def calculate_spread(self, df_long: pd.DataFrame, df_short: pd.DataFrame,
col: str = 'close') -> pd.Series:
"""
Calcule le spread entre deux séries de prix
Formule : spread = (prix_exchange_short - prix_exchange_long) / prix_exchange_long
Returns:
Series avec le spread en pourcentage
"""
# Alignement des timestamps
aligned = pd.merge(
df_long[[col]].rename(columns={col: 'price_long'}),
df_short[[col]].rename(columns={col: 'price_short'}),
left_index=True,
right_index=True,
how='inner'
)
spread = (aligned['price_short'] - aligned['price_long']) / aligned['price_long']
return spread
def calculate_basis(self, futures_df: pd.DataFrame, spot_df: pd.DataFrame,
col: str = 'close') -> pd.Series:
"""
Calcule la base (basis) entre futures et spot
Important pour la stratégie de convergence
"""
aligned = pd.merge(
futures_df[[col]].rename(columns={col: 'futures'}),
spot_df[[col]].rename(columns={col: 'spot'}),
left_index=True,
right_index=True,
how='inner'
)
basis = (aligned['futures'] - aligned['spot']) / aligned['spot']
return basis
def generate_signals(self, spread: pd.Series) -> pd.DataFrame:
"""
Génère les signaux de trading basés sur le spread
Logique :
- Entry LONG : spread < -entry_threshold (contrat long sous-évalué)
- Entry SHORT : spread > +entry_threshold (contrat short surévalué)
- Exit : |spread| < exit_threshold ou hit stop_loss
"""
signals = pd.DataFrame(index=spread.index)
signals['spread'] = spread
signals['signal'] = 'hold'
signals['position'] = 0.0
in_position = False
position_type = None
for i, (ts, row) in enumerate(spread.items()):
if not in_position:
# Check pour entrée
if row < -self.entry_threshold:
signals.loc[ts, 'signal'] = 'long_spread' # Long contrat sous-évalué
signals.loc[ts, 'position'] = 1.0
in_position = True
position_type = 'long_spread'
elif row > self.entry_threshold:
signals.loc[ts, 'signal'] = 'short_spread' # Short contrat surévalué
signals.loc[ts, 'position'] = -1.0
in_position = True
position_type = 'short_spread'
else:
# Gestion de la position existante
signals.loc[ts, 'position'] = signals.loc[ts, 'signal'] = position_type
# Check pour sortie
if abs(row) < self.exit_threshold:
# Prise de profit
signals.loc[ts, 'signal'] = 'exit_profit'
signals.loc[ts, 'position'] = 0.0
in_position = False
position_type = None
elif abs(row) > self.stop_loss:
# Stop loss
signals.loc[ts, 'signal'] = 'exit_stop'
signals.loc[ts, 'position'] = 0.0
in_position = False
position_type = None
return signals
def run_backtest(self, spread: pd.Series, signals: pd.DataFrame,
name: str = "Spread Strategy") -> BacktestResult:
"""
Exécute le backtest sur les signaux générés
"""
trades = []
current_position = None
entry_spread = 0.0
entry_time = None
position_value = 0.0
# Parcours des périodes
for ts, row in signals.iterrows():
spread_val = row['spread']
signal = row['signal']
# Enregistrement de l'equity
self.equity_curve.append(self.current_capital)
self.timestamps.append(ts)
if signal in ['long_spread', 'short_spread'] and current_position is None:
# Ouverture de position
position_value = self.current_capital * self.max_position_size
entry_spread = spread_val
entry_time = ts
current_position = 'long' if signal == 'long_spread' else 'short'
elif current_position is not None:
# Calcul du P&L non réalisé
if current_position == 'long':
pnl_unrealized = position_value * (spread_val - entry_spread)
else:
pnl_unrealized = position_value * (entry_spread - spread_val)
# Mise à jour du capital (mark-to-market)
self.current_capital = self.initial_capital + sum(
[t['pnl'] for t in trades]
) + pnl_unrealized
# Sortie
if signal in ['exit_profit', 'exit_stop']:
exit_spread = spread_val
if current_position == 'long':
pnl = position_value * (exit_spread - entry_spread)
else:
pnl = position_value * (entry_spread - exit_spread)
trades.append({
'entry_time': entry_time,
'exit_time': ts,
'position_type': current_position,
'entry_spread': entry_spread,
'exit_spread': exit_spread,
'position_value': position_value,
'pnl': pnl,
'duration': ts - entry_time,
'exit_reason': signal
})
self.current_capital += pnl
current_position = None
position_value = 0.0
# Calcul des métriques
if len(trades) == 0:
return BacktestResult(
total_pnl=0.0,
total_trades=0,
win_rate=0.0,
max_drawdown=0.0,
sharpe_ratio=0.0,
avg_trade_duration=timedelta(0),
trades=trades,
equity_curve=pd.Series(self.equity_curve, index=self.timestamps)
)
pnls = [t['pnl'] for t in trades]
wins = [p for p in pnls if p > 0]
# Max drawdown
equity = np.array(self.equity_curve)
running_max = np.maximum.accumulate(equity)
drawdowns = (equity - running_max) / running_max
max_drawdown = abs(drawdowns.min())
# Sharpe Ratio (annualisé,假设日交易)
returns = np.diff(self.equity_curve) / np.array(self.equity_curve[:-1])
sharpe = np.mean(returns) / np.std(returns) * np.sqrt(252) if np.std(returns) > 0 else 0
# Durée moyenne des trades
durations = [t['duration'] for t in trades]
avg_duration = sum(durations, timedelta(0)) / len(durations)
return BacktestResult(
total_pnl=sum(pnls),
total_trades=len(trades),
win_rate=len(wins) / len(trades) if trades else 0,
max_drawdown=max_drawdown,
sharpe_ratio=sharpe,
avg_trade_duration=avg_duration,
trades=trades,
equity_curve=pd.Series(self.equity_curve, index=self.timestamps)
)
Exemple d'utilisation
print("=" * 70)
print("🚀 Lancement du backtest - Spread CME vs Kraken Perpetual")
print("=" * 70)
Données simulées pour démonstration (remplacez par fetch_ohlcv réel)
np.random.seed(42)
dates = pd.date_range(start='2025-01-01', end='2025-12-31', freq='1h')
Simulation prix (Kraken perpetual légèrement en avance sur CME)
kraken_base = 67500 + np.cumsum(np.random.randn(len(dates)) * 50)
cme_base = kraken_base * (1 + np.random.randn(len(dates)) * 0.001 + 0.0005) # CME + base moyenne
df_kraken = pd.DataFrame({
'timestamp': dates,
'close': kraken_base,
'open': kraken_base * 0.999,
'high': kraken_base * 1.002,
'low': kraken_base * 0.998,
'volume': np.random.randint(100, 1000, len(dates))
}).set_index('timestamp')
df_cme = pd.DataFrame({
'timestamp': dates,
'close': cme_base,
'open': cme_base * 0.999,
'high': cme_base * 1.002,
'low': cme_base * 0.998,
'volume': np.random.randint(50, 500, len(dates))
}).set_index('timestamp')
Initialisation du backtester
backtester = SpreadBacktester(initial_capital=100000.0)
Calcul du spread
spread = backtester.calculate_spread(df_cme, df_kraken)
Génération des signaux
signals = backtester.generate_signals(spread)
Exécution du backtest
results = backtester.run_backtest(spread, signals, "CME-Kraken Spread")
Affichage des résultats
print(f"\n📊 Résultats du Backtest")
print(f" ├─ P&L Total : ${results.total_pnl:,.2f}")
print(f" ├─ Nombre de trades : {results.total_trades}")
print(f" ├─ Win Rate : {results.win_rate*100:.1f}%")
print(f" ├─ Max Drawdown : {results.max_drawdown*100:.2f}%")
print(f" ├─ Sharpe Ratio : {results.sharpe_ratio:.2f}")
print(f" └─ Durée moyenne : {results.avg_trade_duration}")
Rentabilité annualisée
roi_annual = (results.total_pnl / backtester.initial_capital) * 100
print(f"\n💰 ROI Annualisé : {roi_annual:.1f}%")
print(f"💵 Capital Final : ${backtester.current_capital:,.2f}")
Visualisation de la courbe de termes et du spread
# src/visualizer.py
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd
import numpy as np
class TermStructureVisualizer:
"""
Visualisation de la courbe de termes BTC Futures
et des opportunités de spread inter-exchanges
"""
def __init__(self, style: str = 'seaborn-v0_8-darkgrid'):
plt.style.use(style)
self.fig = None
def plot_term_structure_snapshot(self, contracts: List[Dict],
date: pd.Timestamp,
save_path: str = None):
"""
Trace un instantané de la courbe de termes
Args:
contracts: Liste de dictionnaires avec 'symbol', 'expiry', 'price'
date: Date de l'instantané
"""
fig, axes = plt.subplots(2, 1, figsize=(14, 10),
gridspec_kw={'height_ratios': [3, 1]})
# Extraction des données
symbols = [c['symbol'] for c in contracts]
prices = [c['price'] for c in contracts]
expiries = [pd.to_datetime(c['expiry']) for c in contracts]
# Tri par date d'expiration
sorted_data = sorted(zip(expiry, prices, symbols))
expiries, prices, symbols = zip(*sorted_data)
# Graphique 1 : Prix des contrats
ax1 = axes[0]
colors = plt.cm.viridis(np.linspace(0, 1, len(contracts)))
for i, (exp, price, sym) in enumerate(sorted_data):
ax1.scatter(exp, price, c=[colors[i]], s=100, zorder=5, edgecolors='white')
ax1.annotate(sym, (exp, price), textcoords="offset points",
xytext=(0,10), ha='center', fontsize=9)
ax1.plot(expiries, prices, 'b--', alpha=0.5, linewidth=1)
ax1.set_title(f'Courbe de Termes BTC Futures - {date.strftime("%Y-%m-%d")}',
fontsize=14, fontweight='bold')
ax1.set_ylabel('Prix (USD)', fontsize=11)
ax1.grid(True, alpha=0.3)
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
# Graphique 2 : Base (différence avec spot)
ax2 = axes[1]
spot_price = sorted_data[0][1] # Prix du contrat le plus proche
basis = [(p - spot_price) / spot_price * 100 for _, p, _ in sorted_data]
bars = ax2.bar(expiries, basis, width=20, color=['green' if b > 0 else 'red'
for b in basis], alpha=0.7)
ax2.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
ax2.set_ylabel('Base (%)', fontsize=11)
ax2.set_xlabel('Date d\'Expiration', fontsize=11)
ax2.grid(True, alpha=0.3)
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=150, bbox_inches='tight')
print(f"📊 Graphique sauvegardé : {save_path}")
self.fig = fig
plt.show()
def plot_spread_analysis(self, spread: pd.Series,
signals: pd.DataFrame,
save_path: str = None):
"""
Trace l'analyse du spread et des signaux de trading
"""
fig, axes = plt.subplots(3, 1, figsize=(15, 12),
gridspec_kw={'height_ratios': [2, 1, 2]})
# Graphique 1 : Évolution du spread
ax1 = axes[0]
ax1.plot(spread.index, spread.values * 100, 'b-', linewidth=0.8, alpha=0.7,
label='Spread (%)')
# Zones de seuils
backtester = SpreadBacktester()
ax1.axhline(y=backtester.entry_threshold * 100, color='green',
linestyle='--', alpha=0.7, label='Seuil Entry')
ax1.axhline(y=-backtester.entry_threshold * 100, color='green',
linestyle='--', alpha=0.7)
ax1.axhline(y=backtester.stop_loss * 100, color='red',
linestyle=':', alpha=0.7, label='Stop Loss')
ax1.axhline(y=-backtester.stop_loss * 100, color='red',
linestyle=':', alpha=0.7)
ax1.fill_between(spread.index, spread.values * 100, 0,
where=(spread.values > 0), alpha=0.3, color='red',
label='Short Spread Zone')
ax1.fill_between(spread.index, spread.values * 100, 0,
where=(spread.values < 0), alpha=0.3, color='green',
label='Long Spread Zone')
ax1.set_title('Évolution du Spread CME-Kraken BTC Futures',
fontsize=14, fontweight='bold')
ax1.set_ylabel('Spread (%)', fontsize=11)
ax1.legend(loc='upper right')
ax1.grid(True, alpha=0.3)
# Graphique 2 : Position dans le temps
ax2 = axes[1]
ax2.fill_between(signals.index, signals['position'].values, 0,
where=(signals['position'].values > 0),
alpha=0.5, color='green', label='Position Long')
ax2.fill_between(signals.index, signals['position'].values, 0,
where=(signals['position'].values < 0),
alpha=0.5, color='red', label='Position Short')
ax2.set_ylabel('Position', fontsize=11)
ax2.set_ylim(-1.5, 1.5)
ax2.legend(loc='upper right')
ax2.grid(True, alpha=0.3)
# Graphique 3 : Equity Curve
ax3 = axes[2]
equity = backtester.equity_curve
timestamps = backtester.timestamps
if len(equity) > 0:
ax3.plot(timestamps, equity, 'b-', linewidth=1.5, label='Equity')
ax3.fill_between(timestamps, equity, backtester.initial_capital,
where=(np.array(equity) > backtester.initial_capital),
alpha=0.3, color='green')
ax3.fill_between(timestamps, equity, backtester.initial_capital,
where=(np.array(equity) < backtester.initial_capital),
alpha=0.3, color='red')
ax3.axhline(y=backtester.initial_capital, color='black',
linestyle='--', alpha=0.5)
ax3.set_title('Equity Curve', fontsize=14, fontweight='bold')
ax3.set_xlabel('Date', fontsize=11)
ax3.set_ylabel('Capital (USD)', fontsize=11)
ax3.legend(loc='upper left')
ax3.grid(True, alpha=0.3)
ax3.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=150, bbox_inches='tight')
print(f"📊 Graphique sauvegardé : {save_path}")
self.fig = fig
plt.show()
Exécution de la visualisation
print("=" * 70)
print("📈 Génération des visualisations de la courbe de termes")
print("=" * 70)
visualizer = TermStructureVisualizer()
Données de démonstration pour courbe de termes
demo_contracts = [
{'symbol': 'BTC-PERPETUAL', 'expiry': '2026-06-30', 'price': 67850},
{'symbol': 'BTC-2026-06-26', 'expiry': '2026-06-26', 'price': 67920},
{'symbol': 'BTC-2026-09-25', 'expiry': '2026-09-25', 'price': 68250},
{'symbol': 'BTC-2026-12-31', 'expiry': '2026-12-31', 'price': 68780},
{'symbol': 'BTC-2027-03-25', 'expiry': '2027-03-25', 'price': 69450},
]
snapshot_date = pd.Timestamp('2026-05-30')
visualizer.plot_term_structure_snapshot(demo_contracts, snapshot_date)
Visualisation du spread
visualizer.plot_spread_analysis(spread, signals)
Erreurs courantes et solutions
Erreur 1 : "401 Unauthorized - Invalid API Key"
Symptôme : La requête retourne une erreur 401 avec le message "Invalid API key" même après avoir collé votre clé.