Skip to content

Commit

Permalink
Merge pull request #7 from aoki-h-jp/release/1.1.0
Browse files Browse the repository at this point in the history
Release/1.1.0
  • Loading branch information
aoki-h-jp authored Mar 16, 2023
2 parents cb75d6a + 1bad19a commit 49c1688
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 4 deletions.
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@ This library can detect perpetual contract with a large divergence in funding ra


```bash

git clone https://github.com/aoki-h-jp/funding-rate-arbitrage.git
pip install funding-rate-arbitrage

```

## Usage
### Fetch FR & commission
```python
from frarb import FundingRateArbitrage

Expand All @@ -42,6 +41,39 @@ fr_binance = fr.fetch_all_funding_rate(exchange='binance')
cm_binance = fr.get_commission(exchange='binance', trade='futures', taker=False)
```

### Display large FR divergence on single CEX
```python
# display large funding rate divergence on bybit
>>> fr.display_large_divergence_single_exchange(exchange='bybit', display_num=5)
Funding Rate [%] Commission [%] Revenue [/100 USDT]
CTC/USDT:USDT 0.1794 0.32 -0.1406
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 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'))
13 changes: 13 additions & 0 deletions examples/get_large_divergence_single_exchange.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""
An example of getting large divergence by single exchange.
"""
from frarb import FundingRateArbitrage


if __name__ == '__main__':
fr = FundingRateArbitrage()
# Display Top 5 large funding rate divergence on binance.
print(fr.display_large_divergence_single_exchange(exchange='binance', display_num=5))

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

logger = getLogger(__name__)


class FundingRateArbitrage:
def __init__(self):
self.exchanges = ['binance', 'bybit', 'okx', 'bitget', 'gate', 'coinex']
# commission
self.is_taker = True
self.by_token = False

@staticmethod
def fetch_all_funding_rate(exchange: str) -> dict:
Expand All @@ -20,7 +27,115 @@ 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:
"""
Display large funding rate divergence on single CEX.
Args:
exchange (str): Name of exchange (binance, bybit, ...)
minus (bool): Sorted by minus FR or plus FR.
display_num (int): Number of display.
Returns (pd.DataFrame): DataFrame sorted by large funding rate divergence.
"""
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) \
+ 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
else:
cm = self.get_commission(exchange=exchange, trade='futures', 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 = sr_fr - sr_cm

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)

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:
"""
Expand Down Expand Up @@ -74,7 +189,10 @@ def get_commission(exchange: str, trade: str, taker=True, by_token=False) -> flo
return 0.018
else:
return 0.02
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 @@ -86,7 +204,10 @@ def get_commission(exchange: str, trade: str, taker=True, by_token=False) -> flo
return 0.06
else:
return 0.01
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 @@ -101,7 +222,13 @@ def get_commission(exchange: str, trade: str, taker=True, by_token=False) -> flo
return 0.05
else:
return 0.02
elif trade == 'options':
if taker:
return 0.03
else:
return 0.02
else:
logger.error(f'{trade} is not available on {exchange}.')
raise KeyError

# https://www.bitget.com/ja/rate/
Expand All @@ -117,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 @@ -132,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 @@ -147,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

0 comments on commit 49c1688

Please sign in to comment.