Skip to content

Commit

Permalink
Merge pull request #8 from aoki-h-jp/feature/1.2.0/rich-display
Browse files Browse the repository at this point in the history
Feature/1.2.0/rich display
  • Loading branch information
aoki-h-jp authored Mar 16, 2023
2 parents 1bad19a + 5ae4aa7 commit 5bcd5a6
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 33 deletions.
99 changes: 99 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,72 @@ CREAM/USDT:USDT 0.0338 0.32 -0.2862
TWT/USDT:USDT 0.0295 0.32 -0.2905
TLM/USDT:USDT 0.0252 0.32 -0.2948
JASMY/USDT:USDT 0.0100 0.32 -0.3100

# display Top 5 large funding rate divergence on bybit one by one.
>>> fr.display_one_by_one_single_exchange(exchange='bybit', display_num=5)
------------------------------------------------
Revenue: -0.1663 / 100USDT
SELL: CTC/USDT:USDT Perp
BUY: CTC/USDT:USDT Spot
Funding Rate: 0.1537 %
Commission: 0.32 %
------------------------------------------------
Revenue: -0.17200000000000001 / 100USDT
SELL: CREAM/USDT:USDT Perp
BUY: CREAM/USDT:USDT Spot
Funding Rate: 0.1480 %
Commission: 0.32 %
------------------------------------------------
Revenue: -0.2107 / 100USDT
SELL: BOBA/USDT:USDT Perp
BUY: BOBA/USDT:USDT Spot
Funding Rate: 0.1093 %
Commission: 0.32 %
------------------------------------------------
Revenue: -0.2854 / 100USDT
SELL: TLM/USDT:USDT Perp
BUY: TLM/USDT:USDT Spot
Funding Rate: 0.0346 %
Commission: 0.32 %
------------------------------------------------
Revenue: -0.2953 / 100USDT
SELL: TOMO/USDT:USDT Perp
BUY: TOMO/USDT:USDT Spot
Funding Rate: 0.0247 %
Commission: 0.32 %

# display Top 5 large funding rate divergence on bybit one by one (minus FR).
>>> fr.display_one_by_one_single_exchange(exchange='bybit', display_num=5, minus=True)
------------------------------------------------
Revenue: -0.1458 / 100USDT
SELL: ARPA/USDT:USDT Options
BUY: ARPA/USDT:USDT Perp
Funding Rate: -0.2342 %
Commission: 0.38 %
------------------------------------------------
Revenue: -0.2569 / 100USDT
SELL: MASK/USDT:USDT Options
BUY: MASK/USDT:USDT Perp
Funding Rate: -0.1231 %
Commission: 0.38 %
------------------------------------------------
Revenue: -0.3056 / 100USDT
SELL: APE/USD:USDC Options
BUY: APE/USD:USDC Perp
Funding Rate: -0.0744 %
Commission: 0.38 %
------------------------------------------------
Revenue: -0.3158 / 100USDT
SELL: SWEAT/USD:USDC Options
BUY: SWEAT/USD:USDC Perp
Funding Rate: -0.0642 %
Commission: 0.38 %
------------------------------------------------
Revenue: -0.3166 / 100USDT
SELL: APE/USDT:USDT Options
BUY: APE/USDT:USDT Perp
Funding Rate: -0.0634 %
Commission: 0.38 %
```

### Display large FR divergence between CEX
Expand All @@ -72,6 +138,39 @@ HNT/USDT:USDT -0.030722 -0.0141 NaN NaN 0.0051 0.304442 0.335
OP/USDT:USDT -0.057856 -0.0235 -0.206011 -0.0589 -0.0162 -0.148713 0.189811 0.200 -0.010189
MKR/USDT:USDT 0.010000 0.0100 -0.056437 0.0104 0.0100 0.075530 0.131967 0.200 -0.068033
TON/USDT:USDT NaN NaN -0.023741 NaN 0.0100 -0.116483 0.126483 0.200 -0.073517

# Display Top 5 large funding rate divergence between multi exchange.
>>> fr.display_one_by_one_multi_exchanges(display_num=5)
------------------------------------------------
Revenue: 0.2184 USDT / 100USDT
SELL: coinex IOTA/USDT:USDT Perp (Funding Rate 0.3478 %)
BUY: okx IOTA/USDT:USDT Perp (Funding Rate -0.0706 %)
Divergence: 0.4184 %
Commission: 0.2000 %
------------------------------------------------
Revenue: 0.1191 USDT / 100USDT
SELL: coinex DASH/USDT:USDT Perp (Funding Rate 0.4267 %)
BUY: okx DASH/USDT:USDT Spot
Divergence: 0.4191 %
Commission: 0.3000 %
------------------------------------------------
Revenue: 0.1080 USDT / 100USDT
SELL: okx TON/USDT:USDT Perp (Funding Rate 0.0482 %)
BUY: coinex TON/USDT:USDT Perp (Funding Rate -0.2598 %)
Divergence: 0.3080 %
Commission: 0.2000 %
------------------------------------------------
Revenue: 0.0842 USDT / 100USDT
SELL: binance GMX/USDT:USDT Perp (Funding Rate 0.0100 %)
BUY: coinex GMX/USDT:USDT Perp (Funding Rate -0.2542 %)
Divergence: 0.2642 %
Commission: 0.1800 %
------------------------------------------------
Revenue: 0.0447 USDT / 100USDT
SELL: okx FIL/USDT:USDT Perp (Funding Rate 0.2416 %)
BUY: gate FIL/USDT:USDT Perp (Funding Rate -0.0031 %)
Divergence: 0.2447 %
Commission: 0.2000 %
```


Expand Down
9 changes: 7 additions & 2 deletions examples/get_large_divergence_multi_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
if __name__ == '__main__':
fr = FundingRateArbitrage()
# Display Top 5 large funding rate divergence between multi exchange.
print(fr.display_large_divergence_multi_exchange(display_num=5, sorted_by='divergence'))
# print(fr.display_large_divergence_multi_exchange(display_num=5, sorted_by='divergence'))

# TODO: Errors occur when running consecutively.
# ccxt.base.errors.BadRequest: binance {"code":-1104,"msg":"Not all sent parameters were read; read '0' parameter(s) but was sent '1'."}
# Display Top 5 large funding rate divergence between multi exchange sorted by revenue.
print(fr.display_large_divergence_multi_exchange(display_num=5, sorted_by='revenue'))
# print(fr.display_large_divergence_multi_exchange(display_num=5, sorted_by='revenue'))

# Display Top 5 large funding rate divergence between multi exchange.
fr.display_one_by_one_multi_exchanges(display_num=5)
8 changes: 7 additions & 1 deletion examples/get_large_divergence_single_exchange.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
An example of getting large divergence by single exchange.
An example of displaying large divergence by single exchange.
"""
from frarb import FundingRateArbitrage

Expand All @@ -11,3 +11,9 @@

# Display Top 5 large funding rate divergence on bybit (minus FR).
print(fr.display_large_divergence_single_exchange(exchange='bybit', display_num=5, minus=True))

# Display Top 5 large funding rate divergence on binance one by one.
fr.display_one_by_one_single_exchange(exchange='binance', display_num=5)

# Display Top 5 large funding rate divergence on bybit one by one (minus FR).
fr.display_one_by_one_single_exchange(exchange='bybit', display_num=5, minus=True)
143 changes: 114 additions & 29 deletions frarb/frarb.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
from logging import getLogger
"""
Main class of funding-rate-arbitrage
"""
import logging
import ccxt
from rich import print
from rich.logging import RichHandler
from ccxt import ExchangeError
import pandas as pd

logger = getLogger(__name__)
logging.basicConfig(
level=logging.INFO,
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler(rich_tracebacks=True)]
)
log = logging.getLogger("rich")


class FundingRateArbitrage:
Expand Down Expand Up @@ -32,7 +43,7 @@ def fetch_all_funding_rate(exchange: str) -> dict:
try:
fr_d[p] = ex.fetch_funding_rate(p)['fundingRate']
except ExchangeError:
logger.exception(f'{p} is not perp.')
log.exception(f'{p} is not perp.')
return fr_d

def display_large_divergence_single_exchange(self, exchange: str, minus=False, display_num=10) -> pd.DataFrame:
Expand All @@ -45,6 +56,41 @@ def display_large_divergence_single_exchange(self, exchange: str, minus=False, d
Returns (pd.DataFrame): DataFrame sorted by large funding rate divergence.
"""
return self.get_large_divergence_dataframe_single_exchange(exchange=exchange, minus=minus)\
.sort_values(by='Funding Rate [%]', ascending=minus).head(display_num)

def display_large_divergence_multi_exchange(self, display_num=10, sorted_by='revenue') -> pd.DataFrame:
"""
Display large funding rate divergence between multi CEX.
"multi CEX" refers to self.exchanges.
Args:
display_num (int): Number of display.
sorted_by (str): Sorted by "revenue" or "divergence"
Returns (pd.DataFrame): DataFrame sorted by large funding rate divergence.
"""
if sorted_by == 'revenue':
sorted_by = 'Revenue [/100 USDT]'
elif sorted_by == 'divergence':
sorted_by = 'Divergence [%]'
else:
log.error(f'{sorted_by} is not available.')
raise KeyError

return self.get_large_divergence_dataframe_multi_exchanges()\
.sort_values(by=sorted_by, ascending=False).head(display_num)

def get_large_divergence_dataframe_single_exchange(self, exchange: str, minus=False):
"""
Get large funding rate divergence on single CEX.
Args:
exchange (str): Name of exchange (binance, bybit, ...)
minus (bool): Sorted by minus FR or plus FR.
Returns (pd.DataFrame): large funding rate divergence DataFrame.
"""
fr = self.fetch_all_funding_rate(exchange=exchange)
columns = ['Funding Rate [%]', 'Commission [%]', 'Revenue [/100 USDT]']
Expand All @@ -65,36 +111,23 @@ def display_large_divergence_single_exchange(self, exchange: str, minus=False, d
df = pd.concat([sr_fr, sr_cm, sr_rv], axis=1)
df.index = list(fr.keys())
df.columns = columns
return df.sort_values(by=columns[0], ascending=minus).head(display_num)
return df

def display_large_divergence_multi_exchange(self, display_num=10, sorted_by='revenue') -> pd.DataFrame:
def get_large_divergence_dataframe_multi_exchanges(self):
"""
Display large funding rate divergence between multi CEX.
Get large funding rate divergence between multi CEX.
"multi CEX" refers to self.exchanges.
Args:
display_num (int): Number of display.
sorted_by (str): Sorted by "revenue" or "divergence"
Returns (pd.DataFrame): DataFrame sorted by large funding rate divergence.
Returns (pd.DataFrame): large funding rate divergence DataFrame.
"""
if sorted_by == 'revenue':
sorted_by = 'Revenue [/100 USDT]'
elif sorted_by == 'divergence':
sorted_by = 'Divergence [%]'
else:
logger.error(f'{sorted_by} is not available.')
raise KeyError

df = pd.DataFrame()
for ex in self.exchanges:
logger.info(f'fetching {ex}')
log.info(f'fetching {ex}')
fr = self.fetch_all_funding_rate(exchange=ex)
df_ex = pd.DataFrame(fr.values(), index=list(fr.keys()), columns=[ex]).T
df = pd.concat([df, df_ex])
df = df.T * 100

diff_list = []
diff_d = {}
for i, data in df.iterrows():
diff = data.max() - data.min()
Expand All @@ -119,7 +152,7 @@ def display_large_divergence_multi_exchange(self, display_num=10, sorted_by='rev
else:
try:
max_commission = self.get_commission(exchange=max_fr_exchange, trade='options') + \
self.get_commission(exchange=max_fr_exchange, trade='spot')
self.get_commission(exchange=max_fr_exchange, trade='spot')
min_commission = self.get_commission(exchange=min_fr_exchange, trade='futures')
except KeyError:
max_commission = self.get_commission(exchange=max_fr_exchange, trade='futures')
Expand All @@ -135,7 +168,59 @@ def display_large_divergence_multi_exchange(self, display_num=10, sorted_by='rev
df_rv = pd.DataFrame(revenue, index=list(comm_d.keys()), columns=['Revenue [/100 USDT]']).T
df = pd.concat([df.T, df_rv]).T

return df.sort_values(by=sorted_by, ascending=False).head(display_num)
return df

def display_one_by_one_single_exchange(self, exchange: str, minus=False, display_num=10):
df = self.get_large_divergence_dataframe_single_exchange(exchange=exchange, minus=minus)
# TODO: Check perp or spot or options exists on CEX.
for i in df.sort_values(by='Funding Rate [%]', ascending=minus).head(display_num).index:
print('------------------------------------------------')
revenue = df.loc[i]["Revenue [/100 USDT]"]
if revenue > 0:
print(f'[bold deep_sky_blue1]Revenue: {revenue} / 100USDT[/]')
else:
print(f'[bold red]Revenue: {revenue} / 100USDT[/]')
if minus:
print(f'[bold red]SELL: {i} Options[/]')
print(f'[bold blue]BUY: {i} Perp[/]')
else:
print(f'[bold red]SELL: {i} Perp[/]')
print(f'[bold blue]BUY: {i} Spot[/]')
print(f'Funding Rate: {df.loc[i]["Funding Rate [%]"]:.4f} %')
print(f'Commission: {df.loc[i]["Commission [%]"]} %')

def display_one_by_one_multi_exchanges(self, display_num=10, sorted_by='revenue'):
if sorted_by == 'revenue':
sorted_by = 'Revenue [/100 USDT]'
elif sorted_by == 'divergence':
sorted_by = 'Divergence [%]'
else:
log.error(f'{sorted_by} is not available.')
raise KeyError
df = self.get_large_divergence_dataframe_multi_exchanges()
# TODO: Check perp or spot or options exists on CEX.
for i in df.sort_values(by=sorted_by, ascending=False).head(display_num).index:
print('------------------------------------------------')
revenue = df.loc[i]["Revenue [/100 USDT]"]
if revenue > 0:
print(f'[bold deep_sky_blue1]Revenue: {revenue:.4f} USDT / 100USDT[/]')
else:
print(f'[bold red]Revenue: {revenue:.4f} USDT / 100USDT[/]')
max_fr_exchange = df.loc[i][:-3].idxmax()
min_fr_exchange = df.loc[i][:-3].idxmin()
max_fr = df.loc[i][:-3].max()
min_fr = df.loc[i][:-3].min()
if max_fr > 0 and min_fr > 0:
print(f'[bold red]SELL: {max_fr_exchange} {i} Perp (Funding Rate {max_fr:.4f} %)[/]')
print(f'[bold blue]BUY: {min_fr_exchange} {i} Spot[/]')
elif max_fr > 0 > min_fr:
print(f'[bold red]SELL: {max_fr_exchange} {i} Perp (Funding Rate {max_fr:.4f} %)[/]')
print(f'[bold blue]BUY: {min_fr_exchange} {i} Perp (Funding Rate {min_fr:.4f} %)[/]')
else:
print(f'[bold red]SELL: {max_fr_exchange} {i} Options[/]')
print(f'[bold blue]BUY: {min_fr_exchange} {i} Perp (Funding Rate {min_fr:.4f} %)[/]')
print(f'Divergence: {df.loc[i]["Divergence [%]"]:.4f} %')
print(f'Commission: {df.loc[i]["Commission [%]"]:.4f} %')

def get_exchanges(self) -> list:
"""
Expand Down Expand Up @@ -192,7 +277,7 @@ def get_commission(exchange: str, trade: str, taker=True, by_token=False) -> flo
elif trade == 'options':
return 0.02
else:
logger.error(f'{trade} is not available on {exchange}.')
log.error(f'{trade} is not available on {exchange}.')
raise KeyError

# https://www.bybit.com/ja-JP/help-center/bybitHC_Article?id=360039261154&language=ja
Expand All @@ -207,7 +292,7 @@ def get_commission(exchange: str, trade: str, taker=True, by_token=False) -> flo
elif trade == 'options':
return 0.03
else:
logger.error(f'{trade} is not available on {exchange}.')
log.error(f'{trade} is not available on {exchange}.')
raise KeyError

# https://www.okx.com/fees
Expand All @@ -228,7 +313,7 @@ def get_commission(exchange: str, trade: str, taker=True, by_token=False) -> flo
else:
return 0.02
else:
logger.error(f'{trade} is not available on {exchange}.')
log.error(f'{trade} is not available on {exchange}.')
raise KeyError

# https://www.bitget.com/ja/rate/
Expand All @@ -244,7 +329,7 @@ def get_commission(exchange: str, trade: str, taker=True, by_token=False) -> flo
else:
return 0.017
else:
logger.error(f'{trade} is not available on {exchange}.')
log.error(f'{trade} is not available on {exchange}.')
raise KeyError

# https://www.gate.io/ja/fee
Expand All @@ -260,7 +345,7 @@ def get_commission(exchange: str, trade: str, taker=True, by_token=False) -> flo
else:
return 0.015
else:
logger.error(f'{trade} is not available on {exchange}.')
log.error(f'{trade} is not available on {exchange}.')
raise KeyError

# https://www.coinex.zone/fees?type=spot&market=normal
Expand All @@ -276,5 +361,5 @@ def get_commission(exchange: str, trade: str, taker=True, by_token=False) -> flo
else:
return 0.03
else:
logger.error(f'{trade} is not available on {exchange}.')
log.error(f'{trade} is not available on {exchange}.')
raise KeyError
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
ccxt
pandas
pandas
rich

0 comments on commit 5bcd5a6

Please sign in to comment.