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.1.0/get divergence multi cex #6

Merged
merged 3 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
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,27 @@ TLM/USDT:USDT 0.0252 0.32 -0.2948
JASMY/USDT:USDT 0.0100 0.32 -0.3100
```

### Display large FR divergence between CEX
```python
# display large funding rate divergence between CEX.
>>> fr.display_large_divergence_multi_exchange(display_num=5, sorted_by='divergence')
binance bybit okx bitget gate coinex Divergence [%] Commission [%] Revenue [/100 USDT]
FIL/USDT:USDT -0.008948 -0.0229 -0.334535 -0.0084 -0.0240 -0.737473 0.729073 0.202 0.527073
HNT/USDT:USDT -0.023885 -0.0125 NaN NaN 0.0056 0.304442 0.328327 0.180 0.148327
WAXP/USDT:USDT NaN NaN NaN NaN 0.0100 0.205733 0.195733 0.500 -0.304267
AXS/USDT:USDT -0.021292 -0.0385 -0.205174 -0.0212 -0.0282 -0.215217 0.194017 0.202 -0.007983
OP/USDT:USDT -0.060397 -0.0228 -0.206011 -0.0601 -0.0147 -0.148713 0.191311 0.200 -0.008689

# sorted by revenue.
>>> fr.display_large_divergence_multi_exchange(display_num=5, sorted_by='revenue')
binance bybit okx bitget gate coinex Divergence [%] Commission [%] Revenue [/100 USDT]
FIL/USDT:USDT -0.004703 -0.0232 -0.334535 -0.0047 -0.0245 -0.737473 0.732773 0.202 0.530773
HNT/USDT:USDT -0.030722 -0.0141 NaN NaN 0.0051 0.304442 0.335164 0.180 0.155164
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
```


## Disclaimer
This project is for educational purposes only. You should not construe any such information or other material as legal,
Expand Down
13 changes: 13 additions & 0 deletions examples/get_large_divergence_multi_exchange.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""
An example of getting large divergence between multi exchange.
"""
from frarb import FundingRateArbitrage


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'))

# 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'))
95 changes: 91 additions & 4 deletions frarb/frarb.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from logging import getLogger
import ccxt
from ccxt import ExchangeError
import pandas as pd

logger = getLogger(__name__)


class FundingRateArbitrage:
def __init__(self):
Expand All @@ -23,7 +27,13 @@ def fetch_all_funding_rate(exchange: str) -> dict:
ex = getattr(ccxt, exchange)()
info = ex.load_markets()
perp = [p for p in info if info[p]['linear']]
return {p: ex.fetch_funding_rate(p)['fundingRate'] for p in perp}
fr_d = {}
for p in perp:
try:
fr_d[p] = ex.fetch_funding_rate(p)['fundingRate']
except ExchangeError:
logger.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 @@ -39,9 +49,10 @@ def display_large_divergence_single_exchange(self, exchange: str, minus=False, d
fr = self.fetch_all_funding_rate(exchange=exchange)
columns = ['Funding Rate [%]', 'Commission [%]', 'Revenue [/100 USDT]']
sr_fr = pd.Series(list(fr.values())) * 100
# TODO: Check perp or spot or options exists on CEX.
if minus:
cm = self.get_commission(exchange=exchange, trade='futures', taker=self.is_taker, by_token=self.by_token)\
+ self.get_commission(exchange=exchange, trade='options', taker=self.is_taker, by_token=self.by_token)\
cm = self.get_commission(exchange=exchange, trade='futures', taker=self.is_taker, by_token=self.by_token) \
+ self.get_commission(exchange=exchange, trade='options', taker=self.is_taker, by_token=self.by_token) \
+ self.get_commission(exchange=exchange, trade='spot', taker=self.is_taker, by_token=self.by_token)
sr_cm = pd.Series([cm * 2 for i in range(len(sr_fr))])
sr_rv = abs(sr_fr) - sr_cm
Expand All @@ -56,6 +67,76 @@ def display_large_divergence_single_exchange(self, exchange: str, minus=False, d
df.columns = columns
return df.sort_values(by=columns[0], 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:
logger.error(f'{sorted_by} is not available.')
raise KeyError

df = pd.DataFrame()
for ex in self.exchanges:
logger.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()
diff_d[i] = diff

df_diff = pd.DataFrame(diff_d.values(), index=list(diff_d.keys()), columns=['Divergence [%]']).T
df = pd.concat([df.T, df_diff]).T

comm_list = []
for i in df.index:
max_fr_exchange = df.loc[i][:-1].idxmax()
min_fr_exchange = df.loc[i][:-1].idxmin()
max_fr = df.loc[i][:-1].max()
min_fr = df.loc[i][:-1].min()
# TODO: Check perp or spot or options exists on CEX.
if max_fr >= 0 and min_fr >= 0:
min_commission = self.get_commission(exchange=min_fr_exchange, trade='spot')
max_commission = self.get_commission(exchange=max_fr_exchange, trade='futures')
elif max_fr >= 0 > min_fr:
max_commission = self.get_commission(exchange=max_fr_exchange, trade='futures')
min_commission = self.get_commission(exchange=min_fr_exchange, trade='futures')
else:
try:
max_commission = self.get_commission(exchange=max_fr_exchange, trade='options') + \
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')
min_commission = self.get_commission(exchange=min_fr_exchange, trade='futures')
sum_of_commission = 2 * (max_commission + min_commission)
comm_list.append(sum_of_commission)

comm_d = {index: commission for index, commission in zip(df.index, comm_list)}
df_comm = pd.DataFrame(comm_d.values(), index=list(comm_d.keys()), columns=['Commission [%]']).T
df = pd.concat([df.T, df_comm]).T

revenue = [diff_value - comm_value for diff_value, comm_value in zip(diff_d.values(), comm_d.values())]
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)

def get_exchanges(self) -> list:
"""
Get a list of exchanges.
Expand Down Expand Up @@ -111,6 +192,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}.')
raise KeyError

# https://www.bybit.com/ja-JP/help-center/bybitHC_Article?id=360039261154&language=ja
Expand All @@ -125,6 +207,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}.')
raise KeyError

# https://www.okx.com/fees
Expand All @@ -145,6 +228,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}.')
raise KeyError

# https://www.bitget.com/ja/rate/
Expand All @@ -160,6 +244,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}.')
raise KeyError

# https://www.gate.io/ja/fee
Expand All @@ -175,10 +260,11 @@ 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}.')
raise KeyError

# https://www.coinex.zone/fees?type=spot&market=normal
if exchange == 'gate':
if exchange == 'coinex':
if trade == 'spot':
if by_token:
return 0.16
Expand All @@ -190,4 +276,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}.')
raise KeyError