Overall Statistics |
Total Trades 4876 Average Win 0.32% Average Loss -0.27% Compounding Annual Return 17.553% Drawdown 31.400% Expectancy 0.288 Net Profit 492.366% Sharpe Ratio 1.043 Probabilistic Sharpe Ratio 45.752% Loss Rate 41% Win Rate 59% Profit-Loss Ratio 1.20 Alpha 0.166 Beta -0.088 Annual Standard Deviation 0.149 Annual Variance 0.022 Information Ratio 0.129 Tracking Error 0.226 Treynor Ratio -1.761 Total Fees $6158.18 |
from datetime import timedelta import numpy as np from scipy import stats from collections import deque import math from dateutil import parser import pickle class statusObj: def __init__(self): self.invested = False self.positionDay = '1820-01-01' class OptimizedTransdimensionalCircuit(QCAlgorithm): def Initialize(self): self.SignalDataBySymbol = {} # create an object self.status=statusObj() self.status.invested=True self.status.positionDay=str(self.Time.day) serialized=pickle.dumps(self.status) self.ObjectStore.SaveBytes("MyObject", serialized) self.SetStartDate(2010, 1, 1) # Set Start Date self.SetCash(50000) # Set Strategy Cash #self.SetEndDate(2020, 11, 30) tickers = ["QQQ","SPY","IYC","IYK","IGV","GLD","TLH","TLT", 'XLI','XLU','FXA','FXF','SLV','GLD'] for x in tickers: self.AddEquity(x, Resolution.Daily) self.SetUniverseSelection(CustomUniverseSelectionModel("CustomUniverseSelectionModel", lambda time: tickers)) self.UniverseSettings.Resolution = Resolution.Daily self.AddAlpha(ETFs_for_UP()) # etfs for up self.AddAlpha(ETFs_for_DOWN()) # etfs for up self.SetExecution(ImmediateExecutionModel()) self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(self.DateRules.Every(DayOfWeek.Tuesday))) # schedule an event to fire on a single day of the week self.Schedule.On(self.DateRules.Every(DayOfWeek.Monday), self.TimeRules.At(12, 0), self.calculate_signal) signal_universe = ['XLI','XLU','FXA','FXF','SLV','GLD'] for x in signal_universe: ticker = self.Symbol(x) mom=CustomMomentum("Momentum", 252) self.RegisterIndicator(ticker, mom, Resolution.Daily) history = self.History(ticker, 252, Resolution.Daily) mom.WarmUp(history) signaldata = SignalData(ticker, mom) self.SignalDataBySymbol[ticker] = signaldata def calculate_signal(self): indicator ={} self.status=statusObj() for ticker, SignalData in self.SignalDataBySymbol.items(): #For all securities in self.stocks indicator[ticker] = SignalData a = indicator[self.Symbol("GLD")].MOM.Value > indicator[self.Symbol("SLV")].MOM.Value b = indicator[self.Symbol("XLU")].MOM.Value > indicator[self.Symbol("XLI")].MOM.Value c = indicator[self.Symbol("FXA")].MOM.Value > indicator[self.Symbol("FXF")].MOM.Value if a and b and c: self.status.invested=False else: self.status.invested=True self.status.positionDay=str(self.Time.day) serialized=pickle.dumps(self.status) self.ObjectStore.SaveBytes("MyObject", serialized) #after save check #deserialized=bytes(self.ObjectStore.ReadBytes("MyObject")) #storedStatus=pickle.loads(deserialized) #self.Log('position day in stored object: '+str(storedStatus.positionDay)) class ETFs_for_DOWN(AlphaModel): def __init__(self): # Initilize method in the alpha model only runs once self.stocks = [] # Array containing all actively traded stocks self.symbolDataBySymbol = {} self.num = 3 self.mom_window = 200 self.vola_window = 60 self.num_stocks = 3 def Update(self, algorithm, data): #Runs as fast as the data comes in. In this case it runs every hour insights = [] # sets insights array to empty indicator ={} self.status=statusObj() if algorithm.ObjectStore.ContainsKey("MyObject"): deserialized = bytes(algorithm.ObjectStore.ReadBytes("MyObject")) storedStatus = (pickle.loads(deserialized)) up_down_signal=storedStatus.invested for ticker, symbolData in self.symbolDataBySymbol.items(): #For all securities in self.stocks indicator[ticker] = symbolData # get the top momentum a=np.array([1,2,3,4]), sorted(a, reverse=True)[:2] top_mom = sorted(self.symbolDataBySymbol.items(), key=lambda x: indicator[x[0]].MOM.Value, reverse=True)[:self.num_stocks] for ticker, symbolData in self.symbolDataBySymbol.items(): #For all securities in self.stocks if not data.ContainsKey(ticker): continue if algorithm.Portfolio[ticker].IsLong: continue '''self.SetHoldings(symbol, position, True)''' if up_down_signal == False: insights.append(Insight.Price(ticker, timedelta(days = 7), InsightDirection.Up)) #Long position that lasts 30 days. After 30 days liquidate #algorithm.Debug("Stock: " + str(x) + ". SMA: " + str(sma)) return insights #Must return insights def OnSecuritiesChanged(self, algorithm, changes): #OnSecuritiesChanged is triggered every time Universe Selection returns a new stock sub_universe = ["GLD","TLH","TLT"] for x in changes.AddedSecurities: ticker = x.Symbol if str(ticker) in sub_universe: self.stocks.append(ticker) if ticker not in self.symbolDataBySymbol: mom=CustomMomentum("Momentum", self.mom_window) vol=CustomVolatility("Volatility", self.vola_window) algorithm.RegisterIndicator(ticker, mom, Resolution.Daily) algorithm.RegisterIndicator(ticker, vol, Resolution.Daily) history = algorithm.History(ticker, max(self.mom_window, self.vola_window), Resolution.Daily) mom.WarmUp(history) vol.WarmUp(history) symbolData = SymbolData(ticker, mom, vol) self.symbolDataBySymbol[ticker] = symbolData symbolsRemoved = [ x.Symbol for x in changes.RemovedSecurities ] for ticker in symbolsRemoved: if ticker in self.stocks: self.stocks.remove(ticker) self.symbolDataBySymbol.pop(ticker, None) class ETFs_for_UP(AlphaModel): def __init__(self): # Initilize method in the alpha model only runs once self.stocks = [] # Array containing all actively traded stocks self.symbolDataBySymbol = {} self.num = 3 self.mom_window = 200 self.vola_window = 60 self.num_stocks = 3 def Update(self, algorithm, data): #Runs as fast as the data comes in. In this case it runs every hour insights = [] # sets insights array to empty indicator ={} self.status=statusObj() if algorithm.ObjectStore.ContainsKey("MyObject"): deserialized = bytes(algorithm.ObjectStore.ReadBytes("MyObject")) storedStatus = (pickle.loads(deserialized)) up_down_signal=storedStatus.invested for ticker, symbolData in self.symbolDataBySymbol.items(): #For all securities in self.stocks indicator[ticker] = symbolData # get the top momentum a=np.array([1,2,3,4]), sorted(a, reverse=True)[:2] top_mom = sorted(self.symbolDataBySymbol.items(), key=lambda x: indicator[x[0]].MOM.Value, reverse=True)[:self.num_stocks] for ticker, symbolData in self.symbolDataBySymbol.items(): #For all securities in self.stocks if not data.ContainsKey(ticker): continue if algorithm.Portfolio[ticker].IsLong: continue '''self.SetHoldings(symbol, position, True)''' if up_down_signal == True: insights.append(Insight.Price(ticker, timedelta(days = 7), InsightDirection.Up)) #Long position that lasts 30 days. After 30 days liquidate #algorithm.Debug("Stock: " + str(x) + ". SMA: " + str(sma)) return insights #Must return insights def OnSecuritiesChanged(self, algorithm, changes): #OnSecuritiesChanged is triggered every time Universe Selection returns a new stock sub_universe = ["QQQ","SPY","IYC","IYK","IGV"] for x in changes.AddedSecurities: ticker = x.Symbol if str(ticker) in sub_universe: self.stocks.append(ticker) if ticker not in self.symbolDataBySymbol: mom=CustomMomentum("Momentum", self.mom_window) vol=CustomVolatility("Volatility", self.vola_window) algorithm.RegisterIndicator(ticker, mom, Resolution.Daily) algorithm.RegisterIndicator(ticker, vol, Resolution.Daily) history = algorithm.History(ticker, max(self.mom_window, self.vola_window), Resolution.Daily) mom.WarmUp(history) vol.WarmUp(history) symbolData = SymbolData(ticker, mom, vol) self.symbolDataBySymbol[ticker] = symbolData symbolsRemoved = [ x.Symbol for x in changes.RemovedSecurities ] for ticker in symbolsRemoved: if ticker in self.stocks: self.stocks.remove(ticker) self.symbolDataBySymbol.pop(ticker, None) class SymbolData: '''Contains data specific to a symbol required by this model''' def __init__(self, symbol, mom, vol): self.Symbol = symbol self.MOM = mom self.VOL = vol class SignalData: '''Contains data specific to a symbol required by this model''' def __init__(self, symbol, mom): self.Symbol = symbol self.MOM = mom class CustomMomentum: def __init__(self, name, period): self.Name = name self.Time = datetime.min self.IsReady = False self.Value = 0 self.queue = deque(maxlen=period) def __repr__(self): return "{0} -> IsReady: {1}. Time: {2}. Value: {3}".format(self.Name, self.IsReady, self.Time, self.Value) # Update method is mandatory def Update(self, input): return self.Update_Main(input.Time, input.Close) def Update_warmup(self, input): return self.Update_Main(input.Index, input.close) def Update_Main(self, time, value): self.queue.appendleft(value) # neutral count = len(self.queue) self.Time = time # neutral self.IsReady = count == self.queue.maxlen #### start indicator calulation if self.IsReady: close = np.array(self.queue) self.Value = close[-1]/close[0] #### finish indicator calulation return self.IsReady def WarmUp(self,history): for tuple in history.itertuples(): self.Update_warmup(tuple) class CustomVolatility: def __init__(self, name, period): self.Name = name self.Time = datetime.min self.IsReady = False self.Value = 0 self.queue = deque(maxlen=period) def __repr__(self): return "{0} -> IsReady: {1}. Time: {2}. Value: {3}".format(self.Name, self.IsReady, self.Time, self.Value) # Update method is mandatory def Update(self, input): return self.Update_Main(input.Time, input.Close) def Update_warmup(self, input): return self.Update_Main(input.Index, input.close) def Update_Main(self, time, value): self.queue.appendleft(value) # neutral count = len(self.queue) self.Time = time # neutral self.IsReady = count == self.queue.maxlen #### start here the indicator calulation if self.IsReady: # [0:-1] is needed to remove last close since diff is one element shorter close = np.array(self.queue) log_close = np.log(close) diffs = np.diff(log_close) self.Value = diffs.std() * math.sqrt(252) * 100 return self.IsReady def WarmUp(self,history): for tuple in history.itertuples(): self.Update_warmup(tuple)