Crypto Orderbook stream bad ERSX spread

I just started using the subscribe_crypto_orderbooks to stream orderbook data for btcusd and ethusd.
It looks like the two exchanges at the moment are ERSX and BNCU. Here are some graphs of the orderbooks I made (there is just 1 graph for now as i’m a new user). You can see the ERSX data is a bit off. Why are the asks crossing over? it looks like the data is doubling up maybe

Heres the code to generate a single plot. Notice there is “INPUTS” section where you can change symbol and exchange. If i use BNCU there is no crossover and the data looks good. ERSX crosses over for both BTC and ETH

import os
import json
import traceback
import matplotlib.pyplot as plt

from alpaca_trade_api.stream import Stream

home_folder = os.path.dirname(os.path.abspath(__file__))

with open(f"{home_folder}/settings.json") as file:
    settings = json.load(file)

API_KEY = settings["alpaca"]["pub"]
SECRET_KEY = settings["alpaca"]["sec"]
stream_client = Stream(API_KEY, SECRET_KEY)

#########  INPUTS  ###########
symbol = "ETHUSD"
exchange = "ERSX"
##############################


async def handler(ob):
    try:
        if ob.exchange == exchange:
            graph_ob(ob)
            await stream_client.stop_ws()
    except:
        traceback.print_exc()
        await stream_client.stop_ws()


def graph_ob(ob):
    plt.figure(figsize=(12, 8))
    # plot bids
    xbids = [point.p for point in ob.bids]
    ybids = [point.s for point in ob.bids]
    plt.plot(xbids, ybids, label="bids")
    # plot asks
    xasks = [point.p for point in ob.asks]
    yasks = [point.s for point in ob.asks]
    plt.plot(xasks, yasks, label="asks")

    plt.title(symbol + " " + exchange)
    plt.xlabel("prices", rotation=0)
    plt.ylabel("sizes", rotation=0)
    plt.legend(loc="upper right")
    plt.savefig(f"{home_folder}/{symbol}_{exchange}.png")
    plt.show()


stream_client.subscribe_crypto_orderbooks(handler, symbol)

stream_client.run()
1 Like

I did a bit of follow up work on this. Here’s a chart I made of all the crypto orderbooks you can get from BTC or ETH

About the chart

  • Plots on the left are BTC and plots on the right are ETH
  • First 3 rows are Streamed Websocket data where i just grab the first full orderbook, then stop the stream
  • Last row is the latest orderbook using the rest api, the Rest api doesn’t allow the exchange param but i think it’s clear that it is using BNCU exchange
  • First 2 rows are ERSX exchange
  • Third and likely (see above) fourth rows are BNCU exchange
  • the last 3 rows are all sharing the same x axis. So you can see that ERSX has a wider price range available

Observations

  • ERSX data has the same cross over issue where the asks data is messed up
  • BNCU data is better shaped but has significantly less orders (see sum in plot title, which is the sum of all sizes in the orderbook). I’m not sure if this is because ERSX is a more popular exchange or maybe because their order data is inaccurate

Questions

  • Can we please get the ERSX data fixed as they appear to be the bigger exchange?
  • Can we have an exchange param for the latest orderbooks?

Code
(this code is very dodgy as i was trying many different things, i can clean it up if anyone wants)

import os
import json
import traceback
import matplotlib.pyplot as plt
import requests

from alpaca_trade_api.stream import Stream
from alpaca_trade_api.rest import REST

home_folder = os.path.dirname(os.path.abspath(__file__))

with open(f"{home_folder}/settings.json") as file:
    settings = json.load(file)

API_KEY = settings["alpaca"]["pub"]
SECRET_KEY = settings["alpaca"]["sec"]
stream_client = Stream(API_KEY, SECRET_KEY)
# stream_client.stop_ws()


api = REST(
    settings["alpaca"]["pub"],
    settings["alpaca"]["sec"],
    settings["alpaca"]["url"],
    api_version="v2",
)


def main():
    global axes
    fig, axes = plt.subplots(4, 2, figsize=(12, 6))

    # the first 4 charts are websocket crypto endpoints

    charts_data = [
        {"axis": (0, 0), "symbol": "BTCUSD", "exchange": "ERSX", "sharex": False},
        {"axis": (0, 1), "symbol": "ETHUSD", "exchange": "ERSX", "sharex": False},
        # {"axis": (1, 0), "symbol": "BTCUSD", "exchange": "ERSX", "sharex": True},
        # {"axis": (1, 1), "symbol": "ETHUSD", "exchange": "ERSX", "sharex": True},
        {"axis": (2, 0), "symbol": "BTCUSD", "exchange": "BNCU", "sharex": True},
        {"axis": (2, 1), "symbol": "ETHUSD", "exchange": "BNCU", "sharex": True},
    ]

    for chart_data in charts_data:
        axis = chart_data["axis"]
        ax = axes[axis[0]][axis[1]]
        symbol = chart_data["symbol"]
        exchange = chart_data["exchange"]
        sharex = chart_data["sharex"]
        handler = get_handler(ax, symbol, exchange, sharex)
        stream_client.subscribe_crypto_orderbooks(handler, symbol)
        stream_client.run()

    # third row of charts is using rest crypto endpoint
    rest_symbols = ["BTC/USD", "ETH/USD"]
    for i, symbol in enumerate(rest_symbols):
        ob = get_rest_ob(symbol)
        graph_rest_ob(axes[3][i], ob, symbol)

    fig.tight_layout()
    plt.savefig(f"{home_folder}/stream_btc_eth_obs.png")
    plt.show()


def get_rest_ob(symbol):
    headers = {
        "APCA-API-KEY-ID": settings["alpaca"]["pub"],
        "APCA-API-SECRET-KEY": settings["alpaca"]["sec"],
    }
    url = (
        f"https://data.alpaca.markets/v1beta2/crypto/latest/orderbooks?"
        f"symbols={symbol}"  # &exchange=BNCU" <- cant do this
    )
    r = requests.get(url, headers=headers)
    result = r.json()
    ob = result["orderbooks"][symbol]
    return ob


def graph_rest_ob(ax, ob, symbol):
    # bid data
    xbids = [point["p"] for point in ob["b"]]
    ybids = [point["s"] for point in ob["b"]]
    # ask data
    xasks = [point["p"] for point in ob["a"]]
    yasks = [point["s"] for point in ob["a"]]
    # plot data
    ax.plot(xbids, ybids, label="bids")
    ax.plot(xasks, yasks, label="asks")
    # formatting
    ax.set_title(
        f"REST {symbol} exchange=??? sum={int(sum(ybids) + sum(yasks))} sharex"
    )
    ax.set_xlabel("prices", rotation=0)
    ax.set_ylabel("sizes", rotation=90)
    ax.legend(loc="upper right")
    ax.set_xlim([xbids[-1], xasks[-1]])


has_run = {}


def get_handler(ax, symbol, exchange, sharex):
    async def handler(ob):
        try:
            if ob.exchange == exchange:
                if (
                    f"{symbol}_{exchange}" not in has_run
                    or has_run[f"{symbol}_{exchange}"] == False
                ):
                    has_run[f"{symbol}_{exchange}"] = True
                    graph_ob(ax, ob, symbol, exchange, sharex)
                    if exchange == "ERSX":
                        col = 0 if symbol == "BTCUSD" else 1
                        graph_ob(axes[1][col], ob, symbol, exchange, True)
                    await stream_client.stop_ws()
        except:
            traceback.print_exc()
            await stream_client.stop_ws()

    return handler


def graph_ob(ax, ob, symbol, exchange, sharex):
    # bid data
    xbids = [point.p for point in ob.bids]
    ybids = [point.s for point in ob.bids]
    # ask data
    xasks = [point.p for point in ob.asks]
    yasks = [point.s for point in ob.asks]
    # plot data
    ax.plot(xbids, ybids, label="bids")
    ax.plot(xasks, yasks, label="asks")
    # formatting
    sharex_str = "sharex" if sharex else ""
    ax.set_title(
        f"STREAM {symbol} {exchange} sum={int(sum(ybids) + sum(yasks))} {sharex_str}"
    )
    ax.set_xlabel("prices", rotation=0)
    ax.set_ylabel("sizes", rotation=90)
    ax.legend(loc="upper right")
    # share with rest axis
    if sharex:
        # this shares the x axis of the charts with the x axis of the rest chart below it
        if symbol == "BTCUSD":
            ax.get_shared_x_axes().join(ax, axes[3][0])
        else:
            ax.get_shared_x_axes().join(ax, axes[3][1])


if __name__ == "__main__":
    main()

UPDATE the third row of charts no longer has data (the script works but the third row is blank for both BTCUSD and ETHUSD)
i.e. I can’t stream BNCU orderbooks…