Created
August 15, 2024 15:52
-
-
Save stalequant/5fcd08647ac5a1b49415df787783558f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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