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"))