Overall Statistics |
Total Trades 1779 Average Win 3.02% Average Loss -2.21% Compounding Annual Return 111.340% Drawdown 82.500% Expectancy 0.222 Net Profit 3427.364% Sharpe Ratio 1.66 Probabilistic Sharpe Ratio 53.009% Loss Rate 48% Win Rate 52% Profit-Loss Ratio 1.37 Alpha 0.381 Beta 0.843 Annual Standard Deviation 0.871 Annual Variance 0.759 Information Ratio 0.283 Tracking Error 0.647 Treynor Ratio 1.714 Total Fees $36630.02 Estimated Strategy Capacity $28000.00 Lowest Capacity Asset ETCUSD E3 |
from io import StringIO from datetime import datetime import pandas as pd import numpy as np from scipy import optimize class CasualFluorescentYellowCaterpillar(QCAlgorithm): def Initialize(self): #Backtest dates self.SetStartDate(2017, 1, 2) # Set Start Date #self.SetEndDate(2020,10,1) #Algorithm cash self.SetCash(4000) #Download returns from each strategy csv = self.Download('https://raw.githubusercontent.com/sergiosierram/SharpSignal/main/data/market_cap.csv') self.market_data = pd.read_csv(StringIO(csv), index_col=0) self.aux_func = lambda x: x+"USD" self.symbols = [] self.last_symbols = [] self.resolution = Resolution.Hour #Parameters self.window = int(self.GetParameter("window")) self.rebalance = int(self.GetParameter("rebalance")) self.topx = int(self.GetParameter("TOPX")) self.t_count = -1 self.week_count = 0 self.symbols = list(self.market_data.iloc[self.week_count,0:self.topx]) self.symbols = list(map(self.aux_func, self.symbols)) #Additional variables #List to store previous weights self.last_w = [0 for i in range(len(self.symbols))] self.use_last = True self.initializeSymbols() self.initRollingWindow() self.Rf=0 # April 2019 average risk free rate of return in USA approx 3% annRiskFreeRate = self.Rf/100 self.r0 = (np.power((1 + annRiskFreeRate), (1.0 / 360.0)) - 1.0) * 100 self.portfolioSize = len(self.symbols) self.SetBrokerageModel(BrokerageName.Bitfinex) self.SetBenchmark("BTCUSD") return def initializeSymbols(self): self.symbols_objects = [] for symbol in self.symbols: try: data = self.AddCrypto(symbol, self.resolution, Market.Bitfinex) data.SetFeeModel(CustomFeeModel(self)) self.symbols_objects.append(data.Symbol) except: self.symbols.remove(symbol) #self.Debug("Symbol not available: "+str(symbol)) return def initRollingWindow(self): c = 0 unavailable = 0 self.rolling = [RollingWindow[float](self.window) for symbol in self.symbols] for symbol in self.symbols: df = pd.DataFrame() while df.empty: try: df = self.History(self.Symbol(symbol), self.window) d = df['close'].to_list() for x in d: self.rolling[c].Add(x) except: unavailable += 1 #self.Debug("Unavailable data for: "+symbol) break c += 1 self.rolling[0:-unavailable] return def OnData(self, data): self.t_count += 1 if self.t_count % self.rebalance == 0: self.SpecificTime() day, month, year = list(map(int, self.market_data.index[self.week_count].split('/'))) prevd = datetime(year+2000, month, day) day, month, year = list(map(int, self.market_data.index[self.week_count+1].split('/'))) nextd = datetime(year+2000, month, day) currentd = self.Time if currentd > prevd and currentd <= nextd: pass else: self.week_count += 1 self.last_symbols = list(self.symbols) self.symbols = list(self.market_data.iloc[self.week_count,0:self.topx]) self.symbols = list(map(self.aux_func, self.symbols)) self.initializeSymbols() self.initRollingWindow() self.portfolioSize = len(self.symbols) c = 0 for symbol in self.symbols: if data.ContainsKey(symbol): self.rolling[c].Add(data[symbol].Close) c+=1 return def SpecificTime(self): #Check the len of the rolling windows flag = True for roll in self.rolling: l = [i for i in roll][::-1] self.Log(len(l)) if len(l) < self.window: flag = False if not flag: return #try: Ri = [] c = 0 for symbol in self.symbols: Ri.append([i for i in self.rolling[c]][::-1]) c+=1 Ri = np.array(Ri).transpose() self.Log(Ri) Ri = StockReturnsComputing(Ri, self.window, self.portfolioSize) Ei = np.mean(Ri, axis = 0) #except: # self.Log(str("Error during data extraction")) # return cov = np.cov(Ri, rowvar=False) #initialization xOptimal =[] minRiskPoint = [] expPortfolioReturnPoint =[] maxSharpeRatio = 0 #compute maximal Sharpe Ratio and optimal weights result = MaximizeSharpeRatioOptmzn(Ei, cov, self.r0, self.portfolioSize) xOptimal.append(result.x) w = list(xOptimal[0]) w = [ 0 if wx < 0.0000001 else wx for wx in w ] #self.Debug(w) if self.week_count > 1: self.LiquidateOldSymbols() if not self.use_last: self.Liquidate() targets = [] for i in range(len(w)): currency = self.symbols[i] if not self.use_last: self.SetHoldings(currency, w[i]) else: targets.append(PortfolioTarget(currency, 0.75*w[i])) if self.use_last: self.SetHoldings(targets) return def LiquidateOldSymbols(self): for symbol in self.last_symbols: if symbol not in self.symbols: self.Debug("###### not in last: "+symbol) self.Liquidate(symbol) return def OnEndOfAlgorithm(self): self.Liquidate() return # Custom fee model. class CustomFeeModel(FeeModel): def GetOrderFee(self, parameters): fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.002 return OrderFee(CashAmount(fee, "USD")) def MaximizeSharpeRatioOptmzn(MeanReturns, CovarReturns, RiskFreeRate, PortfolioSize): # define maximization of Sharpe Ratio using principle of duality def f(x, MeanReturns, CovarReturns, RiskFreeRate, PortfolioSize): funcDenomr = np.sqrt(np.matmul(np.matmul(x, CovarReturns), x.T) ) funcNumer = np.matmul(np.array(MeanReturns),x.T)-RiskFreeRate func = -(funcNumer / funcDenomr) return func #define equality constraint representing fully invested portfolio def constraintEq(x): A=np.ones(x.shape) b=1 constraintVal = np.matmul(A,x.T)-b return constraintVal #define bounds and other parameters xinit=np.repeat(0.33, PortfolioSize) cons = ({'type': 'eq', 'fun':constraintEq}) lb = 0 ub = 1 bnds = tuple([(lb,ub) for x in xinit]) #invoke minimize solver opt = optimize.minimize (f, x0 = xinit, args = (MeanReturns, CovarReturns,\ RiskFreeRate, PortfolioSize), method = 'SLSQP', \ bounds = bnds, constraints = cons, tol = 10**-3) return opt def StockReturnsComputing(StockPrice, Rows, Columns): StockReturn = np.zeros([Rows-1, Columns]) for j in range(Columns): # j: Assets for i in range(Rows-1): # i: Daily Prices StockReturn[i,j]=((StockPrice[i+1, j]-StockPrice[i,j])/StockPrice[i,j])*100 return StockReturn