Apache Kafka für Tick-Daten: Streaming-Architektur für Trading.
Solange ein Skript an einem WebSocket hängt, reicht ein WebSocket. Sobald drei Komponenten parallel auf denselben Tick-Stream zugreifen wollen — Live-Strategie, Backtest-Recorder, Monitoring — wird die Frage drängend, wo der Stream eigentlich wirklich lebt. Kafka beantwortet sie.
Was Kafka eigentlich ist.
Kafka ist eine verteilte, persistente Log-basierte Streaming-Plattform. Klingt sperrig, ist im Kern simpel: Producer schreiben Nachrichten in Topics, Consumer lesen sie. Anders als bei klassischen Message-Queues bleiben Nachrichten im Topic, auch nachdem sie gelesen wurden — über Tage oder Wochen. Mehrere Consumer können denselben Stream unabhängig voneinander lesen, jeder mit eigenem Offset.
Genau das ist für Trading interessant: Sie pumpen die Ticks einmal rein und haben dauerhaft die Möglichkeit, beliebig viele Consumer parallel anzuhängen — ohne die Datenquelle (IBKR, Binance, etc.) erneut zu belasten.
Die Kern-Konzepte.
- Producer: Schickt Nachrichten in ein Topic. Bei Trading: der WebSocket-Adapter, der die Roh-Ticks vom Broker entgegennimmt.
- Consumer: Liest aus einem Topic. Bei Trading: die Live-Strategie, der Tick-Recorder für die DB, das Monitoring-System.
- Topic: Logischer Channel, z. B.
ticks.equities.usoderticks.crypto.binance. - Partition: Innerhalb eines Topics wird per Partition parallelisiert. Schlüssel (z. B. Symbol) bestimmt, in welche Partition eine Nachricht landet — Ordering bleibt pro Schlüssel garantiert.
- Consumer Group: Mehrere Consumer einer Gruppe teilen sich die Partitionen — horizontale Skalierung.
- Offset: Position eines Consumers im Log. Persistiert, sodass Consumer nach Neustart genau dort weiterlesen, wo sie aufgehört haben.
Use-Case: WebSocket → Kafka → N Consumer.
Architektur, die ich in größeren Setups einsetze:
[IBKR WebSocket] [Binance WebSocket]
| |
v v
+-----------+ +-----------+
| Producer | | Producer |
+-----------+ +-----------+
\ /
v v
+---------------------+
| Kafka |
| ticks.equities.us |
| ticks.crypto.bin |
+---------------------+
| | |
v v v
+---------+ +-------+ +----------+
|Strategie| |Record-| |Monitoring|
| Live | | er→DB | | Grafana |
+---------+ +-------+ +----------+
Drei unabhängige Consumer, ein einziger WebSocket pro Quelle. Wenn die Strategie crasht, bekommt der Recorder die Ticks trotzdem. Wenn ich eine vierte Strategie anhängen will, brauche ich keine zweite WebSocket-Connection — die Strategie wird einfach eine weitere Consumer-Group.
Producer in Python.
from confluent_kafka import Producer
import orjson, time
producer = Producer({
'bootstrap.servers': 'kafka:9092',
'linger.ms': 5,
'compression.type': 'lz4',
'acks': '1',
})
def on_tick(symbol, price, size, side, ts):
msg = orjson.dumps({
'symbol': symbol, 'price': price, 'size': size,
'side': side, 'ts': ts,
})
producer.produce(
topic='ticks.equities.us',
key=symbol.encode(), # Partition-Key: Ordering pro Symbol
value=msg,
)
# WebSocket-Loop ruft on_tick(...) auf
# Periodisch flushen:
while True:
producer.poll(1.0)
Consumer in Python.
from confluent_kafka import Consumer
import orjson
consumer = Consumer({
'bootstrap.servers': 'kafka:9092',
'group.id': 'strategy.mean_reversion_v3',
'auto.offset.reset': 'latest',
'enable.auto.commit': True,
})
consumer.subscribe(['ticks.equities.us'])
while True:
msg = consumer.poll(0.1)
if msg is None or msg.error():
continue
tick = orjson.loads(msg.value())
strategy.on_tick(tick)
Eine zweite Strategie bekommt schlicht eine andere group.id —
strategy.momentum_v1 — und liest denselben Stream unabhängig. Der
Recorder, der alle Ticks in TimescaleDB schreibt, ist eine dritte Group.
Das Ökosystem rundherum.
- Kafka Connect: Vorgefertigte Source-/Sink-Konnektoren. JDBC-Sink schreibt Topics direkt nach Postgres/Timescale — ohne dass Sie den Recorder selbst schreiben müssen.
- ksqlDB: Stream-Processing in SQL. Sie können live aus dem Tick-Stream 1-Minuten-Bars als materialisierten Stream berechnen.
- Schema Registry: Zentraler Speicher für Avro-/Protobuf-Schemas. Verhindert, dass ein Producer das Format ändert und alle Consumer kaputt sind. Für ernsthafte Setups Pflicht.
Performance.
Kafka ist auf Durchsatz optimiert. 1 Mio. Nachrichten pro Sekunde auf einem mittelgroßen Broker-Cluster sind alltäglich; mit getunter Konfiguration sind mehrere Millionen erreichbar. Für Trading-Tick-Daten ist das massive Overkill — ein einzelner Broker-Knoten reicht für die meisten Setups locker.
Latenz: Bei linger.ms=0 und acks=1 bewegt sich End-to-End
(Producer → Broker → Consumer) im einstelligen Millisekunden-Bereich. Für
Latenz-kritisches HFT ist das zu langsam — aber für Strategien mit Tick-bis-Order
im Bereich 50–500 ms ist Kafka unproblematisch.
Alternative: Redis Streams.
Wenn Kafka zu viel Maschinerie ist, sind Redis Streams die schlanke Alternative. Ein einzelner Redis-Container, ähnliches Konzept (Consumer Groups, Offsets/IDs), aber deutlich weniger Operations-Overhead. Sweet-Spot: Solo-Trader mit 2–3 Strategien, die parallel auf denselben Stream zugreifen.
Grenze: Redis Streams persistiert in RAM (mit AOF-Backup), Kafka auf Disk. Bei Volumen über einigen GB pro Tag oder Retention > 24 h wird Kafka praktisch.
Docker-Compose-Setup.
services:
zookeeper:
image: confluentinc/cp-zookeeper:7.6.0
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
kafka:
image: confluentinc/cp-kafka:7.6.0
depends_on: [zookeeper]
ports: ["9092:9092"]
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_LOG_RETENTION_HOURS: 168 # 7 Tage
KAFKA_LOG_SEGMENT_BYTES: 1073741824
KAFKA_COMPRESSION_TYPE: lz4
schema-registry:
image: confluentinc/cp-schema-registry:7.6.0
depends_on: [kafka]
environment:
SCHEMA_REGISTRY_HOST_NAME: schema-registry
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: kafka:9092
ports: ["8081:8081"]
tick-producer:
build: ./producer
depends_on: [kafka]
environment:
KAFKA_BROKERS: kafka:9092
IBKR_API_KEY: ${IBKR_API_KEY}
Für Produktion: KRaft-Modus statt Zookeeper (ab Kafka 3.5 stabil), mindestens drei Broker für Replikation, separates Disk-Volume für die Log-Segments. Aber für den Einstieg reicht obiges Single-Node-Setup vollkommen.
Wann Kafka lohnt — und wann nicht.
Kafka lohnt, wenn:
- Mehrere Strategien oder Services parallel auf denselben Stream zugreifen sollen.
- Sie Mandanten-Setups betreiben, in denen jede Strategie einen eigenen Container hat, aber alle dieselben Marktdaten brauchen.
- Sie Replay-Fähigkeit wollen (Strategie hat einen Bug → neu starten, vom letzten Offset oder von gestern Mittag aus replayen).
- Sie Stream-Processing (Bar-Building, Feature-Berechnung) nahe an der Quelle machen wollen.
Kafka lohnt nicht, wenn:
- Sie ein einzelner Trader mit einer Strategie auf einem VPS sind — der WebSocket direkt in die Strategie ist einfacher und schneller.
- Sie Latenzen unter 1 ms brauchen — dann sollten Sie ohnehin nicht über Python und Kafka reden.
- Ihr Team kein Interesse an einem zusätzlichen Stück Infrastruktur hat. Kafka will betrieben werden — Disks, Retention, Monitoring, Upgrades.
Meine Praxis.
In Solo-Setups: WebSocket → Strategie, direkt. Kein Kafka. In Multi-Strategie- und Mandanten-Setups: Kafka als zentrales Streaming-Backbone. Ein Producer pro Marktdaten-Quelle, Topics nach Asset-Class und Venue, jede Strategie eine eigene Consumer-Group. Recorder schreibt parallel alle Topics nach TimescaleDB für Backtest und Forensik.
Aufwand für das initiale Setup mit zwei Brokern und drei Consumern: rund eine
Woche, inklusive Schema-Registry und Monitoring. Was Sie danach bekommen, ist eine
Architektur, die mit jeder weiteren Strategie nicht teurer wird — sondern
schlicht eine neue group.id bekommt. Genau das macht den Unterschied
zwischen Bastelei und System.
Sie wollen aus einem WebSocket-Skript eine echte Streaming-Architektur machen? Erstgespräch buchen — wir entscheiden gemeinsam, ob Kafka, Redis Streams oder doch nur ein sauberer Direkt-Consumer der richtige Schritt ist.