Bitcoin order book shape analysis
On the road to developing bitcoin alphas for algorithmic trading
Another area of alpha research I am interested in is Bitcoin trading. Before discussing alphas for Bitcoin, it makes sense to start looking at metrics we can derive from real-time order book data. To do so, I signed up for API access at Alpaca. They have streaming order book data for Bitcoin, which would be useful for real-time algorithmic trading. However, I will use REST calls to develop metrics and charts as it makes it easier to test. I decided to create metrics for the following:
- Bid-Ask Imbalance — When the bid-ask imbalance reaches a certain threshold, it can indicate that either buyers or sellers are dominating the market. For example, if the bid-ask imbalance is negative, there are more sellers than buyers, and the market may be bearish. Conversely, if the bid-ask imbalance is positive, there are more buyers than sellers, and the market may be bullish.
- Bid-Ask Spread — When the bid-ask spread is large, it can indicate low liquidity in the market. This can lead to higher volatility and wider bid-ask spreads in the future.
- Price Impact — The price impact signal can be used to identify market trends. When the price impact is high, it means that trades are significantly impacting the asset's price. This can indicate that there is high market interest and potential for a price movement. Conversely, when the price impact is low, it can mean that the market is relatively stable, and there may not be much movement in the near future.
- Bid-Ask Depth Signal: Traders can use bid-ask depth information to identify potential support and resistance levels. When the bid depth is high, it can indicate strong support for the asset at that price level. Conversely, when the ask depth is high, it can mean strong resistance at that price level.
Here is a python script to create these metrics from the Alpaca REST API. Please note that this script does not update the same chart but continuously creates a new one. A future goal would be to implement a streaming chart.
import requests
import pandas as pd
import json
from datetime import datetime
import matplotlib.pyplot as plt
import time
#url = 'https://data.alpaca.markets/v1beta2/crypto/latest/orderbooks?symbols=BTC/USD,ETH/BTC,ETH/USD'
url = 'https://data.alpaca.markets/v1beta2/crypto/latest/orderbooks?symbols=BTC/USD'
headers = {
'replace with your alpaca api key': '<KEY>',
'replace with your alpaca api secret': '<SECRET>'
}
# Create a DataFrame with columns 'Bid' and 'Ask'
df = pd.DataFrame(columns=['Bid', 'Ask', 'Bid Depth','Ask Depth', 'Timestamp', 'Imbalance','Mid-price','Spread','Price impact' ])
while True:
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
symbol = 'BTC/USD'
bids = []
asks = []
# Extract bid and ask data from response
for item in data['orderbooks'][symbol]['a']:
bid = item['p']
bid_qty = item['s']
bids.append([bid, bid_qty])
for item in data['orderbooks'][symbol]['b']:
ask = item['p']
ask_qty = item['s']
asks.append([ask, ask_qty])
# Get current timestamp
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Create DataFrame from bid and ask data
new_data = pd.DataFrame({'Bid': bids, 'Ask': asks, 'Timestamp': timestamp, 'Bid Depth':None,'Ask Depth':None, 'Imbalance':None,'Mid-price':None,
'Spread':None, 'Price impact':None })
# Compute bid and ask depths
new_data['Bid Depth'] = new_data['Bid'].cumsum(axis=0).apply(lambda x: x[1])
new_data['Ask Depth'] = new_data['Ask'].cumsum(axis=0).apply(lambda x: x[1])
# Compute bid-ask imbalance
new_data['Imbalance'] = (new_data['Bid Depth'] - df['Ask Depth']) / (new_data['Bid Depth'] + new_data['Ask Depth'])
# Compute mid-price and bid-ask spread
new_data['Mid-price'] = (new_data['Bid'].apply(lambda x: x[0]) +new_data['Ask'].apply(lambda x: x[0])) / 2
new_data['Spread'] = new_data['Ask'].apply(lambda x: x[0]) - new_data['Bid'].apply(lambda x: x[0])
# Compute price impact
new_data['Price impact'] = new_data['Bid Depth'].apply(lambda x: x**2) / new_data['Ask Depth'].apply(lambda x: x**2)
# Append new data to existing DataFrame
df = df.append(new_data, ignore_index=True)
# Plot bid and ask depths, bid-ask imbalance, and price impact
fig, ax = plt.subplots(2, 2, figsize=(12, 8))
ax[0, 0].plot(df['Bid Depth'], label='Bid Depth')
ax[0, 0].plot(df['Ask Depth'], label='Ask Depth')
ax[0, 0].legend()
ax[0, 0].set_xlabel('Order Book Position')
ax[0, 0].set_ylabel('Depth')
ax[0, 1].plot(df['Imbalance'])
ax[0, 1].set_xlabel('Order Book Position')
ax[0, 1].set_ylabel('Bid-Ask Imbalance')
ax[1, 0].plot(df['Price impact'])
ax[1, 0].set_xlabel('Order Book Position')
ax[1, 0].set_ylabel('Price Impact')
ax[1, 1].plot(df['Spread'])
ax[1, 1].set_xlabel('Order Book Position')
ax[1, 1].set_ylabel('Bid-Ask Spread')
plt.show()
else:
print(f'Error retrieving order book data: {response.status_code} - {response.text}')
time.sleep(1)
Some examples of charts I saved at different times and different days of streaming order book data
Notice that the bid-ask imbalance on the shape chart is negative, meaning sellers currently dominate the action. I captured the last hour of BTX price data from the coin desk, which corresponds to me grabbing the shape chart in the past hour from my script, and we can see the downward pressure on the price. Keep an eye on the bid-ask imbalance and see how it is correlated with price.
Of course, sellers do not always dominate the action. Buyers can be dominating as well. I took this chart a few days ago, and you can see that the positive bid-ask imbalance means buyers dominate.
We can also create a heat map of the correlations of these metrics
This order book shape metrics heatmap corresponds to the charts from 3–13–2023 with more sellers than buyers. You can see the most significant correlation with price impact is the bid depth at this point.
Again, things change quickly in the bitcoin world; here is a different heatmap I captured when there were more buyers than sellers.
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# Create the heatmap
sns.heatmap(df.corr(), annot=True, cmap='coolwarm', linewidths=0.5)
plt.show()
I hope you found this article interesting. Please be sure to clap and subscribe since, in a future article, I will examine using these metrics to create signals one can use in algorithmic trading.