Overall Statistics
Total Trades
1432
Average Win
0.23%
Average Loss
-0.17%
Compounding Annual Return
8.397%
Drawdown
6.500%
Expectancy
0.457
Net Profit
76.840%
Sharpe Ratio
1.216
Probabilistic Sharpe Ratio
72.310%
Loss Rate
38%
Win Rate
62%
Profit-Loss Ratio
1.36
Alpha
0.056
Beta
0.025
Annual Standard Deviation
0.048
Annual Variance
0.002
Information Ratio
-0.291
Tracking Error
0.151
Treynor Ratio
2.333
Total Fees
$0.00
Estimated Strategy Capacity
$12000.00
Lowest Capacity Asset
XMRUSD E3
# https://quantpedia.com/strategies/time-series-momentum-factor-in-cryptocurrencies/
#
# The investment universe consists of 11 cryptocurrencies (the full list can be found in the paper). Momentum factor is the prior week’s return
# for each currency and therefore can be calculated as a sum of returns for the last week (the data are avalaible at coinmetrics.io). After that,
# the momentum factor is standardized with z-scoring the variable longitudinally – de-meaning it and dividing by its standard deviation to create
# a normalized variable with a zero mean and unit standard deviation, separately for each currency. Portfolio is equally weighted, where the 
# absolute weight is 10% divided by n, where 10% is the gross exposure limit (only 10% of portfolio is invested in cryptocurrencies) and n is
# the number of currencies available for investment. The weight is positive when the longitudinally standardized momentum factor is above zero
# and negative when this factor is below zero, this allows portfolios that can be net long or short the market. However, it is not possible to
# short cryptocurrencies and the practical application would require for example the long only strategy. Portfolio is rebalanced weekly. Last 
# but not least, there are two weighting schemes, the second one is risk based and more information about it is in the paper, we have chosen 
# equally-weighted strategy for a representative purposes.

import numpy as np

class TimeSeriesMomentumCryptocurrencies(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetCash(100000)

        self.symbols = ['BTCUSD', 'ETCUSD', 'ETHUSD', 'LTCUSD', 'XMRUSD', 'ZECUSD']
        self.data = {}
        
        for symbol in self.symbols:
            self.AddCrypto(symbol, Resolution.Daily, Market.Bitfinex)
            self.data[symbol] = RollingWindow[float](5)
        
        self.Schedule.On(self.DateRules.Every(DayOfWeek.Monday), self.TimeRules.AfterMarketOpen(self.symbols[0]), self.Rebalance)

    def OnData(self, data):
        for symbol in self.data:
            symbol_obj = self.Symbol(symbol)
            if symbol_obj in data.Bars:
                if data[symbol_obj]:
                    price = data[symbol_obj].Value
                    if price != 0:
                        self.data[symbol].Add(price)

    def Rebalance(self):
        perf_vol = {}
        
        for symbol in self.symbols:
            if self.data[symbol].IsReady:
                prices = np.array([x for x in self.data[symbol]])
                perf = prices[0] / prices[-1] - 1
                
                daily_returns = prices[:-1] / prices[1:] - 1
                vol = np.std(daily_returns)
                perf_vol[symbol] = (perf, vol)

        # Volatility weighting
        total_vol = sum([1 / x[1][1] for x in perf_vol.items()])
        if total_vol == 0: return

        weight = {}
        for symbol in perf_vol:
            vol = perf_vol[symbol][1]
            if vol != 0:
                weight[symbol] = (1.0 / vol) / total_vol
            else: 
                weight[symbol] = 0

        # Trade execution.
        long = [x[0] for x in perf_vol.items() if x[1][0] > 0]

        invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
        for symbol in invested:
            if symbol not in long:
                self.Liquidate(symbol)

        for symbol in long:
            self.SetHoldings(symbol, 0.1 * weight[symbol])