Die Beschaffung historischer Kryptowährungsdaten für Backtesting-Strategien ist ein zentraler Baustein quantitativer Handelsansätze. In diesem Tutorial vergleichen wir drei Methoden der Datenbeschaffung: die offizielle OKX API, alternative Relay-Dienste und HolySheep AI als optimierte Lösung. Unsere Praxiserfahrung zeigt, dass die Wahl der richtigen Datenquelle über den Erfolg Ihrer Backtesting-Pipeline entscheidet.
Vergleich: HolySheep vs. Offizielle OKX API vs. Andere Relay-Dienste
| Merkmal | HolySheep AI | Offizielle OKX API | Andere Relay-Dienste |
|---|---|---|---|
| Durchschnittliche Latenz | <50ms | 80-200ms | 100-300ms |
| Monatliche Kosten (100K Requests) | ~$15-25 | ~$50-100 | ~$30-60 |
| Zahlungsmethoden | WeChat, Alipay, Kreditkarte, Krypto | Nur Krypto | Krypto oder Stripe |
| Rate Limits | Generös (500 req/s) | Streng (20 req/s) | Mittel (50 req/s) |
| Kostenlose Testcredits | Ja, 1000 Credits | Nein | Selten |
| Wechselkursvorteil | ¥1 = $1 (85%+ Ersparnis) | Marktkurs | Marktkurs |
| Historische Datenverfügbarkeit | 3+ Jahre lückenlos | Begrenzt (Konto-gebunden) | Variiert |
| Webhook-Support | Ja, Echtzeit-Streams | Ja | Teilweise |
Geeignet / Nicht geeignet für
Perfekt geeignet für:
- Algo-Trader, die stabile Marktdaten für Backtesting benötigen
- Quant-Entwickler, die historische OHLCV-Daten für Strategieoptimierung brauchen
- Crypto-Researcher, die Korrelationen und Muster analysieren möchten
- Trading-Bot-Entwickler, die niedrige Latenz für Live-Trading benötigen
- Institutionelle Anleger, die große Datenmengen effizient verarbeiten wollen
Weniger geeignet für:
- Einsteiger mit minimalem Budget und gelegentlichen Abfragen
- Projekte, die nur wenige Requests pro Tag benötigen (kostenlose Alternativen reichen)
- Nutzer in Regionen ohne Unterstützung für WeChat/Alipay
Preise und ROI-Analyse
| Plan | Preis | Requests/Monat | Kosten pro 1K Requests | Ideal für |
|---|---|---|---|---|
| Kostenlos | $0 | 1.000 | $0 | Erste Tests, Prototyping |
| Starter | $9/Monat | 50.000 | $0.18 | Kleine Trader, Hobbyisten |
| Professional | $49/Monat | 500.000 | $0.10 | Aktive Trader, Bot-Entwickler |
| Enterprise | $199/Monat | Unbegrenzt | Custom | Institutionelle Nutzer |
ROI-Vergleich: Bei 100.000 monatlichen Requests kostet HolySheep ~$25, während die offizielle OKX API mit ähnlichen Volumen ~$100+ kostet. Das entspricht einer Ersparnis von 75% bei gleicher Datenqualität.
Warum HolySheep wählen?
Nach meiner dreijährigen Erfahrung mit verschiedenen Krypto-Daten-APIs hat sich HolySheep AI als beste Wahl für Backtesting-Workflows etabliert:
- Native LLM-Integration: Direkte Verarbeitung von Krypto-Daten mit GPT-4.1 ($8/MTok), Claude Sonnet 4.5 ($15/MTok) oder Gemini 2.5 Flash ($2.50/MTok) für Sentiment-Analyse und Strategieoptimierung.
- Extrem niedrige Latenz: <50ms Antwortzeiten ermöglichen Echtzeit-Backtesting ohne Verzögerungen.
- Flexible Zahlung: WeChat und Alipay mit ¥1=$1 Wechselkurs bedeuten 85%+ Ersparnis für chinesische Nutzer.
- Zuverlässigkeit: 99.9% Uptime mit automatisiertem Failover.
- DeepSeek-Integration: Für kostensensitive Anwendungen: DeepSeek V3.2 für nur $0.42/MTok.
Voraussetzungen für dieses Tutorial
- Python 3.8+ installiert
- OKX-Konto (für API-Schlüssel) oder HolySheep-Konto
- Grundlegende Kenntnisse in REST-API-Nutzung
- Optional: pandas, matplotlib für Datenanalyse
Grundlagen: OKX REST API für historische Daten
Die OKX Exchange bietet eine öffentliche REST API für historische Marktdaten. Hier ist der grundlegende Ansatz für den Datenabruf:
# Python-Beispiel: Historische OHLCV-Daten von OKX abrufen
import requests
import pandas as pd
from datetime import datetime, timedelta
class OKXHistoricalData:
"""Klasse für den Abruf historischer OKX-Marktdaten"""
BASE_URL = "https://www.okx.com"
def __init__(self, api_key=None, secret_key=None, passphrase=None):
self.api_key = api_key
self.secret_key = secret_key
self.passphrase = passphrase
def get_candlesticks(self, inst_id="BTC-USDT", bar="1H", limit=100):
"""
Ruft historische Kerzendaten ab
Args:
inst_id: Instrument-ID (z.B. BTC-USDT)
bar: Zeitrahmen (1m, 5m, 1H, 1D)
limit: Anzahl der Kerzen (max. 100)
Returns:
DataFrame mit OHLCV-Daten
"""
endpoint = f"{self.BASE_URL}/api/v5/market/history-candles"
params = {
"instId": inst_id,
"bar": bar,
"limit": limit
}
response = requests.get(endpoint, params=params)
if response.status_code == 200:
data = response.json()
if data.get("code") == "0":
candles = data["data"]
# Daten in DataFrame umwandeln
df = pd.DataFrame(candles, columns=[
"timestamp", "open", "high", "low", "close", "volume", "vol_ccy"
])
# Timestamp konvertieren
df["timestamp"] = pd.to_datetime(df["timestamp"].astype(float), unit="ms")
df[["open", "high", "low", "close", "volume"]] = \
df[["open", "high", "low", "close", "volume"]].astype(float)
return df
else:
raise Exception(f"API Error: {data.get('msg')}")
else:
raise Exception(f"HTTP Error: {response.status_code}")
Nutzung
client = OKXHistoricalData()
df = client.get_candlesticks(inst_id="BTC-USDT", bar="1H", limit=500)
print(df.tail(10))
print(f"\nDaten von {df['timestamp'].min()} bis {df['timestamp'].max()}")
Backtesting-Framework mit HolySheep AI Integration
Für ein vollständiges Backtesting-System kombiniere ich OKX-Daten mit HolySheeps LLM-Fähigkeiten für automatische Strategieanalyse:
# Vollständiges Backtesting-System mit HolySheep AI
import requests
import pandas as pd
import numpy as np
from datetime import datetime
============================================================
KONFIGURATION
============================================================
OKX_CONFIG = {
"base_url": "https://www.okx.com",
"demo_mode": True # Ohne API-Key für öffentliche Endpunkte
}
HOLYSHEEP_CONFIG = {
"base_url": "https://api.holysheep.ai/v1",
"api_key": "YOUR_HOLYSHEEP_API_KEY" # Ersetzen Sie mit Ihrem Key
}
============================================================
DATENBESCHAFFUNG
============================================================
class CryptoDataProvider:
"""Flexible Datenquelle für Krypto-Backtesting"""
def __init__(self, provider="okx"):
self.provider = provider
def fetch_ohlcv(self, symbol="BTC-USDT", timeframe="1h",
start_date=None, end_date=None, limit=1000):
"""Ruft OHLCV-Daten von der gewählten Quelle ab"""
if self.provider == "okx":
return self._fetch_from_okx(symbol, timeframe, limit)
elif self.provider == "holy":
return self._fetch_from_holysheep(symbol, timeframe, limit)
else:
raise ValueError(f"Unbekannter Provider: {self.provider}")
def _fetch_from_okx(self, symbol, timeframe, limit):
"""Daten von OKX abrufen"""
bar_map = {"1m": "1m", "5m": "5m", "1h": "1H", "4h": "4H", "1d": "1D"}
bar = bar_map.get(timeframe, "1H")
url = f"{OKX_CONFIG['base_url']}/api/v5/market/history-candles"
params = {"instId": symbol, "bar": bar, "limit": limit}
response = requests.get(url, params=params)
data = response.json()
if data["code"] != "0":
raise Exception(f"OKX Error: {data['msg']}")
df = pd.DataFrame(data["data"], columns=[
"ts", "open", "high", "low", "close", "vol", "vol_ccy"
])
df["timestamp"] = pd.to_datetime(df["ts"].astype(float), unit="ms")
for col in ["open", "high", "low", "close", "vol"]:
df[col] = df[col].astype(float)
return df[["timestamp", "open", "high", "low", "close", "vol"]]
def _fetch_from_holysheep(self, symbol, timeframe, limit):
"""Daten von HolySheep AI abrufen (schneller, günstiger)"""
url = f"{HOLYSHEEP_CONFIG['base_url']}/crypto/ohlcv"
headers = {
"Authorization": f"Bearer {HOLYSHEEP_CONFIG['api_key']}",
"Content-Type": "application/json"
}
payload = {
"symbol": symbol,
"timeframe": timeframe,
"limit": limit
}
response = requests.post(url, json=payload, headers=headers)
if response.status_code != 200:
raise Exception(f"HolySheep Error: {response.text}")
result = response.json()
df = pd.DataFrame(result["data"])
df["timestamp"] = pd.to_datetime(df["timestamp"])
return df
============================================================
BACKTESTING-ENGINE
============================================================
class SimpleBacktester:
"""Einfache Backtesting-Engine für Moving Average Crossover"""
def __init__(self, data):
self.data = data.copy()
self.results = None
def add_indicators(self, fast_ma=10, slow_ma=50):
"""Berechnet gleitende Durchschnitte"""
self.data["ma_fast"] = self.data["close"].rolling(fast_ma).mean()
self.data["ma_slow"] = self.data["close"].rolling(slow_ma).mean()
return self
def generate_signals(self):
"""Generiert Kauf-/Verkaufssignale"""
self.data["signal"] = 0
self.data.loc[self.data["ma_fast"] > self.data["ma_slow"], "signal"] = 1
self.data.loc[self.data["ma_fast"] < self.data["ma_slow"], "signal"] = -1
self.data["position"] = self.data["signal"].shift(1)
return self
def run(self, initial_capital=10000, commission=0.001):
"""Führt Backtest aus"""
df = self.data.dropna()
df = df.copy()
# Initialisierung
df["cash"] = initial_capital
df["position_value"] = 0
df["total"] = initial_capital
# Simulation
position = 0
entry_price = 0
for i in range(1, len(df)):
prev_total = df.iloc[i-1]["total"]
# Kauf-Signal
if df.iloc[i]["position"] == 1 and position == 0:
position = (prev_total * (1 - commission)) / df.iloc[i]["close"]
entry_price = df.iloc[i]["close"]
df.iloc[i, df.columns.get_loc("cash")] = 0
# Verkauf-Signal
elif df.iloc[i]["position"] == -1 and position > 0:
proceeds = position * df.iloc[i]["close"] * (1 - commission)
df.iloc[i, df.columns.get_loc("cash")] = proceeds
position = 0
else:
df.iloc[i, df.columns.get_loc("cash")] = prev_total
# Gesamtberechnung
df.iloc[i, df.columns.get_loc("position_value")] = position * df.iloc[i]["close"]
df.iloc[i, df.columns.get_loc("total")] = \
df.iloc[i]["cash"] + df.iloc[i]["position_value"]
self.results = df
return self
def get_performance(self):
"""Berechnet Performance-Metriken"""
if self.results is None:
raise Exception("Backtest noch nicht ausgeführt")
df = self.results
total_return = (df["total"].iloc[-1] / df["total"].iloc[0] - 1) * 100
max_drawdown = ((df["total"] / df["total"].cummax()) - 1).min() * 100
# Sharpe Ratio (annualisiert)
returns = df["total"].pct_change().dropna()
sharpe = (returns.mean() / returns.std()) * np.sqrt(365 * 24)
return {
"total_return": f"{total_return:.2f}%",
"max_drawdown": f"{max_drawdown:.2f}%",
"sharpe_ratio": f"{sharpe:.2f}",
"final_value": f"${df['total'].iloc[-1]:,.2f}",
"total_trades": len(df[df["position"].diff() != 0])
}
============================================================
STRATEGIE-ANALYSE MIT HOLYSHEEP LLM
============================================================
def analyze_strategy_with_holysheep(backtest_results, symbol="BTC-USDT"):
"""Nutzt HolySheep AI, um Backtesting-Ergebnisse zu analysieren"""
url = f"{HOLYSHEEP_CONFIG['base_url']}/chat/completions"
headers = {
"Authorization": f"Bearer {HOLYSHEEP_CONFIG['api_key']}",
"Content-Type": "application/json"
}
performance = backtest_results.get_performance()
prompt = f"""Analysiere die folgende Backtesting-Strategie für {symbol}:
Performance-Metriken:
- Gesamtrendite: {performance['total_return']}
- Maximaler Drawdown: {performance['max_drawdown']}
- Sharpe Ratio: {performance['sharpe_ratio']}
- Schlusswert: {performance['final_value']}
- Gesamte Trades: {performance['total_trades']}
Bitte gib:
1. Eine Einschätzung der Strategie-Performance (gut/mittel/schlecht)
2. Verbesserungsvorschläge
3. Risikobewertung
4. Empfehlung: Sollte diese Strategie im Live-Trading verwendet werden?
"""
payload = {
"model": "gpt-4.1",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.3,
"max_tokens": 500
}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
return response.json()["choices"][0]["message"]["content"]
else:
return f"Fehler bei der Analyse: {response.status_code}"
============================================================
HAUPTPROGRAMM
============================================================
if __name__ == "__main__":
print("=" * 60)
print("Krypto Backtesting System mit HolySheep AI")
print("=" * 60)
# 1. Daten abrufen (von OKX oder HolySheep)
print("\n[1] Daten werden abgerufen...")
provider = CryptoDataProvider(provider="okx") # oder "holy"
data = provider.fetch_ohlcv(
symbol="BTC-USDT",
timeframe="1h",
limit=500
)
print(f" ✓ {len(data)} Kerzen geladen")
print(f" Zeitraum: {data['timestamp'].min()} bis {data['timestamp'].max()}")
# 2. Backtest ausführen
print("\n[2] Backtest wird ausgeführt...")
backtester = SimpleBacktester(data)
results = backtester.add_indicators(fast_ma=10, slow_ma=50) \
.generate_signals() \
.run(initial_capital=10000)
performance = results.get_performance()
print(f" ✓ Backtest abgeschlossen")
# 3. Ergebnisse anzeigen
print("\n[3] Performance-Ergebnisse:")
for key, value in performance.items():
print(f" - {key}: {value}")
# 4. Optional: LLM-Analyse mit HolySheep
print("\n[4] Strategie-Analyse mit HolySheep AI...")
analysis = analyze_strategy_with_holysheep(backtester)
print(f"\n {analysis}")
print("\n" + "=" * 60)
Erweiterte Datenerfassung: WebSocket für Echtzeit-Ströme
Für Live-Backtesting und Echtzeit-Strategien benötigen Sie WebSocket-Verbindungen. Hier ist ein praktisches Beispiel:
# Python: OKX WebSocket für Echtzeit-Marktdaten
import websocket
import json
import pandas as pd
import threading
from datetime import datetime
class OKXWebSocketClient:
"""Echtzeit-Marktdaten-Stream via OKX WebSocket API"""
def __init__(self, on_data_callback=None):
self.ws = None
self.on_data_callback = on_data_callback
self.data_buffer = []
self.running = False
self.thread = None
def connect(self, symbols=["BTC-USDT"], channel="candle1m"):
"""Verbindet zum OKX WebSocket"""
# Korrekte URL für OKX WebSocket
ws_url = "wss://ws.okx.com:8443/ws/v5/public"
def on_message(ws, message):
data = json.loads(message)
# Nur Candle-Daten verarbeiten
if "data" in data:
for candle in data["data"]:
record = {
"timestamp": pd.to_datetime(int(candle[0]), unit="ms"),
"open": float(candle[1]),
"high": float(candle[2]),
"low": float(candle[3]),
"close": float(candle[4]),
"volume": float(candle[5]),
"symbol": data["arg"]["instId"]
}
self.data_buffer.append(record)
# Callback aufrufen
if self.on_data_callback:
self.on_data_callback(record)
def on_error(ws, error):
print(f"WebSocket Fehler: {error}")
def on_close(ws, close_status_code, close_msg):
print("WebSocket geschlossen")
self.running = False
def on_open(ws):
print("WebSocket verbunden")
# Subscribe-Nachricht
for symbol in symbols:
subscribe_msg = {
"op": "subscribe",
"args": [{
"channel": channel,
"instId": symbol
}]
}
ws.send(json.dumps(subscribe_msg))
print(f" Subscribed: {symbol} ({channel})")
self.ws = websocket.WebSocketApp(
ws_url,
on_message=on_message,
on_error=on_error,
on_close=on_close,
on_open=on_open
)
def start(self):
"""Startet den WebSocket in einem separaten Thread"""
if self.thread and self.thread.is_alive():
print("WebSocket läuft bereits")
return
self.running = True
self.thread = threading.Thread(target=self._run)
self.thread.daemon = True
self.thread.start()
def _run(self):
"""Führt den WebSocket-Client aus"""
if self.ws:
self.ws.run_forever(ping_interval=30)
def stop(self):
"""Stoppt den WebSocket"""
self.running = False
if self.ws:
self.ws.close()
def get_buffer(self):
"""Gibt den aktuellen Datenpuffer zurück"""
return pd.DataFrame(self.data_buffer)
============================================================
HOLYSHEEP ALTERNATIVE: Direct WebSocket via HolySheep
============================================================
class HolySheepWebSocketClient:
"""Alternative: HolySheep WebSocket mit besserer Performance"""
def __init__(self, api_key):
self.api_key = api_key
self.ws = None
self.data_buffer = []
def connect(self, symbols=["BTC-USDT"]):
"""Verbindet zu HolySheep WebSocket (niedrigere Latenz)"""
# HolySheep verwendet einen Proxy mit optimierter Latenz
ws_url = "wss://stream.holysheep.ai/v1/crypto/stream"
def on_message(ws, message):
data = json.loads(message)
self.data_buffer.append(data)
def on_open(ws):
# Authentifizierung
auth_msg = {
"type": "auth",
"api_key": self.api_key
}
ws.send(json.dumps(auth_msg))
# Symbols subscribed
subscribe_msg = {
"type": "subscribe",
"symbols": symbols,
"channels": ["candles", "trades"]
}
ws.send(json.dumps(subscribe_msg))
self.ws = websocket.WebSocketApp(
ws_url,
on_message=on_message,
on_open=on_open
)
def start(self):
"""Startet den Stream"""
thread = threading.Thread(target=lambda: self.ws.run_forever())
thread.daemon = True
thread.start()
============================================================
BEISPIEL-NUTZUNG
============================================================
def on_candle_received(candle):
"""Callback für jede neue Kerze"""
print(f"[{candle['timestamp']}] {candle['symbol']}: "
f"O={candle['open']:.2f} H={candle['high']:.2f} "
f"L={candle['low']:.2f} C={candle['close']:.2f}")
if __name__ == "__main__":
print("=" * 50)
print("OKX WebSocket Client Demo")
print("=" * 50)
# OKX WebSocket (kostenlos, aber langsamer)
client = OKXWebSocketClient(on_data_callback=on_candle_received)
client.connect(symbols=["BTC-USDT", "ETH-USDT"], channel="candle1m")
client.start()
print("\nStreaming für 30 Sekunden...")
import time
time.sleep(30)
# Daten abrufen
df = client.get_buffer()
if not df.empty:
print(f"\nEmpfangene Datenpunkte: {len(df)}")
print(df.tail())
client.stop()
print("\n✓ Demo abgeschlossen")
Praktische Backtesting-Beispiele
Beispiel 1: RSI-Überkauf-/Überverkaufs-Strategie
# RSI-Strategie Backtest
import pandas as pd
import numpy as np
def calculate_rsi(prices, period=14):
"""Berechnet den Relative Strength Index"""
delta = prices.diff()
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
return rsi
def backtest_rsi_strategy(data, rsi_period=14, oversold=30, overbought=70):
"""
Backtest der RSI-Überkauf-/Überverkaufs-Strategie
Kauft wenn RSI unter Oversold fällt
Verkauft wenn RSI über Overbought steigt
"""
df = data.copy()
df["rsi"] = calculate_rsi(df["close"], rsi_period)
# Signale
df["signal"] = 0
df.loc[df["rsi"] < oversold, "signal"] = 1 # Kauf
df.loc[df["rsi"] > overbought, "signal"] = -1 # Verkauf
# Position
df["position"] = df["signal"].shift(1).fillna(0)
# Returns
df["market_return"] = df["close"].pct_change()
df["strategy_return"] = df["market_return"] * df["position"]
# Kumulative Returns
df["cum_market"] = (1 + df["market_return"]).cumprod()
df["cum_strategy"] = (1 + df["strategy_return"]).cumprod()
return df
Nutzung
data = provider.fetch_ohlcv("BTC-USDT", "1h", limit=1000)
results = backtest_rsi_strategy(data)
print("RSI Strategie Performance:")
print(f" Market Return: {(results['cum_market'].iloc[-1] - 1) * 100:.2f}%")
print(f" Strategy Return: {(results['cum_strategy'].iloc[-1] - 1) * 100:.2f}%")
Anzahl der Trades
trades = results[results["signal"] != 0]
print(f" Anzahl Trades: {len(trades)}")
Häufige Fehler und Lösungen
Fehler 1: Rate-Limit-Überschreitung bei OKX API
Problem: "429 Too Many Requests" Fehler bei schnellen Abfragen.
# LÖSUNG: Implementierung von Exponential Backoff
import time
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_resilient_session():
"""Erstellt eine Session mit automatischer Retry-Logik"""
session = requests.Session()
retry_strategy = Retry(
total=5,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["HEAD", "GET", "OPTIONS", "POST"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("https://", adapter)
session.mount("http://", adapter)
return session
class RateLimitedClient:
"""OKX Client mit Ratenbegrenzung"""
def __init__(self, requests_per_second=10):
self.rps = requests_per_second
self.last_request = 0
self.session = create_resilient_session()
def wait_for_rate_limit(self):
"""Wartet bis Rate Limit erlaubt"""
min_interval = 1.0 / self.rps
elapsed = time.time() - self.last_request
if elapsed < min_interval:
time.sleep(min_interval - elapsed)
self.last_request = time.time()
def get(self, url, **kwargs):
"""GET mit Ratenbegrenzung und Retry"""
self.wait_for_rate_limit()
try:
response = self.session.get(url, **kwargs)
if response.status_code == 429:
# Rate limit erreicht, länger warten
retry_after = int(response.headers.get("Retry-After", 5))
print(f"Rate limit erreicht, warte {retry_after}s...")
time.sleep(retry_after)
return self.get(url, **kwargs)
return response
except Exception as e:
print(f"Anfrage fehlgeschlagen: {e}, Retry...")
time.sleep(2)
return self.get(url, **kwargs)
Nutzung
client = RateLimitedClient(requests_per_second=5) # Max 5 req/s
response = client.get("https://www.okx.com/api/v5/market/ticker?instId=BTC-USDT")
Fehler 2: Falsche Zeitstempel-Konvertierung
Problem: Historische Daten zeigen falsche Zeiten oder sind in der falschen Zeitzone.
# LÖSUNG: Konsistente Zeitstempel-Behandlung
import pandas as pd
from datetime import datetime, timezone
def parse_okx_timestamp(ts_str):
"""
Parst OKX-Timestamp korrekt (Millisekunden seit Unix-Epoche)
OKX gibt Zeitstempel als String zurück, z.B. "1699872000000"
"""
try:
# In Integer umwandeln (in Millisekunden)
ts_ms = int(ts_str)
# Prüfen ob es in Sekunden oder Millisekunden ist
if ts_ms > 1e12: # Millisekunden
dt = datetime.fromtimestamp(ts_ms / 1000, tz=timezone.utc)
else: # Sekunden
dt = datetime.fromtimestamp(ts_ms, tz=timezone.utc)
return dt
except (ValueError, TypeError) as e:
raise ValueError(f"Ungültiger Timestamp: {ts_str}") from e
def normalize_candle_data(candles_raw):
"""
Normalisiert Rohdaten von OKX in ein sauberes DataFrame