Realtime Streaming

You can stream realtime market data via WebSocket if you have subscribed to the enterprise package.

The v2 WebSocket streams realtime market data by exchange: traded prices and bid/ask
quotes, for single instruments or a whole exchange with one subscription.

1. Connect

wscat -c "wss://ws.bavest.co/v2" -H "x-api-key: <API KEY>"

Your API key is validated during the handshake. An invalid or missing key rejects the
connection before it opens.

2. Topics

Everything you can subscribe to is a topic:

<channel>.<MIC>.<ISIN>      e.g.  trades.XMUN.DE0007164600
<channel>.<MIC>.*           e.g.  quotes.XMUN.*     (every instrument on the exchange)
PartMeaning
channeltrades = traded prices. quotes = bid/ask.
MICISO-10383 operating MIC of the exchange. Currently enabled: XMUN (Börse München / gettex).
ISIN12-character ISIN, or * to receive every instrument on the exchange.

Subscribing to an exchange that is not enabled returns an error frame per topic, e.g.
exchange XETR is not enabled.

3. Actions

{"action": "subscribe",   "topics": ["trades.XMUN.DE0007164600", "quotes.XMUN.*"]}
{"action": "unsubscribe", "topics": ["quotes.XMUN.*"]}
{"action": "ping"}

The server acknowledges with {"ev":"subscribed", ...} / {"ev":"unsubscribed", ...} /
{"ev":"pong"}. Invalid topics in a subscribe request are rejected individually with an
error frame; the valid ones still subscribe.

4. Messages

Every server frame carries an ev field. Data frames may arrive batched as a JSON
array
(one WebSocket message containing many ticks); control frames (acks, pong,
errors, snapshots) are single objects. Your client must handle both shapes.

Trade (ev: "T", channel trades)

Every trade is delivered; trades are never dropped or merged.

{"ev": "T", "topic": "trades.XMUN.DE0007164600", "x": "XMUN",
 "isin": "DE0007164600", "p": 123.45, "v": 100, "c": "EUR", "t": 1764236768617}
FieldDescription
pTraded price
vTrade volume (optional)
cCurrency
tEvent time, epoch milliseconds UTC
xExchange MIC

Quote (ev: "Q", channel quotes)

Bid/ask updates, conflated to roughly one message per instrument per second under load
(you always receive the newest state; superseded intermediate quotes are dropped).

{"ev": "Q", "topic": "quotes.XMUN.DE0007164600", "x": "XMUN",
 "isin": "DE0007164600", "bp": 123.40, "bs": 200, "ap": 123.45, "as": 100,
 "c": "EUR", "t": 1764236768617}
FieldDescription
bp / bsBid price / bid size
ap / asAsk price / ask size

Snapshot

Immediately after subscribing to an exact-ISIN topic you receive the last known value
with "snap": true, so you can render current state before the first live tick arrives.

Errors

{"ev": "error", "code": "exchange_not_enabled", "topic": "quotes.XETR.DE0007164600",
 "message": "exchange XETR is not enabled"}
CodeMeaning
bad_requestMessage was not valid JSON or had no usable action/topics
bad_topicTopic does not match <channel>.<MIC>.<ISIN or *>
exchange_not_enabledThe MIC is not available on your stream
too_many_topicsPer-connection topic limit reached

5. Connection lifecycle, heartbeats and reconnects

  • Keep-alive: the connection closes after 10 minutes of inactivity. Send
    {"action":"ping"} every 30 to 60 seconds; the server replies {"ev":"pong"}.
  • Maximum session length: 2 hours (platform limit). Your client must expect the
    socket to close and reconnect.
  • Reconnect best practice: reconnect with exponential backoff and jitter (start at
    1s, double up to a 30s cap, add random jitter). After reconnecting, resubscribe to your
    topics; the snapshots you receive bring you back to current state. Do not reconnect in
    a tight loop: connections are capped per API key, and a reconnect storm will be
    rejected at the handshake.
  • Subscription changes become effective within a few seconds. The subscribe snapshot
    reflects current state immediately.

6. Limits

LimitValue
Topics per connection200
Concurrent connections per API key20
Max message size (server to client)128 KB (data is chunked below this)

7. Quickstart (Python)

import asyncio, json, random
import websockets

URL = "wss://ws.bavest.co/v2"
TOPICS = ["trades.XMUN.*", "quotes.XMUN.DE0007164600"]

async def stream():
    backoff = 1
    while True:
        try:
            async with websockets.connect(
                URL, extra_headers={"x-api-key": "<API KEY>"}
            ) as ws:
                backoff = 1  # reset after a successful connect
                await ws.send(json.dumps({"action": "subscribe", "topics": TOPICS}))
                async def keepalive():
                    while True:
                        await asyncio.sleep(30)
                        await ws.send(json.dumps({"action": "ping"}))
                asyncio.create_task(keepalive())
                async for message in ws:
                    frames = json.loads(message)
                    for frame in frames if isinstance(frames, list) else [frames]:
                        if frame.get("ev") == "T":
                            print("trade", frame["isin"], frame["p"])
                        elif frame.get("ev") == "Q":
                            print("quote", frame["isin"], frame["bp"], frame["ap"])
        except Exception:
            await asyncio.sleep(backoff + random.random())
            backoff = min(backoff * 2, 30)

asyncio.run(stream())