Overall Statistics |
Total Trades 0 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Net Profit 0% Sharpe Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio -11.629 Tracking Error 0.006 Treynor Ratio 0 Total Fees $0.00 |
import numpy as np class CrossSectionalMomentum(QCAlgorithm): def Initialize(self): self.SetStartDate(2019, 1, 1) self.SetEndDate(2020, 1, 1) self.SetCash(1000000) self.SetBrokerageModel(AlphaStreamsBrokerageModel()) self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverse(self.CoarseSelection) self.SetAlpha(ReversalFakeoutAlpha()) self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel()) self.SetRiskManagement(TrailingStopRiskManagementModel(0.02)) self.SetExecution(ImmediateExecutionModel()) def CoarseSelection(self, coarse): sortedCoarse = sorted(coarse, key=lambda c:c.DollarVolume, reverse=True) return [c.Symbol for c in sortedCoarse][:1000] class ReversalFakeoutAlpha(AlphaModel): def __init__(self): self.symbols = {} self.lastWeek = -1 def Update(self, algorithm, data): insights = [] # Update daily rolling windows with daily data for symbol, symbolData in self.symbols.items(): if data.ContainsKey(symbol) and data[symbol] != None: symbolData.dailyWindow.Add(data[symbol]) # Retrieve week number from datetime thisWeek = algorithm.Time.isocalendar()[1] # Only rebalance once a week if self.lastWeek == thisWeek: return insights self.lastWeek = thisWeek # Retrieve all symbols that are ready symbols = [symbol for symbol, symbolData in self.symbols.items() if symbolData.IsReady] # Sort by annualized returns in descending order sortedByReturns = sorted(symbols, key=lambda s: self.symbols[s].AnnualizedReturn, reverse=True) winningSymbols = sortedByReturns[:100] losingSymbols = sortedByReturns[-100:] # Sort symbols by continuity and filter for symbols with recent reversals longContinuity = [symbol for symbol in sorted(winningSymbols, key=lambda s: self.symbols[s].Continuity, reverse=False) if self.symbols[symbol].RecentDownTrend] shortContinuity = [symbol for symbol in sorted(losingSymbols, key=lambda s: self.symbols[s].Continuity, reverse=True) if self.symbols[symbol].RecentUpTrend] # Create insights for the top 5 most continous symbols which are in a recent reversal insights += [Insight.Price(symbol, timedelta(days = 5), InsightDirection.Up) for symbol in longContinuity[:5]] insights += [Insight.Price(symbol, timedelta(days = 5), InsightDirection.Down) for symbol in shortContinuity[:5]] return insights def OnSecuritiesChanged(self, algorithm, changes): for security in changes.AddedSecurities: symbol = security.Symbol if symbol not in self.symbols: self.symbols[symbol] = SymbolData(algorithm, symbol) for security in changes.RemovedSecurities: symbol = security.Symbol if symbol in self.symbols: # Remove consolidators from algorithm and remove symbol from dictionary algorithm.SubscriptionManager.RemoveConsolidator(symbol, self.symbols[symbol].monthlyConsolidator) algorithm.SubscriptionManager.RemoveConsolidator(symbol, self.symbols[symbol].dailyConsolidator) self.symbols.pop(symbol, None) class SymbolData: def __init__(self, algorithm, symbol): self.algorithm = algorithm self.symbol = symbol # Define daily and monthly rolling windows self.monthlyWindow = RollingWindow[TradeBar](13) self.dailyWindow = RollingWindow[TradeBar](280) # Define daily and monthly consolidators self.monthlyConsolidator = algorithm.Consolidate(symbol, Calendar.Monthly, self.OnMonthlyData) self.dailyConsolidator = TradeBarConsolidator(timedelta(days = 1)) #ZO CHANGES #MIGHT need to use MINUTE (to consolidate UP to 15M?) self.fifteenminConsolidator = TradeBarConsolidator(timedelta(minutes = 15)) algorithm.SubscriptionManager.AddConsolidator(symbol, self.fifteenminConsolidator) self.BBD = BollingerBands(20,1,MovingAverageType.Simple) self.BB15 = BollingerBands(20,1,MovingAverageType.Simple) algorithm.RegisterIndicator(symbol, self.BBD, self.dailyConsolidator) algorithm.RegisterIndicator(symbol, self.BB15, self.fifteenminConsolidator) #ZO # Register daily consolistor to algorithm algorithm.SubscriptionManager.AddConsolidator(symbol, self.dailyConsolidator) # Define and register ADX indicator self.adxThreshold = 25 self.adx = AverageDirectionalIndex(20) algorithm.RegisterIndicator(symbol, self.adx, self.dailyConsolidator) # Use historical data to warmup rolling windows, consolidators, and indicators history = algorithm.History(symbol, 280, Resolution.Daily) for bar in history.itertuples(): tbar = TradeBar(bar.Index[1], symbol, bar.open, bar.high, bar.low, bar.close, bar.volume) self.dailyWindow.Add(tbar) self.monthlyConsolidator.Update(tbar) self.fifteenminConsolidator.Update(tbar) self.adx.Update(tbar) def OnMonthlyData(self, bar): """Adds monthly bars to monthly rolling window""" self.monthlyWindow.Add(bar) @property def Continuity(self): """Returns the difference between losing days and winning days as a percentage of trading days""" positives = 0 negatives = 0 for bar in self.dailyWindow: dreturn = (bar.Close-bar.Open/bar.Open) if dreturn > 0: positives += 1 else: negatives += 1 return (negatives - positives)/(negatives + positives) @property def AnnualizedReturn(self): """Returns the 12 month compounded monthly return over a 13 month lookback period skipping the latest month.""" returns = [] for bar in self.monthlyWindow: monthlyReturn = (bar.Close/bar.Open) returns.append(monthlyReturn) returns.pop(0) return np.prod(returns) - 1 @property def RecentDownTrend(self): """Returns true if the ADX is above a given threshold and DX+ is lower than DX-""" return self.adx.Current.Value > self.adxThreshold and \ self.adx.PositiveDirectionalIndex.Current.Value < self.adx.NegativeDirectionalIndex.Current.Value @property def RecentUpTrend(self): """Returns true if the ADX is above a given threshold and DX+ is higher than DX-""" return self.adx.Current.Value > self.adxThreshold and \ self.adx.PositiveDirectionalIndex.Current.Value > self.adx.NegativeDirectionalIndex.Current.Value @property def IsReady(self): """Returns true if all the rolling windows and indicators are ready: have been updated with enough inputs to yield valid values""" return self.monthlyWindow.IsReady and self.dailyWindow.IsReady and self.adx.IsReady
''' In short_corss / long_cross #try: # cond2 = self.fmCond[0].Close < S2 #except: #cond2 = self.dailyWindow[0].Close < S2 #cond2 = self.algo.Securites[self.symbol].Price < S2 #This should work ? #cond2 = hist.close.iloc[-1] < S2 #try: # cond2 = self.fmCons[0].Close > R2 #Why not? #except: #cond2 = self.algo.Securites[self.symbol].Price > R2 #cond2 = hist.close.iloc[-1] > R2 '''
import numpy as np class CrossSectionalMomentum(QCAlgorithm): def Initialize(self): self.SetStartDate(2019, 1, 1) self.SetEndDate(2019, 1, 2) self.SetCash(1000000) self.SetBrokerageModel(AlphaStreamsBrokerageModel()) self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverse(self.CoarseSelection, self.FineSelection) self.SetAlpha(CounterTrendAlpha()) self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel()) self.SetRiskManagement(TrailingStopRiskManagementModel(0.02)) self.SetExecution(ImmediateExecutionModel()) def CoarseSelection(self, coarse): sbVol = sorted(coarse, key = lambda c: c.DollarVolume, reverse=True)[:100] return [s.Symbol for s in sbVol] def FineSelection(self, fine): #sbFCFG = sorted(fine, key = lambda x: x.ValuationRatios.EVToEBITDA, reverse=True) posEVTEB = [fine for fine in fine if fine.ValuationRatios.EVToEBITDA > 0] posFCFG = [fine for fine in posEVTEB if fine.OperationRatios.FCFGrowth.OneYear > 0] #SPY_ONLY = [fine for fine in fine if fine.Symbol[:3] == 'SPY'] return [f.Symbol for f in posFCFG] class CounterTrendAlpha(AlphaModel): def __init__(self): self.symbols = {} self.lastWeek = -1 def Update(self, algorithm, data): insights = [] # Update daily rolling windows with daily data -- Thanks ! for symbol, symbolData in self.symbols.items(): if data.ContainsKey(symbol) and data[symbol] != None: symbolData.dailyWindow.Add(data[symbol]) #algorithm.Debug(f'{symbol}, {symbolData}') syms = [sym for sym, data in self.symbols.items() if data.IsReady] #syms = [symbol for symbol, symbolData in self.symbols.items()] #if symbolData.IsReady] #self.longs = [sym for sym in syms if sym.] self.longs = [symbol for symbol, SymbolData in self.symbols.items() if SymbolData.long_cross] self.shorts = [symbol for symbol, SymbolData in self.symbols.items() if SymbolData.short_cross] for sym in self.longs: self.Debug(f'{self.Securities[sym].Price}') insights += [Insight.Price(symbol, timedelta(days=99), InsightDirection.Up) for symbol in self.longs] insights += [Insight.Price(symbol, timedelta(days=99), InsightDirection.Down) for symbol in self.shorts] #Alpha Models DONT EMIT -- simply RETURN insights.. PCM EMITS? return insights def OnSecuritiesChanged(self, algorithm, changes): for security in changes.AddedSecurities: symbol = security.Symbol if symbol not in self.symbols: self.symbols[symbol] = SymbolData(algorithm, symbol) #INITIALIZE Indics etc algorithm.Debug(f'{self.symbols[symbol]}') for security in changes.RemovedSecurities: symbol = security.Symbol if symbol in self.symbols: # Remove consolidators from algorithm and remove symbol from dictionary -- GC Equiv. algorithm.SubscriptionManager.RemoveConsolidator(symbol, self.symbols[symbol].fmCons) algorithm.SubscriptionManager.RemoveConsolidator(symbol, self.symbols[symbol].dailyCons) self.symbols.pop(symbol, None) class SymbolData: '''Get required indicators, windows, etc''' #DONT NEED HISTORY dont think -- can CALL INSIDE and DECOUPLE FURTHER def __init__(self, algorithm, symbol): self.algo = algorithm self.symbol = symbol self.dailyWindow = RollingWindow[TradeBar](10) self.fmWindow = RollingWindow[TradeBar](10) self.minWindow = RollingWindow[TradeBar](10) self.uppers = RollingWindow[float](5) self.lowers = RollingWindow[float](5) #Consolidators... self.dailyCons = TradeBarConsolidator(timedelta(days = 1)) self.fmCons = TradeBarConsolidator(timedelta(minutes = 15)) self.minCons = TradeBarConsolidator(timedelta(minutes = 1)) algorithm.SubscriptionManager.AddConsolidator(symbol, self.fmCons) algorithm.SubscriptionManager.AddConsolidator(symbol, self.dailyCons) algorithm.SubscriptionManager.AddConsolidator(symbol, self.minCons) #Indicators + Subscriptions self.BBD = BollingerBands(20,1,MovingAverageType.Simple) self.BB15 = BollingerBands(20,1, MovingAverageType.Simple) self.MA = SimpleMovingAverage(12) algorithm.RegisterIndicator(symbol, self.BBD, self.dailyCons) algorithm.RegisterIndicator(symbol, self.BB15, self.fmCons) algorithm.RegisterIndicator(symbol, self.MA, self.dailyCons) #This might need to be minute for 15M consolidator history = algorithm.History(symbol, 35, Resolution.Daily) #MIGHT need to be minute for bar in history.itertuples(): tb = TradeBar(bar.Index[1], bar.open, bar.high, bar.low, bar.close, bar.volume) #Consolidators self.dailyCons.Update(tb) self.fmCons.Update(tb) self.minCons.Update(tb) #Windows self.dailyWindow.Add(tb) self.fmWindow.Add(tb) self.minWindow.Add(tb) #Indicators self.BBD.Update(tb.EndTime, tb.Close) self.BB15.Update(tb.EndTime, tb.Close) self.MA.Update(tb.EndTime, tb.Close) #Indic Windows self.uppers.Add(self.BBD.UpperBand.Current.Value) self.lowers.Add(self.BBD.LowerBand.Current.Value) def res_and_sup(self): #Helper function -- for Resistance and Support levels. if self.IsReady: center = (self.dailyWindow[0].Open + self.dailyWindow[1].Close) / 2 R2 = center + .5 * self.MA.Current.Value S2 = center - .5 * self.MA.Current.Value return R2, S2 @property def long_cross(self): #hist = self.History(self.symbol, 1,Resolution.Minute) cond1, cond2 = False, False if self.IsReady: R2, _ = self.res_and_sup() cond1 = self.BB15.LowerBand.Current.Value > self.uppers[3] cond2 = self.fmWindow[0].Close > R2 return cond1 and cond2 @property def short_cross(self): #hist = self.History(self.symbol, 1,Resolution.Minute) cond1, cond2 = False, False if self.IsReady: _, S2 = self.res_and_sup() cond1 = self.BB15.UpperBand.Current.Value < self.lowers[3] cond2 = self.fmWindow[0].Close < S2 return cond1 and cond2 #Very fucking cool ! Love this implementation style -- Very C#. @property def pivlo(self): for i in range(1,8): if self.fmWindow[i].Close > self.fmWindow[i + 1].Close: return False if self.fmWindow[0].Close < self.fmWindow[1].Close: return False return True @property def pivhi(self): for i in range(1,8): if self.fmWindow[i].Close < self.fmWindow[i + 1].Close: return False if self.fmWindow[0].Close > self.fmWindow[1].Close: return False return True @property def IsReady(self): return self.BBD.IsReady and self.BB15.IsReady and self.MA.IsReady and self.uppers.IsReady and self.lowers.IsReady ''' Changes -- Changed logic of cond2's - why does self.Securities.Price not work? '''