Overall Statistics |
Total Trades 302 Average Win 0.24% Average Loss -0.30% Compounding Annual Return -15.479% Drawdown 28.600% Expectancy -0.478 Net Profit -16.704% Sharpe Ratio -0.333 Probabilistic Sharpe Ratio 4.606% Loss Rate 71% Win Rate 29% Profit-Loss Ratio 0.79 Alpha 0.047 Beta 0.909 Annual Standard Deviation 0.247 Annual Variance 0.061 Information Ratio 0.771 Tracking Error 0.077 Treynor Ratio -0.091 Total Fees $413.22 Estimated Strategy Capacity $4000.00 Lowest Capacity Asset QCOM R735QTJ8XC9X |
#region imports from AlgorithmImports import * #endregion class SparseOptimizationIndexTrackingDemo(QCAlgorithm): def Initialize(self): self.SetStartDate(2017, 1, 1) self.SetStartDate(2022, 1, 1) self.SetBrokerageModel(BrokerageName.AlphaStreams) self.SetCash(1000000) # Add our ETF constituents of the index that we would like to track. self.QQQ = self.AddEquity("QQQ", Resolution.Minute).Symbol self.UniverseSettings.Resolution = Resolution.Minute self.AddUniverse(self.Universe.ETF(self.QQQ, self.UniverseSettings, self.ETFSelection)) self.SetBenchmark("QQQ") # Set up varaibles to flag the time to recalibrate and hold the constituents. self.time = datetime.min self.assets = [] def ETFSelection(self, constituents): # We want all constituents to be considered. self.assets = [x.Symbol for x in constituents] return self.assets def OnData(self, data): qb = self if self.time > self.Time: return # Prepare the historical return data of the constituents and the ETF (as index to track). history = qb.History(self.assets, 252, Resolution.Daily) if history.empty: return historyPortfolio = history.close.unstack(0) pctChangePortfolio = np.log(historyPortfolio/historyPortfolio.shift(1)).dropna() historyQQQ = qb.History(self.AddEquity("QQQ").Symbol, 252, Resolution.Daily) historyQQQ = historyQQQ.close.unstack(0) pctChangeQQQ = np.log(historyQQQ/historyQQQ.shift(1)).loc[pctChangePortfolio.index] m = pctChangePortfolio.shape[0]; n = pctChangePortfolio.shape[1] # Set up optimization parameters. p = 0.5; M = 0.0001; l = 0.01 # Set up convergence tolerance, maximum iteration of optimization, iteration counter and Huber downward risk as minimization indicator. tol = 0.001; maxIter = 20; iters = 1; hdr = 10000 # Initial weightings and placeholders. w_ = np.array([1/n] * n).reshape(n, 1) self.weights = pd.Series() a = np.array([None] * m).reshape(m, 1) c = np.array([None] * m).reshape(m, 1) d = np.array([None] * n).reshape(n, 1) # Iterate to minimize the HDR. while iters < maxIter: x_k = (pctChangeQQQ.values - pctChangePortfolio.values @ w_) for i in range(n): w = w_[i] d[i] = d_ = 1/(np.log(1+l/p)*(p+w)) for i in range(m): xk = float(x_k[i]) if xk < 0: a[i] = M / (M - 2*xk) c[i] = xk else: c[i] = 0 if 0 <= xk <= M: a[i] = 1 else: a[i] = M/abs(xk) L3 = 1/m * pctChangePortfolio.T.values @ np.diagflat(a.T) @ pctChangePortfolio.values eigVal, eigVec = np.linalg.eig(L3.astype(float)) eigVal = np.real(eigVal); eigVec = np.real(eigVec) q3 = 1/max(eigVal) * (2 * (L3 - max(eigVal) * np.eye(n)) @ w_ + eigVec @ d - 2/m * pctChangePortfolio.T.values @ np.diagflat(a.T) @ (c - pctChangeQQQ.values)) # We want to keep the upper bound of each asset to be 0.1 mu = float(-(np.sum(q3) + 2)/n); mu_ = 0 while mu > mu_: mu = mu_ index1 = [i for i, q in enumerate(q3) if mu + q < -0.1*2] index2 = [i for i, q in enumerate(q3) if -0.1*2 < mu + q < 0] mu_ = float(-(np.sum([q3[i] for i in index2]) + 2 - len(index1)*0.1*2)/len(index2)) # Obtain the weights and HDR of this iteration. w_ = np.amax(np.concatenate((-(mu + q3)/2, 0.1*np.ones((n, 1))), axis=1), axis=1).reshape(-1, 1) w_ = w_/np.sum(abs(w_)) hdr_ = float(w_.T @ w_ + q3.T @ w_) # If the HDR converges, we take the current weights if abs(hdr - hdr_) < tol: break # Else, we would increase the iteration count and use the current weights for the next iteration. iters += 1 hdr = hdr_ # ----------------------------------------------------------------------------------------- orders = [] for i in range(n): orders.append(PortfolioTarget(pctChangePortfolio.columns[i], float(w_[i]))) self.SetHoldings(orders) # Recalibrate on quarter end. self.time = Expiry.EndOfQuarter(self.Time)