Cointegrated Cryptocurrency Portfolios
Log in to collectAcademic paper
Constructing Cointegrated Cryptocurrency Portfolios for Statistical Arbitrage
Tim Leung; Hung Cuong Nguyen
- University of Washington
- ?University of Washington - Department of Applied Math
- BGRisk Engineering (Bulgaria)
- ?Computational Finance and Risk Management
Strategy in a nutshell
Trades a spread of Bitcoin, Ethereum, Litecoin, and Bitcoin Cash based on cointegration. Long the spread when it falls below mean −0.5×SD and short when above mean +0.5×SD, holding one unit at a time with positions sized by OLS-estimated beta coefficients.
Economic rationale
Exploits mean reversion in cointegrated crypto prices: the spread, constructed from a stationary combination of non-stationary assets, reverts to its mean over time, allowing profits from buying low and selling high (or shorting high and covering low).
Backtest performance
Annualised return42.68%
Beta-0.148
Maximum drawdown-15.98%
Win rate46%
Full Python code
from AlgorithmImports import *
import numpy as np
import statsmodels.api as sm
#endregion
class CointegratedCryptocurrencyPortfolios(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(100000)
self.cryptos = [
"BTCUSD", # Bitcoin
"ETHUSD", # Ethereum
# "BCHUSD", # Bitcoin cash # bchusd bitfinex quantconnect price ends in 2018.
"LTCUSD", # Litecoin
]
self.data = {}
self.spread = []
self.c = 0.5 # Constant for this strategy, however there are other possible values in source paper
self.period = 21
self.last_day = -1
self.invested_long = None
self.SetBrokerageModel(BrokerageName.Bitfinex)
for crypto in self.cryptos:
data = self.AddCrypto(crypto, Resolution.Daily, Market.Bitfinex)
data.SetFeeModel(CustomFeeModel())
data.SetLeverage(5)
self.data[crypto] = RollingWindow[float](self.period)
def OnData(self, data):
if self.last_day == self.Time.day: return
self.last_day = self.Time.day
for crypto in self.cryptos:
if crypto in data.Bars:
if data[crypto]:
price = data.Bars[crypto].Value
self.data[crypto].Add(price)
used_cryptos = []
bitcoin_prices = None
other_currencies_prices = []
for crypto in self.cryptos:
if not self.data[crypto].IsReady:
return
else:
if crypto is "BTCUSD":
bitcoin_prices = np.array([x for x in self.data[crypto]])
else:
crypto_prices = [x for x in self.data[crypto]]
if not crypto_prices[1:] == crypto_prices[:-1]: # If values in one list aren't same, then it can be use in MultipleLinearRegression
other_currencies_prices.append([x for x in self.data[crypto]])
used_cryptos.append(crypto)
if len(other_currencies_prices) == 0:
self.Liquidate()
return
regression_model = self.MultipleLinearRegression(other_currencies_prices, bitcoin_prices)
alpha = regression_model.params[0]
betas = [regression_model.params[index] for index in range(len(regression_model.params)) if index != 0]
beta_index = 0
current_spread_value = 0
for crypto in self.cryptos:
if crypto is "BTCUSD":
current_spread_value = current_spread_value + self.data[crypto][0]
elif crypto in used_cryptos:
current_speard_value = self.data[crypto][0] * betas[beta_index]
beta_index = beta_index + 1
self.spread.append(current_spread_value)
if len(self.spread) < self.period: # We need at least one month values of spread
return
threshold_long = np.mean(self.spread) - self.c * np.std(self.spread)
threshold_short = np.mean(self.spread) + self.c * np.std(self.spread)
if self.invested_long is None:
if current_spread_value < threshold_long: # long or exit short
self.InvestLong(betas, used_cryptos)
self.invested_long = True
elif current_spread_value > threshold_short: # short or exit long
self.InvestShort(betas, used_cryptos)
self.invested_long = False
else:
if current_spread_value < threshold_long and self.invested_long: # long or exit short
self.invested_long = True
self.InvestLong(betas, used_cryptos)
elif current_spread_value > threshold_short and not self.invested_long: # short or exit long
self.invested_long = False
self.InvestShort(betas, used_cryptos)
def InvestLong(self, betas, used_cryptos):
beta_index = 0
for crypto in self.cryptos:
if self.Portfolio[crypto].Invested:
self.Liquidate(crypto)
if crypto is "BTCUSD":
self.MarketOrder(crypto, 1)
elif crypto in used_cryptos:
self.MarketOrder(crypto, betas[beta_index])
beta_index = beta_index + 1
def InvestShort(self, betas, used_cryptos):
beta_index = 0
for crypto in self.cryptos:
if self.Portfolio[crypto].Invested:
self.Liquidate(crypto)
if crypto is "BTCUSD":
self.MarketOrder(crypto, -1)
elif crypto in used_cryptos:
self.MarketOrder(crypto, -betas[beta_index])
beta_index = beta_index + 1
def MultipleLinearRegression(self, x, y):
x = np.array(x).T
x = sm.add_constant(x)
result = sm.OLS(endog=y, exog=x).fit()
return result
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))