Overall Statistics |
Total Trades 304 Average Win 0.32% Average Loss -0.29% Compounding Annual Return 20.474% Drawdown 6.600% Expectancy 0.327 Net Profit 20.412% Sharpe Ratio 1.289 Probabilistic Sharpe Ratio 58.127% Loss Rate 36% Win Rate 64% Profit-Loss Ratio 1.08 Alpha 0.18 Beta -0.064 Annual Standard Deviation 0.137 Annual Variance 0.019 Information Ratio 0.788 Tracking Error 0.169 Treynor Ratio -2.746 Total Fees $338.86 Estimated Strategy Capacity $1100000.00 |
# V1 # Find a source with historical OEF(SP100) holdings # Check SPY holdings monthly only invest in top X OEF holdings # Notice do not repeat GOOGL & GOOG # Rebalance every month to maintian equal weight # V2 # Calculate MOMP of last x days for each OEF holdings # Only invest in top numOfHoldings with greatest momp # Test this on QQQ as well import numpy as np import pandas as pd from io import StringIO from datetime import timedelta class OptimizedBuyAndHold(QCAlgorithm): def Initialize(self): self.SetStartDate(2005, 1, 1) # Set Start Date self.SetEndDate(2006, 1, 1) # Set Start Date self.SetCash(100000) # Set Strategy Cash self.SetWarmUp(timedelta(60)) # Warm up technical indicators self.UniverseSettings.Resolution = Resolution.Daily self.MOMPlist = {} # Dict of Momentum indicator keyed by Symbol self.lookback = 10 # Momentum indicator lookback period self.numOfCoarse = 200 # Number of symbols selected at Coarse Selection self.numOfLong = 15 # Number of symbols with open positions self.month = -1 self.file = self.Download("https://www.dropbox.com/s/mjqxvu73vyxvbtc/SP500HistoricalHolding_Modified.csv?dl=1") self.rebalance = False self.AddUniverse(self.CoarseSelectionFunction) def CoarseSelectionFunction(self, coarse): '''Drop securities which have no fundamental data or have too low prices. Select those with highest by dollar volume''' if self.month == self.Time.month: return Universe.Unchanged self.rebalance = True self.month = self.Time.month df = pd.read_csv(StringIO(self.file)) month = format(self.month, '02d') time = str(self.Time.year) + month candidates = pd.DataFrame({'Ticker': df['Ticker'], time: df[time]}).dropna() candidates = candidates.sort_values(by=time, ascending=False)[:int(self.numOfCoarse)] candidates = candidates['Ticker'] selected = [] for candidate in candidates: for x in coarse: if x.Symbol.Value == candidate: selected.append(x) return [x.Symbol for x in selected] def OnData(self, data): # Update the indicator for symbol, mom in self.MOMPlist.items(): mom.Update(self.Time, self.Securities[symbol].Close) if not self.rebalance: return # Selects the securities with highest momentum sorted_mom = sorted([k for k,v in self.MOMPlist.items() if v.IsReady], key=lambda x: self.MOMPlist[x].Current.Value, reverse=True) selected = sorted_mom[:self.numOfLong] # Liquidate securities that are not in the list for symbol, mom in self.MOMPlist.items(): if symbol not in selected: self.Liquidate(symbol, 'Not selected') # Buy selected securities for symbol in selected: self.SetHoldings(symbol, 1/self.numOfLong) self.rebalance = False def OnSecuritiesChanged(self, changes): # Clean up data for removed securities and Liquidate for security in changes.RemovedSecurities: symbol = security.Symbol if self.MOMPlist.pop(symbol, None) is not None: self.Liquidate(symbol, 'Removed from universe') for security in changes.AddedSecurities: if security.Symbol not in self.MOMPlist: self.MOMPlist[security.Symbol] = Momentum(self.lookback) # Warm up the indicator with history price if it is not ready addedSymbols = [k for k,v in self.MOMPlist.items() if not v.IsReady] history = self.History(addedSymbols, 1 + self.lookback, Resolution.Daily) history = history.close.unstack(level=0) for symbol in addedSymbols: ticker = str(symbol) if ticker in history: for time, value in history[ticker].items(): item = IndicatorDataPoint(symbol, time, value) self.MOMPlist[symbol].Update(item)