Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/1.2.0/rich display #8

Merged
merged 4 commits into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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