| 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)