| Overall Statistics |
|
Total Trades 1947 Average Win 0.09% Average Loss -0.09% Compounding Annual Return 8.154% Drawdown 21.200% Expectancy 0.008 Net Profit 62.256% Sharpe Ratio 0.634 Probabilistic Sharpe Ratio 12.432% Loss Rate 49% Win Rate 51% Profit-Loss Ratio 0.99 Alpha 0.056 Beta 0.004 Annual Standard Deviation 0.095 Annual Variance 0.009 Information Ratio -1.717 Tracking Error 0.627 Treynor Ratio 14.383 Total Fees $276.62 Estimated Strategy Capacity $3100.00 |
# The trading universe consists of 4 cryptocurrencies – Bitcoin, Ethereum, Litecoin and Bitcoin Cash.
# Firstly, start with a linear model given as BTCt = c + beta1ETHt + beta2LTCt + beta3BCHt + Epsilont (at time t) and find the beta coefficients using a simple OLS approach.
# Nextly, define the spread S in time t, which is equal to the value of BTC plus beta1 times Ethereum plus beta2 times Litecoin plus beta3 times Bitcoin Cash.
# To purchase 1 unit of the spread S, buy 1 unit of BTC, beta1 unit of ETH, beta2 unit of LTC and beta3 unit of BCH.
# Of course, the beta coefficients can be (and in the paper with the given backtesting period are) negative.
# If the coefficient is negative, the investor needs to short sell the currency (it is possible with the usage of CFDs).
# Long the spread or exit short position when the spread S is lower than the mean value of the spread minus the constant c times the standard deviation of the spread
# (in the strategy which we have selected the c is equal to the 0.5, however, there are other possibilities in the source paper).
# Short the spread or exit long position when the spread S is greater than the mean value of the spread plus the constant c times the standard deviation of the spread.
# Lastly, expose only to 1 unit of the spread at any given time.
#
# QC implementation changes:
import numpy as np
import statsmodels.api as sm
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(self))
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"))