asyncio für Trading-Bots: Event-Loop, WebSockets, Order-Streams.
Ein Trading-Bot, der mehrere Märkte gleichzeitig beobachtet, auf WebSocket-Ticks reagiert und parallel Orders rausschickt, läuft sauber nur mit asyncio. Threads sind in Python zu teuer, Multiprocessing zu schwerfällig. Aber asyncio richtig zu nutzen ist anspruchsvoll — und ein einziger blockierender Aufruf killt den ganzen Bot.
Warum asyncio für Trading.
Trading-Workloads sind I/O-gebunden: Sie warten auf Marktdaten, Order-Confirmations, REST-Responses. Genau hier glänzt asyncio. Statt für jeden WebSocket einen Thread zu starten (und unter dem GIL keinen echten Parallelismus zu bekommen), läuft alles auf einer Event-Loop. Ein Single-Thread-Bot kann problemlos 20 Symbole parallel tracken — bei minimaler CPU-Last.
Wichtig: asyncio ist nicht schneller für CPU-Arbeit. Wer Indikatoren oder ML-Modelle on-the-fly berechnet, muss diese Last in einen Worker-Pool auslagern (siehe weiter unten). asyncio koordiniert nur das Warten — die Rechenarbeit muss anderswo passieren.
Grundstruktur eines async-Bots.
import asyncio
import websockets
import json
class TradingBot:
def __init__(self, symbols):
self.symbols = symbols
self.prices = {}
self.order_queue = asyncio.Queue()
async def market_data_feed(self, symbol):
url = f'wss://stream.exchange.com/ws/{symbol}@trade'
async for ws in websockets.connect(url):
try:
async for msg in ws:
data = json.loads(msg)
self.prices[symbol] = float(data['p'])
await self.on_tick(symbol, data)
except websockets.ConnectionClosed:
continue # Auto-Reconnect
async def on_tick(self, symbol, data):
# Strategie-Logik
if self.should_trade(symbol):
await self.order_queue.put({'symbol': symbol, 'side': 'buy'})
async def order_worker(self):
while True:
order = await self.order_queue.get()
await self.send_order(order)
async def run(self):
feeds = [self.market_data_feed(s) for s in self.symbols]
await asyncio.gather(*feeds, self.order_worker())
bot = TradingBot(['BTCUSDT', 'ETHUSDT', 'SOLUSDT'])
asyncio.run(bot.run())
Diese Struktur trennt drei Verantwortlichkeiten: Datenempfang (pro Symbol ein Feed),
Strategie-Logik (on_tick) und Order-Versand (separater Worker, der
aus einer Queue konsumiert). Sauberer Code, klare Verantwortlichkeiten, und jedes
Element kann unabhängig getestet werden.
Die häufigsten Fallen.
Falle 1: Blockierende Aufrufe
Der gefährlichste Fehler ist eine synchrone Funktion, die in einer Coroutine
aufgerufen wird. time.sleep(1) blockiert den gesamten Event-Loop —
während dieser Sekunde verarbeitet der Bot weder Marktdaten noch Orders. Genauso
requests.get, pandas.read_sql oder ein synchroner
File-Read.
Faustregel: in async-Code keine synchronen I/O-Bibliotheken. Stattdessen
httpx oder aiohttp für HTTP, asyncpg oder
aiomysql für Datenbanken, aiofiles für Files.
Falle 2: Unhandled Exceptions
Eine Exception in einem Task, die nicht awaitet wird, verschwindet still — der Task stoppt, der Rest läuft weiter, und Sie merken erst nach Stunden, dass ein Feed tot ist. Lösung: Tasks immer mit Exception-Handlern wrappen.
import asyncio
import logging
async def safe_task(coro, name):
try:
await coro
except asyncio.CancelledError:
raise
except Exception as e:
logging.exception(f'Task {name} crashed: {e}')
# Optional: Alarm an Telegram / Pagerduty
async def main():
tasks = [
asyncio.create_task(safe_task(feed_btc(), 'btc')),
asyncio.create_task(safe_task(feed_eth(), 'eth')),
asyncio.create_task(safe_task(order_worker(), 'orders')),
]
await asyncio.gather(*tasks)
Falle 3: CPU-Bound im Event-Loop
Indikator-Berechnung, ML-Inference, große DataFrame-Operationen — alles, was mehr
als ein paar Millisekunden CPU braucht, blockiert den Loop. Bei einem Tick alle
10 ms ist eine 50-ms-Berechnung katastrophal. Lösung: asyncio.to_thread
oder ein expliziter ProcessPoolExecutor.
import asyncio
from concurrent.futures import ProcessPoolExecutor
executor = ProcessPoolExecutor(max_workers=4)
def heavy_signal_calc(prices):
# ML-Modell, große Rolling-Stats, etc.
return signal
async def on_tick(symbol, tick):
loop = asyncio.get_running_loop()
signal = await loop.run_in_executor(
executor, heavy_signal_calc, prices_history[symbol]
)
if signal > 0.7:
await place_order(symbol)
WebSocket-Reconnects und Heartbeats.
Produktiv läuft kein WebSocket 24/7 ohne Disconnect. Netzwerk-Schluckauf, Server-
Restarts, Exchange-Wartung — Reconnects sind die Norm. Die Standard-Bibliothek
websockets kann das, wenn man den Generator-Modus nutzt
(async for ws in websockets.connect(url)). Wichtig ist außerdem ein
eigener Heartbeat: viele Exchanges schließen idle Connections nach 60 Sekunden.
Zweiter wichtiger Punkt: nach einem Reconnect ist Ihr lokaler Order-State möglicherweise veraltet. Best Practice ist ein Resync-Schritt nach jedem Reconnect, der die offenen Orders und Positionen vom Exchange neu abholt.
Backpressure und Queue-Limits.
Wenn der Markt verrückt spielt, kommen mehr Ticks rein, als Sie verarbeiten können.
Eine unbegrenzte Queue füllt sich, der Speicher wächst, irgendwann crasht der Bot.
asyncio.Queue(maxsize=N) setzt eine Obergrenze. Bei vollem Buffer können
Sie ältere Ticks droppen, was für die meisten Trading-Strategien sinnvoller ist
als veraltete Daten zu verarbeiten.
import asyncio
tick_queue = asyncio.Queue(maxsize=1000)
async def producer(ws):
async for msg in ws:
try:
tick_queue.put_nowait(msg)
except asyncio.QueueFull:
# Drop oldest, put new
try:
tick_queue.get_nowait()
except asyncio.QueueEmpty:
pass
tick_queue.put_nowait(msg)
Testing und Monitoring.
Async-Code ist schwerer zu testen als synchroner. pytest-asyncio hilft,
aber die Disziplin ist eine andere: Sie müssen Mocks für WebSockets bauen, Timing-
Effekte simulieren, Reconnect-Szenarien durchspielen. Eine Investition, die sich
lohnt — ein Bug in einem Live-Trading-Bot kostet schnell vierstellig.
Im Produktivbetrieb sind zwei Metriken kritisch: Event-Loop-Lag (wie lange dauert es, bis ein Task scheduled wird?) und Queue-Tiefe (wie weit hinkt die Verarbeitung hinter den Inputs hinterher?). Beides sollte permanent geloggt werden — am besten nach Prometheus oder Influx.
Wann asyncio nicht passt.
Drei Fälle, in denen asyncio nicht der richtige Hammer ist: erstens reine Backtests — hier ist Vektorisierung in Pandas/Polars schneller; zweitens ultra-low-latency-Setups, wo Python überhaupt nicht mehr passt und Sie zu C++, Rust oder MQL5 wechseln müssen; drittens triviale Bots, die alle paar Minuten eine REST-API aufrufen — da reicht ein simpler Sync-Loop.
Fazit.
asyncio ist das richtige Werkzeug für Trading-Bots mittlerer Komplexität: mehrere Feeds, Multi-Asset, REST + WebSocket parallel. Die Lernkurve ist real — die Mental-Models von „Was darf ich awaiten?" und „Was blockiert den Loop?" brauchen Zeit. Aber wer einmal einen sauberen async-Bot gebaut hat, schreibt nie wieder Trading-Code mit Threads.
Sie planen einen Trading-Bot mit Multi-Asset-Anbindung? Erstgespräch buchen — wir bauen die Architektur, die nicht in der ersten Live-Woche umkippt.