Overall Statistics |
Total Trades 2467 Average Win 0.08% Average Loss -0.06% Compounding Annual Return 8.906% Drawdown 4.400% Expectancy 0.371 Net Profit 30.435% Sharpe Ratio 1.334 Probabilistic Sharpe Ratio 73.680% Loss Rate 40% Win Rate 60% Profit-Loss Ratio 1.29 Alpha 0.058 Beta 0.039 Annual Standard Deviation 0.046 Annual Variance 0.002 Information Ratio -0.127 Tracking Error 0.203 Treynor Ratio 1.602 Total Fees $1298.54 Estimated Strategy Capacity $16000.00 Lowest Capacity Asset TRXUSD 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. #region imports from AlgorithmImports import * import numpy as np import pandas as pd from datetime import timedelta #endregion class TimeSeriesMomentumCryptocurrencies(QCAlgorithm): def Initialize(self): self.SetStartDate(2020, 1, 1) self.SetCash(100000) self.symbols = ['BTCUSD', 'ETHUSD', 'XRPUSD', 'ADAUSD', 'XMRUSD', 'MATICUSD', 'TRXUSD', 'ATOMUSD', 'ETCUSD','LINKUSD', 'LTCUSD', 'XLMUSD', 'XTZUSD', 'EOSUSD'] self.data = {} self.percentage_traded = 0.1 month_list = [] for year in ["2020","2021","2022","2023","2024"]: for i in range(12): if i >= 9: month_list.append(f"{year}{i+1}") else: month_list.append(f"{year}0{i+1}") df = pd.DataFrame({'Date': month_list}) df['EndOfMonth'] = pd.to_datetime(df['Date'], format="%Y%m") + pd.tseries.offsets.MonthEnd(0) self.date_store = df.EndOfMonth.dt.date.tolist() self.end_of_month = False; self.momentum_run = True; self.eom_profit = 1.0; self.sign_eom_profit = np.sign(self.eom_profit); for symbol in self.symbols: data = self.AddCrypto(symbol, Resolution.Daily, Market.Bitfinex) data.SetFeeModel(CustomFeeModel()) data.SetLeverage(10) self.data[symbol] = RollingWindow[float](5) def OnData(self, data): for symbol in self.data: symbol_obj = self.Symbol(symbol) if symbol_obj in data.Bars and data[symbol_obj]: self.data[symbol].Add(data[symbol_obj].Value) if self.Time.date() + timedelta(days=2) in self.date_store: self.end_of_month = True; self.eom_profit = self.Portfolio.TotalPortfolioValue; for symbol in self.symbols: self.SetHoldings(symbol, self.percentage_traded / 10) return elif self.Time.date() - timedelta(days=1) in self.date_store: invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested] for symbol in invested: self.Liquidate(symbol) self.end_of_month = False; self.momentum_run = True; self.eom_profit = self.Portfolio.TotalPortfolioValue - self.eom_profit; self.sign_eom_profit = int(self.eom_profit > 0); elif self.end_of_month: return if self.momentum_run: self.momentum_run = False; else: if self.Time.date().weekday() != 0: return 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: if symbol in data and data[symbol]: self.SetHoldings(symbol, self.sign_eom_profit * self.percentage_traded * weight[symbol]) # Custom fee model. class CustomFeeModel(FeeModel): def GetOrderFee(self, parameters): fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.0006 return OrderFee(CashAmount(fee, "USD"))