Quant BuffetRelax, Not Over Thinking

Cryptomarket Discounts

Log in to collect

Academic paper

Cryptomarket Discounts

AuthorsNicola Borri; Kirill Shakhnov

Institute
  • 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

Annualised return17.82%
Volatility3.28%
Beta-0.005
Sharpe ratio4.21
Win rate62%

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)