Cryptomarket Discounts
Log in to collectAcademic paper
Nicola Borri; Kirill Shakhnov
- ITLibera Università Internazionale degli Studi Sociali Guido Carli
- ?LUISS University - Department of Economics and Finance
- University of Surrey
Strategy in a nutshell
The strategy trades Bitcoin-dollar pairs across multiple exchanges, treating each pair as a separate asset. For each exchange, the Bitcoin price is compared to Bitfinex, and a discount is computed. Assets are ranked by discount and grouped into five portfolios. The strategy buys Bitcoin on exchanges with the lowest discounts (highest premiums) and sells on Bitfinex. Portfolios are equally weighted, rebalanced daily, and use Bitcoin futures with borrowed dollars, including interest.
Economic rationale
Bitcoin prices differ across exchanges due to market inefficiencies, despite the law of one price. Pure arbitrage is costly due to fees and delays, but exploiting these price discrepancies remains profitable after trading costs, allowing investors to capture substantial returns from persistent exchange-level price gaps.
Backtest performance
Full Python code
from AlgorithmImports import *
import data_tools
# endregion
class CryptomarketDiscounts(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(100000)
# exchanges
tickers:list[str] = [
'ABUCOINS_BTCUSD', 'BITBAY_BTCUSD', 'BITSTAMP_BTCUSD', 'BITTREX_BTCUSD',
'CEX_BTCUSD', 'COINBASE_BTCUSD', 'EXMO_BTCUSD', 'GEMINI_BTCUSD',
'HITBTC_BTCUSD', 'ITBIT_BTCUSD', 'KRAKEN_BTCUSD', 'OKCOIN_BTCUSD',
'YOBIT_BTCUSD'
]
bitfinex_btc_ticker:str = 'BITFINEX_BTCUSD'
self.quantile:int = 5
self.portfolio_percentage:float = .1
self.exchange_btc_symbols:list[Symbol] = []
# subscribe symbols
for ticker in tickers + [bitfinex_btc_ticker]:
security:Security = self.AddData(data_tools.QuantpediaBTCExchanges, ticker, Resolution.Daily)
security.SetFeeModel(data_tools.CustomFeeModel())
security.SetLeverage(5)
if ticker == bitfinex_btc_ticker:
self.bitfinex_btc_symbol:Symbol = security.Symbol
else:
self.exchange_btc_symbols.append(security.Symbol)
def OnData(self, data: Slice):
premium_by_symbol:dict[Symbol, float] = {}
# calculate discount (premium)
if data.ContainsKey(self.bitfinex_btc_symbol):
bitfinex_btc_price:float = data[self.bitfinex_btc_symbol].Value
for exch_symbol in self.exchange_btc_symbols:
if data.ContainsKey(exch_symbol):
exch_price:float = data[exch_symbol].Value
premium:float = exch_price / bitfinex_btc_price - 1
premium_by_symbol[exch_symbol] = premium
if len(premium_by_symbol) < self.quantile:
self.Liquidate()
return
quantile:int = int(len(premium_by_symbol) / self.quantile)
sorted_by_discount:list[Symbol] = [x[0] for x in sorted(premium_by_symbol.items(), key=lambda item: item[1])]
# investing in the markets with the highest discounts
long_leg:list[Symbol] = sorted_by_discount[:quantile]
long_length = len(long_leg)
# liquidate
invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for exch_symbol in invested:
if exch_symbol not in long_leg and exch_symbol != self.bitfinex_btc_symbol:
self.Liquidate(exch_symbol)
# trade arbitrage
for exch_symbol in long_leg:
self.SetHoldings(exch_symbol, (1 / long_length) * self.portfolio_percentage)
self.SetHoldings(self.bitfinex_btc_symbol, -1 * self.portfolio_percentage)