Skip to content

Instantly share code, notes, and snippets.

@stalequant
Created August 15, 2024 15:52
Show Gist options
  • Save stalequant/5fcd08647ac5a1b49415df787783558f to your computer and use it in GitHub Desktop.
Save stalequant/5fcd08647ac5a1b49415df787783558f to your computer and use it in GitHub Desktop.
# CODE TO CALCULATE THE SPREAD AT A GIVEN DEPTH ON ALL CCXT EXCHANGES
# CREDIT TO @stalequant
# Python code by https://x.com/PythonLearnGang he is happy to help troubleshoot errors
import asyncio
import ccxt.pro
import matplotlib.pyplot as plt
import nest_asyncio
import numpy as np
import pandas as pd
SPOT_BASES = ['USDT', 'USD', 'USDC', 'EUR',]
FUT_BASES = ['USDT:USDT', 'USD:USD', 'USDC:USDC',]
TOP_50_COINS = ['BTC', 'ETH', 'SOL', 'XRP', 'TON',
'DOGE', 'ADA', 'TRX', 'AVAX', 'SHIB',
'BCH', 'DOT', 'LINK', 'LTC', 'NEAR',
'MATIC', 'PEPE', 'KAS', 'UNI', 'ICP',
'APT', 'XLM', 'ETC', 'XMR', 'SUI',
'STX', 'FIL', 'TAO', 'HBAR', 'MKR',
'ARB', 'ATOM', 'IMX', 'VET', 'INJ',
'WIF', 'OP', 'AAVE', 'AR']
TEST_SIZE = 5000
spreads = {}
#%%
nest_asyncio.apply()
def measure_spread(ob):
mid_px = (ob['asks'][0][0]
+ ob['bids'][0][0])/2
bid_vol = pd.DataFrame(ob['bids'])
bid_vol = bid_vol.iloc[:, :2]
bid_vol.columns = 'p', 'q'
bid_vol['n_sold'] = np.maximum(
np.minimum(bid_vol.q.cumsum(),
TEST_SIZE/mid_px)
- bid_vol.q.cumsum().shift()
.fillna(0), 0)
# COINS SOLD AT EACH DEPTH OF BIDS
avg_sell = sum(bid_vol.n_sold
* bid_vol.p
)/bid_vol.n_sold.sum()
# AVG PRICE OF SALES
if (sum(bid_vol.n_sold) < TEST_SIZE/mid_px*.9):
avg_sell = np.nan
ask_vol = pd.DataFrame(ob['asks'])
ask_vol = ask_vol.iloc[:, :2]
ask_vol.columns = 'p', 'q'
ask_vol['v'] = ask_vol.q*ask_vol.p
# value availible
ask_vol['d_paid'] = np.maximum(
np.minimum(ask_vol.v.cumsum(), TEST_SIZE)
- ask_vol.v.cumsum()
.shift().fillna(0), 0)
#Value purchase at each level
avg_buy = (sum(ask_vol.d_paid)
/ sum(ask_vol.d_paid
/ ask_vol.p))
if sum(ask_vol.d_paid) < TEST_SIZE*.9:
avg_buy = np.nan
return avg_buy/avg_sell-1
async def test_exchange(exchange_name):
api = getattr(ccxt.pro, exchange_name)()
await api.load_markets()
for trial in range(60):
for coin in TOP_50_COINS:
for base in SPOT_BASES+FUT_BASES:
try:
if coin+'/'+base in api.markets:
symbol = coin+'/'+base
else:
m = [a for a in api.markets
if coin in a
and a.endswith('/'+base)]
if m:
symbol = m[0]
else:
continue
try:
await asyncio.sleep(0.1)
ob = await asyncio.wait_for(
api.fetch_order_book(
symbol, limit=100),
5)
except Exception:
await asyncio.sleep(0.1)
ob = await asyncio.wait_for(
api.fetch_order_book(
symbol),
5)
spreads[api.name,
'spot'
if coin in SPOT_BASES
else 'fut',
coin,
base,
trial] = measure_spread(ob)
except Exception:
pass
await asyncio.sleep(60)
async def do_plots():
#%%
df = pd.Series(spreads)
df.index.names = ['Exchange', 'Market',
'Coin', 'Base', 'Trial']
df_by_pair = df.groupby(
['Exchange', 'Market', 'Coin', 'Base',]).median()
#Median spread across trials
df_by_coin = df_by_pair.groupby(['Exchange', 'Market',
'Coin',]).min()
#Best base for each coin
df_by_coin_mp = (df_by_coin.unstack(['Coin',])
.fillna(1000).stack(['Coin',]))
# Count missing coins as failures
df_by_exch = df_by_coin_mp.groupby(['Exchange',
'Market',]).median()
# Aggregate to the exch level
rrr = df_by_coin_mp.unstack(0).loc['fut']*1_00_00/2
rrr = rrr.loc[TOP_50_COINS]
rrr['count'] = range(len(rrr), 0, -1)
plt.figure(figsize=(6, 8))
plt.scatter(rrr['Binance'], rrr['count'],
color='orange', label='Binance')
plt.scatter(rrr['Bybit'], rrr['count'],
color='red', label='Bybit', )
plt.scatter(rrr['Hyperliquid'], rrr['count'],
color='lightseagreen', label='Hyperliquid')
plt.scatter(rrr['OKX'], rrr['count'], color='grey', label='OKX')
plt.ylabel('Coin')
plt.xlabel('Basis point slippage on $5000 order')
plt.xlim(0, 10)
# Rotate x-axis labels by 90 degrees
plt.legend()
plt.yticks(ticks=rrr['count'],
labels=rrr.index)
plt.show()
sss = (rrr.quantile([.25, .5, .75]).T.sort_values(.75)).iloc[:20]
sss['count'] = range(len(sss), 0, -1)
plt.figure(figsize=(6, 8))
plt.scatter(sss[.25], sss['count'], color='blue', label='Top 10')
plt.scatter(sss[.5], sss['count'], color='red', label='Top 20', )
plt.scatter(sss[.75], sss['count'], color='green', label='Top 30')
plt.ylabel('Venue')
plt.xlabel('Basis point slippage on $5000 order')
plt.xlim(0, 15)
# Rotate x-axis labels by 90 degrees
plt.legend()
plt.yticks(ticks=sss['count'],
labels=sss.index)
plt.show()
print((df_by_exch.loc[:, 'fut'].sort_values()
.iloc[:20]*100*100).round(2))
#%%
async def main():
for exchange_name in ccxt.pro.exchanges:
if exchange_name in {'binanceusdm', 'binancecoinm', }:
continue
await asyncio.sleep(1)
asyncio.ensure_future(test_exchange(exchange_name))
print('queued ' + exchange_name)
for _ in range(60):
await asyncio.sleep(60)
await do_plots()
asyncio.run(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment