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()