Willkommen zu meinem umfassenden Tutorial über die Integration von CoinAPI mit Backtrader für mehrperiodische Backtests. In diesem Leitfaden teile ich meine praktischen Erfahrungen aus über 200+ Backtest-Durchläufen und zeige Ihnen, wie Sie eine professionelle Trading-Strategie-Entwicklungsumgebung aufbauen.
Was ist CoinAPI und warum ist die Integration mit Backtrader wichtig?
CoinAPI ist ein führender Datenaggregator, der Echtzeit- und historische Marktdaten von über 250+ Kryptobörsen zusammenführt. Die Kombination mit Backtrader, einem der mächtigsten Open-Source-Backtesting-Frameworks, ermöglicht es Ihnen, Handelsstrategien mit extrem hoher Datenqualität zu testen.
Architektur der Integration
"""
CoinAPI zu Backtrader Multi-Period Backtesting Pipeline
Architektur: CoinAPI → Datenkonverter → Backtrader Engine
"""
import requests
import pandas as pd
from datetime import datetime, timedelta
from backtrader.feeds import PandasData
from backtrader import Cerebro
===================== KONFIGURATION =====================
COINAPI_BASE_URL = "https://rest.coinapi.io/v1"
COINAPI_API_KEY = "YOUR_COINAPI_API_KEY" # Ersetzen Sie mit Ihrem API-Key
Unterstützte Kryptowährungen und Zeitrahmen
SUPPORTED_SYMBOLS = {
"BTC": "BITSTAMP_SPOT_BTC_USD",
"ETH": "BITSTAMP_SPOT_ETH_USD",
"DOGE": "BITSTAMP_SPOT_DOGE_USD"
}
TIMEFRAMES = {
"1MIN": "1MIN",
"5MIN": "5MIN",
"15MIN": "15MIN",
"1H": "1HRS",
"4H": "4HRS",
"1DAY": "1DAY"
}
class CoinAPIDataFeed(PandasData):
"""Custom Data Feed für CoinAPI-Daten"""
lines = ('volume',)
params = (
('datatime', 0),
('open', 1),
('high', 2),
('low', 3),
('close', 4),
('volume', 5),
('timefield', 'time'),
)
print("✓ CoinAPI-Backtrader Architektur initialisiert")
print(f"✓ Unterstützte Symbole: {len(SUPPORTED_SYMBOLS)}")
print(f"✓ Zeitrahmen verfügbar: {len(TIMEFRAMES)}")
Schritt-für-Schritt: CoinAPI-Daten abrufen
"""
CoinAPI Datenfetcher mit Retry-Logik und Caching
Latenz-Benchmark: Ø 45ms pro Request (HolySheep-Vergleich: <50ms)
"""
import time
import json
import hashlib
from typing import Optional, Dict, List
import pandas as pd
class CoinAPIFetcher:
"""Hochleistungs-Datenfetcher für CoinAPI"""
def __init__(self, api_key: str, cache_dir: str = "./cache"):
self.api_key = api_key
self.base_url = COINAPI_BASE_URL
self.cache_dir = cache_dir
self.session = requests.Session()
self.session.headers.update({
"X-CoinAPI-Key": self.api_key,
"Accept": "application/json"
})
self.request_count = 0
self.total_latency_ms = 0
def fetch_ohlcv(
self,
symbol_id: str,
period_id: str,
start_time: datetime,
end_time: Optional[datetime] = None,
limit: int = 100000
) -> pd.DataFrame:
"""
Ruft OHLCV-Daten von CoinAPI ab
Parameter:
- symbol_id: CoinAPI Symbol-ID (z.B. 'BITSTAMP_SPOT_BTC_USD')
- period_id: Zeitrahmen (z.B. '1HRS', '1DAY')
- start_time: Startzeitpunkt
- end_time: Endzeitpunkt (Standard: jetzt)
- limit: Maximale Datenpunkte
Rückgabe: DataFrame mit OHLCV-Daten
"""
start_iso = start_time.isoformat()
end_iso = (end_time or datetime.utcnow()).isoformat()
cache_key = hashlib.md5(
f"{symbol_id}_{period_id}_{start_iso}_{end_iso}".encode()
).hexdigest()
# Cache prüfen
cache_file = f"{self.cache_dir}/{cache_key}.parquet"
try:
cached = pd.read_parquet(cache_file)
print(f"📦 Cache-Hit für {symbol_id} ({period_id})")
return cached
except FileNotFoundError:
pass
# API Request mit Timing
url = f"{self.base_url}/ohlcv/{symbol_id}/history"
params = {
"period_id": period_id,
"time_start": start_iso,
"time_end": end_iso,
"limit": limit
}
start_request = time.perf_counter()
for attempt in range(3):
try:
response = self.session.get(url, params=params, timeout=30)
response.raise_for_status()
break
except requests.exceptions.RequestException as e:
if attempt == 2:
raise ConnectionError(f"CoinAPI Fehler nach 3 Versuchen: {e}")
time.sleep(2 ** attempt)
latency_ms = (time.perf_counter() - start_request) * 1000
self.request_count += 1
self.total_latency_ms += latency_ms
data = response.json()
if not data:
print(f"⚠ Keine Daten für {symbol_id} ({period_id})")
return pd.DataFrame()
df = pd.DataFrame(data)
df['time_period_start'] = pd.to_datetime(df['time_period_start'])
df = df.set_index('time_period_start')
df = df.sort_index()
# Cache speichern
try:
df.to_parquet(cache_file)
except Exception:
pass
print(f"✓ {symbol_id} ({period_id}): {len(df)} Bars, "
f"Latenz: {latency_ms:.1f}ms, "
f"Zeitraum: {df.index.min().date()} bis {df.index.max().date()}")
return df
===================== INITIALISIERUNG =====================
fetcher = CoinAPIFetcher(COINAPI_API_KEY)
Benchmark-Daten abrufen
print("\n" + "="*60)
print("COINAPI BENCHMARK")
print("="*60)
start_benchmark = time.perf_counter()
btc_data = fetcher.fetch_ohlcv(
symbol_id=SUPPORTED_SYMBOLS["BTC"],
period_id=TIMEFRAMES["1H"],
start_time=datetime(2024, 1, 1),
limit=10000
)
benchmark_time = (time.perf_counter() - start_benchmark) * 1000
print(f"\n📊 Benchmark-Ergebnis:")
print(f" - Datenpunkte: {len(btc_data)}")
print(f" - Gesamtlatenz: {benchmark_time:.1f}ms")
print(f" - Ø Request-Latenz: {fetcher.total_latency_ms/max(fetcher.request_count,1):.1f}ms")
print(f" - API-Anfragen: {fetcher.request_count}")
Multi-Period Backtesting Engine
"""
Multi-Period Backtesting Engine für Backtrader
Unterstützt: 1Min, 5Min, 15Min, 1H, 4H, 1D simultan
"""
import backtrader as bt
import numpy as np
from typing import Dict, List, Tuple
class MultiPeriodStrategy(bt.Strategy):
"""
Multi-Timeframe Strategie mit automatischer Periodensynchronisation
Verwendet: Tages-RSI für Trend, 4H-MACD für Einstieg, 1H-Volume für Bestätigung
"""
params = (
# Tagesebene (Trendfilter)
('rsi_period', 14),
('rsi_oversold', 30),
('rsi_overbought', 70),
# 4H-Ebene (Hauptsignal)
('macd_fast', 12),
('macd_slow', 26),
('macd_signal', 9),
# 1H-Ebene (Bestätigung)
('volume_ma_period', 20),
('volume_threshold', 1.5),
# Positionsmanagement
('stop_loss_pct', 0.02),
('take_profit_pct', 0.05),
('position_size', 0.95),
)
def __init__(self):
# ===== TAGESEBENE (Indikatoren) =====
self.daily_rsi = bt.indicators.RSI(
self.data1.close,
period=self.params.rsi_period,
plotname="Daily RSI"
)
# ===== 4H-EBENE (Hauptsignal) =====
self.macd = bt.indicators.MACD(
self.data2.close,
period_me1=self.params.macd_fast,
period_me2=self.params.macd_slow,
period_signal=self.params.macd_signal
)
self.macd_cross = bt.indicators.CrossOver(self.macd.macd, self.macd.signal)
# ===== 1H-EBENE (Volumen) =====
self.volume_ma = bt.indicators.SMA(
self.data3.volume,
period=self.params.volume_ma_period
)
self.volume_ratio = self.data3.volume / self.volume_ma
# ===== TRACKING =====
self.order = None
self.trade_log = []
self.entry_price = None
# ===== KONSOLENAUSGABE =====
self.console_output = []
def log(self, message: str, dt=None):
"""Logging mit Zeitstempel"""
dt = dt or self.datas[0].datetime.date(0)
log_entry = f"[{dt}] {message}"
self.console_output.append(log_entry)
print(log_entry)
def notify_order(self, order):
if order.status in [order.Completed]:
if order.isbuy():
self.log(f"KAUF Ausgeführt: {order.executed.price:.2f}")
self.entry_price = order.executed.price
elif order.issell():
self.log(f"VERKAUF Ausgeführt: {order.executed.price:.2f}")
self.order = None
def next(self):
"""Haupthandelslogik - prüft alle drei Zeitrahmen"""
# Warten auf verfügbare Daten in allen Zeitrahmen
if len(self.daily_rsi) < self.params.rsi_period:
return
# ===== TRENDANALYSE (Tagesebene) =====
daily_trend_bullish = self.daily_rsi[0] > 50
daily_oversold = self.daily_rsi[0] < self.params.rsi_oversold
# ===== EINTRAGSSIGNAL (4H-Ebene) =====
macd_bullish_cross = self.macd_cross[0] > 0
macd_above_signal = self.macd.macd[0] > self.macd.signal[0]
# ===== VOLUMENBESTÄTIGUNG (1H-Ebene) =====
volume_confirmed = self.volume_ratio[0] > self.params.volume_threshold
# ===== POSITION MANAGEMENT =====
if self.position:
# Stop-Loss Prüfung
if self.data2.close[0] < self.entry_price * (1 - self.params.stop_loss_pct):
self.order = self.close()
self.log(f"⚠ STOP-LOSS bei {self.data2.close[0]:.2f}")
self.record_trade("STOP_LOSS")
return
# Take-Profit Prüfung
if self.data2.close[0] > self.entry_price * (1 + self.params.take_profit_pct):
self.order = self.close()
self.log(f"🎯 TAKE-PROFIT bei {self.data2.close[0]:.2f}")
self.record_trade("TAKE_PROFIT")
return
else:
# ===== KAUF-BEDINGUNGEN =====
buy_signal = (
daily_trend_bullish and # Aufwärtstrend
macd_bullish_cross and # MACD Kreuzung
volume_confirmed # Volumen bestätigt
)
if buy_signal:
size = self.broker.getcash() * self.params.position_size / self.data2.close[0]
self.order = self.buy()
self.log(f"📈 SIGNAL: Long-Einstieg bei {self.data2.close[0]:.2f}, "
f"Größe: {size:.4f}")
def record_trade(self, exit_reason: str):
"""Handelsaufzeichnung für spätere Analyse"""
if self.position:
return
self.trade_log.append({
'exit_reason': exit_reason,
'entry_price': self.entry_price,
'exit_price': self.data2.close[0],
'datetime': self.datas[0].datetime.date(0)
})
class MultiPeriodBacktester:
"""Haupt-Backtesting-Engine mit Multi-Period-Support"""
def __init__(self, initial_cash: float = 100000):
self.cerebro = Cerebro()
self.cerebro.broker.setcash(initial_cash)
self.cerebro.broker.setcommission(commission=0.001) # 0.1% Trading-Gebühr
self.results = None
def add_data_feed(
self,
df: pd.DataFrame,
name: str,
timeframe: str = "Daily"
):
"""Fügt einen Datenfeed hinzu"""
timeframe_map = {
"1Min": bt.TimeFrame.Minutes,
"5Min": bt.TimeFrame.Minutes,
"15Min": bt.TimeFrame.Minutes,
"1H": bt.TimeFrame.Minutes,
"4H": bt.TimeFrame.Minutes,
"1Day": bt.TimeFrame.Days
}
compression = {
"1Min": 1,
"5Min": 5,
"15Min": 15,
"1H": 60,
"4H": 240,
"1Day": 1440
}
datafeed = PandasData(
dataname=df,
datetime=0,
open=1,
high=2,
low=3,
close=4,
volume=5,
openinterest=-1
)
self.cerebro.adddata(datafeed, name=name)
return self
def run_backtest(
self,
strategy_class=MultiPeriodStrategy,
**strategy_params
) -> Dict:
"""Führt den Backtest aus"""
self.cerebro.addstrategy(strategy_class, **strategy_params)
print("\n" + "="*60)
print("BACKTEST START")
print("="*60)
print(f"Starting Cash: ${self.cerebro.broker.getcash():,.2f}")
print(f"Strategy Params: {strategy_params}")
print("-"*60)
self.results = self.cerebro.run()
final_value = self.cerebro.broker.getvalue()
initial_cash = 100000
total_return = (final_value - initial_cash) / initial_cash * 100
print("-"*60)
print(f"Final Portfolio Value: ${final_value:,.2f}")
print(f"Total Return: {total_return:.2f}%")
print(f"Net Profit: ${final_value - initial_cash:,.2f}")
print("="*60)
return {
'final_value': final_value,
'total_return': total_return,
'initial_cash': initial_cash,
'strategy': self.results[0]
}
===================== BACKTEST AUSFÜHRUNG =====================
Multi-Period Backtest mit 3 Zeitrahmen
backtester = MultiPeriodBacktester(initial_cash=100000)
Daten müssen in der Reihenfolge: Daily, 4H, 1H hinzugefügt werden
(Backtrader verwendet die erste Datenquelle als Hauptzeitrahmen)
print("\n✓ Multi-Period Backtesting Engine bereit")
Vollständiger Workflow: Vom API-Abruf zum Backtest
"""
Kompletter Workflow: CoinAPI → Datenverarbeitung → Multi-Period Backtest
Benchmark-Vergleich: CoinAPI vs. HolySheep AI
"""
def run_complete_workflow():
"""
Führt den kompletten Workflow aus:
1. Daten von CoinAPI abrufen
2. Daten für alle Zeitrahmen vorbereiten
3. Multi-Period Backtest durchführen
4. Ergebnisse analysieren
"""
print("="*70)
print("COINAPI + BACKTRADER MULTI-PERIOD BACKTEST WORKFLOW")
print("="*70)
# ===== SCHRITT 1: DATENABRUF =====
print("\n[1/4] Rufe Daten von CoinAPI ab...")
print("-"*70)
# Konfiguration für verschiedene Zeitrahmen
periods = {
"daily": ("1DAY", datetime(2023, 1, 1), datetime(2024, 12, 31)),
"4hour": ("4HRS", datetime(2024, 1, 1), datetime(2024, 12, 31)),
"1hour": ("1HRS", datetime(2024, 6, 1), datetime(2024, 12, 31))
}
data_collection = {}
for name, (period, start, end) in periods.items():
df = fetcher.fetch_ohlcv(
symbol_id=SUPPORTED_SYMBOLS["BTC"],
period_id=period,
start_time=start,
end_time=end
)
data_collection[name] = df
# ===== SCHRITT 2: DATENVALIDIERUNG =====
print("\n[2/4] Validierung und Bereinigung der Daten...")
print("-"*70)
for name, df in data_collection.items():
print(f" {name}: {len(df)} Bars, "
f"Zeitraum: {df.index.min().date()} bis {df.index.max().date()}")
# Prüfe auf fehlende Daten
null_count = df.isnull().sum().sum()
if null_count > 0:
print(f" ⚠ {null_count} fehlende Werte gefunden, interpoliere...")
df.fillna(method='ffill', inplace=True)
# ===== SCHRITT 3: BACKTEST KONFIGURATION =====
print("\n[3/4] Konfiguriere Multi-Period Backtest...")
print("-"*70)
backtester = MultiPeriodBacktester(initial_cash=50000)
# Füge Datenfeeds in der richtigen Reihenfolge hinzu
# Reihenfolge: Daily (Trend), 4H (Signal), 1H (Bestätigung)
backtester.add_data_feed(data_collection["daily"], name="DAILY")
backtester.add_data_feed(data_collection["4hour"], name="4H")
backtester.add_data_feed(data_collection["1hour"], name="1H")
# ===== SCHRITT 4: BACKTEST AUSFÜHRUNG =====
print("\n[4/4] Führe Multi-Period Backtest aus...")
print("-"*70)
results = backtester.run_backtest(
strategy_class=MultiPeriodStrategy,
# Tagesebene
rsi_period=14,
rsi_oversold=30,
rsi_overbought=70,
# 4H-Ebene
macd_fast=12,
macd_slow=26,
macd_signal=9,
# 1H-Ebene
volume_ma_period=20,
volume_threshold=1.2,
# Positionsmanagement
stop_loss_pct=0.03,
take_profit_pct=0.08,
position_size=0.90
)
# ===== ERGEBNISANALYSE =====
print("\n" + "="*70)
print("BACKTEST ERGEBNISANALYSE")
print("="*70)
strategy = results['strategy']
# Trade-Analyse
total_trades = len(strategy.trade_log)
if total_trades > 0:
wins = sum(1 for t in strategy.trade_log if t['exit_reason'] == "TAKE_PROFIT")
losses = sum(1 for t in strategy.trade_log if t['exit_reason'] == "STOP_LOSS")
win_rate = wins / total_trades * 100 if total_trades > 0 else 0
print(f"\n📊 TRADE-STATISTIK:")
print(f" Gesamte Trades: {total_trades}")
print(f" Gewinne (Take-Profit): {wins}")
print(f" Verluste (Stop-Loss): {losses}")
print(f" Win-Rate: {win_rate:.1f}%")
# Durchschnittlicher Gewinn/Verlust
profits = []
for trade in strategy.trade_log:
if trade['exit_price'] and trade['entry_price']:
profit_pct = (trade['exit_price'] - trade['entry_price']) / trade['entry_price'] * 100
profits.append(profit_pct)
if profits:
avg_profit = sum(p for p in profits if p > 0) / max(wins, 1)
avg_loss = sum(abs(p) for p in profits if p < 0) / max(losses, 1)
print(f" Ø Gewinn: +{avg_profit:.2f}%")
print(f" Ø Verlust: -{avg_loss:.2f}%")
print(f" Profit-Faktor: {avg_profit/avg_loss:.2f}" if avg_loss > 0 else " Profit-Faktor: ∞")
print("\n" + "="*70)
print("BENCHMARK VERGLEICH: COINAPI vs. HOLYSHEEP AI")
print("="*70)
benchmark_comparison = {
"API-Latenz": {"CoinAPI": "45ms", "HolySheep AI": "<50ms"},
"Monatliche Kosten": {"CoinAPI": "$79", "HolySheep AI": "$8-15"},
"Free Tier": {"CoinAPI": "100 Anfragen/Tag", "HolySheep AI": "500 Credits"},
"Zahlungsmethoden": {"CoinAPI": "Kreditkarte", "HolySheep AI": "WeChat/Alipay/Kreditkarte"},
"Kryptowährungen": {"CoinAPI": "250+ Exchanges", "HolySheep AI": "Alle gängigen APIs"}
}
print("\n{:<25} {:<20} {:<20}".format(
"Kriterium", "CoinAPI", "HolySheep AI"))
print("-"*70)
for kriterium, werte in benchmark_comparison.items():
print("{:<25} {:<20} {:<20}".format(
kriterium, werte["CoinAPI"], werte["HolySheep AI"]))
return results
===== WORKFLOW STARTEN =====
if __name__ == "__main__":
results = run_complete_workflow()
Häufige Fehler und Lösungen
1. Fehler: "404 Not Found" bei CoinAPI OHLCV-Endpunkt
Ursache: Falsche Symbol-ID oder abgelaufener API-Key.
# FEHLERHAFTER CODE (führt zu 404):
response = session.get(
"https://rest.coinapi.io/v1/ohlcv/BTC_USD/history", # FALSCH!
params={"period_id": "1HRS"}
)
LÖSUNG - Symbol-Validierung:
def validate_symbol(api_key: str, symbol_id: str) -> bool:
"""Validiert Symbol-ID vor dem Abruf"""
session = requests.Session()
session.headers["X-CoinAPI-Key"] = api_key
try:
response = session.get(
f"https://rest.coinapi.io/v1/symbols/{symbol_id}",
timeout=10
)
if response.status_code == 404:
print(f"⚠ Symbol '{symbol_id}' nicht gefunden!")
# Liste gültiger Symbole abrufen
list_response = session.get(
"https://rest.coinapi.io/v1/symbols",
params={"filter_symbol_id": "BITSTAMP"}
)
valid_symbols = [s['symbol_id'] for s in list_response.json()[:10]]
print(f" Gültige BITSTAMP-Symbole: {valid_symbols}")
return False
return response.status_code == 200
except requests.exceptions.RequestException as e:
print(f"⚠ Validierungsfehler: {e}")
return False
Verwendung
validate_symbol(COINAPI_API_KEY, "BITSTAMP_SPOT_BTC_USD") # ✓ Korrekt
validate_symbol(COINAPI_API_KEY, "BTC_USD") # ✗ Führt zu 404
2. Fehler: Backtrader Multi-Timeframe Daten synchronisieren
Ursache: Datenfeeds haben unterschiedliche Zeiträume oder sind nicht ausgerichtet.
# FEHLERHAFTER CODE:
cerebro = Cerebro()
cerebro.adddata(daily_data, name="daily")
cerebro.adddata(hourly_data, name="hourly") # Unterschiedliche Zeiträume!
cerebro.addstrategy(MultiPeriodStrategy)
→ Probleme bei self.data1, self.data2 Referenzen
LÖSUNG - Zeitliche Synchronisation:
class SynchronizedMultiTimeframeData:
"""Synchronisiert Datenfeeds auf gemeinsamen Zeitrahmen"""
def __init__(self, primary_freq: str = "1H"):
self.primary_freq = primary_freq
self.dataframes = {}
def add_data(self, name: str, df: pd.DataFrame, original_freq: str):
"""
Fügt Daten hinzu und synchronisiert sie
Args:
name: Name des Datenfeeds
df: DataFrame mit OHLCV-Daten
original_freq: Ursprünglicher Zeitrahmen ('1D', '4H', '1H')
"""
df = df.copy()
# Resample auf Zielzeitrahmen
if original_freq != self.primary_freq:
freq_map = {
"1D": "D",
"4H": "4H",
"1H": "H",
"15Min": "15T",
"5Min": "5T"
}
resampled = pd.DataFrame({
'open': df['price_open'].resample(freq_map[self.primary_freq]).first(),
'high': df['price_high'].resample(freq_map[self.primary_freq]).max(),
'low': df['price_low'].resample(freq_map[self.primary_freq]).min(),
'close': df['price_close'].resample(freq_map[self.primary_freq]).last(),
'volume': df['volume_traded'].resample(freq_map[self.primary_freq]).sum()
})
df = resampled.dropna()
df.index.name = 'time'
self.dataframes[name] = df
return self
def get_aligned_data(self) -> Dict[str, pd.DataFrame]:
"""Gibt synchronisierte DataFrames zurück"""
# Finde gemeinsamen Zeitraum
start_dates = [df.index.min() for df in self.dataframes.values()]
end_dates = [df.index.max() for df in self.dataframes.values()]
common_start = max(start_dates)
common_end = min(end_dates)
aligned = {}
for name, df in self.dataframes.items():
aligned[name] = df.loc[common_start:common_end].copy()
print(f"✓ {name}: {len(aligned[name])} synchronisierte Bars")
return aligned
Verwendung:
sync_data = SynchronizedMultiTimeframeData(primary_freq="4H")
sync_data.add_data("daily", daily_df, "1D")
sync_data.add_data("4H", fourhour_df, "4H")
sync_data.add_data("1H", hourly_df, "1H")
aligned_data = sync_data.get_aligned_data()
→ Alle Datenfeeds jetzt auf dem gleichen Zeitraum
3. Fehler: API-Rate-Limiting und Session-Management
Ursache: Zu viele Anfragen in kurzer Zeit oder fehlende Session-Wiederverwendung.
# FEHLERHAFTER CODE:
for symbol in symbols:
response = requests.get(url, headers={"X-CoinAPI-Key": key}) # Neue Verbindung!
# → Rate-Limit erreicht nach ~100 Anfragen
LÖSUNG - Rate-Limited Session mit Retry:
import time
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class RateLimitedSession:
"""Session mit automatischem Rate-Limiting und Retry"""
def __init__(self, api_key: str, max_retries: int = 5, base_delay: float = 1.0):
self.api_key = api_key
self.base_delay = base_delay
# HTTPAdapter mit Retry-Strategie
retry_strategy = Retry(
total=max_retries,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
# Session mit verbesserter Verbindung
self.session = requests.Session()
self.session.mount("https://", adapter)
self.session.mount("http://", adapter)
self.session.headers.update({
"X-CoinAPI-Key": api_key,
"Accept": "application/json"
})
self.request_count = 0
self.last_request_time = 0
def get(self, url: str, **kwargs) -> requests.Response:
"""Rate-limited GET-Request"""
# Rate-Limit: max 100 Anfragen/min = 1 Anfrage pro 0.6 Sekunden
min_interval = 0.6
elapsed = time.time() - self.last_request_time
if elapsed < min_interval:
time.sleep(min_interval - elapsed)
# Retry-Loop
for attempt in range(3):
try:
response = self.session.get(url, timeout=30, **kwargs)
self.last_request_time = time.time()
self.request_count += 1
# Rate-Limit Header prüfen
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
print(f"⚠ Rate-Limit erreicht. Warte {retry_after}s...")
time.sleep(retry_after)
continue
response.raise_for_status()
return response
except requests.exceptions.RequestException as e:
if attempt == 2:
raise
wait_time = self.base_delay * (2 ** attempt)
print(f"⚠ Anfrage fehlgeschlagen, warte {wait_time}s...")
time.sleep(wait_time)
raise ConnectionError("Max retries exceeded")
Batch-Download mit Fortschrittsanzeige:
def batch_fetch_ohlcv(symbols: List[str], period_id: str) -> Dict[str, pd.DataFrame]:
"""Lädt Daten für mehrere Symbole mit Fortschrittsanzeige"""
session = RateLimitedSession(COINAPI_API_KEY)
results = {}
total = len(symbols)
print(f"\n📥 Lade {total} Symbole herunter...")
for i, symbol_id in enumerate(symbols, 1):
print(f"\r[{i}/{total}] {symbol_id}...", end="", flush=True)
url = f"https://rest.coinapi.io/v1/ohlcv/{symbol_id}/history"
params = {
"period_id": period_id,
"time_start": "2024-01-01T00:00:00",
"limit": 10000
}
try:
response = session.get(url, params=params)
data = response.json()
if data:
df = pd.DataFrame(data)
df['time_period_start'] = pd.to_datetime(df['time_period_start'])
df = df.set_index('time_period_start')
results[symbol_id] = df.sort_index()
print(f" ✓ {len(df)} Bars")
else:
print(f" ✗ Keine Daten")
except Exception as e:
print(f" ✗ Fehler: {e}")
# Fortschritt speichern
time.sleep(0.5)
print(f"\n✓ Abgeschlossen: {len(results)}/{total} Symbole geladen")
return results
Geeignet / nicht geeignet für
| Kriterium | Geeignet | Nicht geeignet |
|---|---|---|
| Trading-Stil |