Overall Statistics |
Total Trades 661 Average Win 0.27% Average Loss -0.34% Compounding Annual Return 8.299% Drawdown 17.400% Expectancy 0.331 Net Profit 74.080% Sharpe Ratio 0.924 Probabilistic Sharpe Ratio 38.387% Loss Rate 26% Win Rate 74% Profit-Loss Ratio 0.81 Alpha 0 Beta 0 Annual Standard Deviation 0.092 Annual Variance 0.008 Information Ratio 0.924 Tracking Error 0.092 Treynor Ratio 0 Total Fees $798.86 Estimated Strategy Capacity $130000.00 |
'''An implementation of Meb Faber's AGGRESSIVE model: Global Tactical Asset Allocation model GTAA(13) ranking stocks on 1/3/6/12month MOM and owning TOP6 with 10-month SimpleMovingAverage Filter (200day), monthly rebalance: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=962461 "A Quantitative Approach to Tactical Asset Allocation" published May 2006. Analysis only occurs at month End/Start, signals are NOT generated intra-month. ''' # self.Debug(str(dir( x ))) from alpha_model import MomentumAndSMAAlphaModel class GlobalTacticalAssetAllocation(QCAlgorithm): def Initialize(self): #self.SetStartDate(date(2014, 1, 29) + timedelta(days=200)) self.SetStartDate(2014, 5, 20) #self.SetEndDate(2020, 5, 20) self.SetCash(100000) self.Settings.FreePortfolioValuePercentage = 0.02 self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) self.UniverseSettings.Resolution = Resolution.Daily tickerWeightPairs = { # (1x) ETF EarliestStartDate: 2014/02 'VLUE': 0.05, # 5% US Large Value, (VLUE, 2013/05) 'MTUM': 0.05, # 5% US Large Momentum (MTUM, 2013/5) 'VBR': 0.05, # 5% US Small Cap Value (VBR) 'XSMO': 0.05, # 5% US Small Cap Momentum (XSMO) 'EFA': 0.10, # 10% Foreign Developed (EFA) 'VWO': 0.10, # 10% Foreign Emerging (VWO) 'IEF': 0.05, # 5% US 10Y Gov Bonds (IEF) 'TLT': 0.05, # 5% US 30Y Gov Bonds (TLT) 'LQD': 0.05, # 5% US Corporate Bonds (LQD) 'BWX': 0.05, # 5% Foreign 10Y Gov Bonds (BWX) 'DBC': 0.10, # 10% Commodities (DBC) 'GLD': 0.10, # 10% Gold (GLD) 'VNQ': 0.20 # 20% NAREIT (VNQ) } symbols = [Symbol.Create(ticker, SecurityType.Equity, Market.USA) for ticker in [*tickerWeightPairs]] self.AddUniverseSelection( ManualUniverseSelectionModel(symbols) ) weightsTotal = sum(tickerWeightPairs.values()) if weightsTotal != 1.0: self.Log(f"********** Weights = {str(weightsTotal)}. WILL be scaled down to 1.0 ********** ") self.AddAlpha( MomentumAndSMAAlphaModel( tickerWeightPairs) ) self.Settings.RebalancePortfolioOnSecurityChanges = False self.Settings.RebalancePortfolioOnInsightChanges = False self.SetPortfolioConstruction( InsightWeightingPortfolioConstructionModel(self.RebalanceFunction,\ PortfolioBias.Long) ) self.SetExecution( ImmediateExecutionModel() ) self.AddRiskManagement( NullRiskManagementModel() ) self.Log("GTAA(13) Initialsing... ") # Initialise plot #assetWeightsPlot = Chart('AssetWeights %') #for ticker in [*tickerWeightPairs]: # assetWeightsPlot.AddSeries(Series(ticker, SeriesType.Line, f'{ticker}%')) def RebalanceFunction(self, time): return Expiry.EndOfMonth(self.Time) def OnData(self, data): # Update Plot self.Plot('Number of Holdings', len(self.Portfolio)) #for kvp in self.Portfolio: # symbol = kvp.Key # holding = kvp.Value # self.Plot('AssetWeights %', f"{str(holding.Symbol)}%", holding.HoldingsValue/self.Portfolio.TotalPortfolioValue)
class MomentumAndSMAAlphaModel(AlphaModel): """ Alpha model(Original): Price > SMA own asset, else own RiskOff (IEF). AggressiveModel: EqualWeight top6 ranked by average 1,3,6,12month momentum, if Price > SMA. Else RiskOff. """ def __init__(self, tickerWeightPairs, smaLength=200, resolution=Resolution.Daily): '''Initializes a new instance of the SmaAlphaModel class Args: period: The SMA period resolution: The reolution for the SMA''' self.tickerWeightPairs = tickerWeightPairs self.smaLength = smaLength self.resolution = resolution self.symbolDataBySymbol = {} self.month = -1 self.riskOffAsset = "IEF" def Update(self, algorithm, data): '''This is called each time the algorithm receives data for (@resolution of) subscribed securities Returns: The new insights generated. THIS: analysis only occurs at month start, so any signals intra-month are disregarded.''' ######## Plotting currentTotalPortfolioValue = algorithm.Portfolio.TotalPortfolioValue # get current portfolio value totalPortfolioExposure = (algorithm.Portfolio.TotalHoldingsValue / currentTotalPortfolioValue) * 100 algorithm.Plot('Chart Total Portfolio Exposure %', 'Daily Portfolio Exposure %', totalPortfolioExposure) ######## if self.month == algorithm.Time.month: return [] self.month = algorithm.Time.month insights = [] risk_off_weight = 0 momentum_scores = [self.symbolDataBySymbol[x].Momentum for x in self.symbolDataBySymbol] momentum_sort = sorted([k for k,v in self.symbolDataBySymbol.items()], # if v.BlendedMomentum.IsReady?? key=lambda x: self.symbolDataBySymbol[x].Momentum, reverse=True) targets = momentum_sort[:6] for symbol, symbolData in self.symbolDataBySymbol.items(): if symbol in targets: if str(symbol) == self.riskOffAsset: risk_off_weight += 0.16 continue price = algorithm.Securities[symbol].Price if price != 0 and symbolData.MovingAverage.IsReady: if price > symbolData.MovingAverage.Current.Value: insights.append(Insight.Price(symbol, Expiry.EndOfMonth, InsightDirection.Up, None, None, None, 0.16)) else: insights.append(Insight.Price(symbol, Expiry.EndOfMonth, InsightDirection.Flat, None, None, None, 0)) risk_off_weight += 0.16 else: insights.append(Insight.Price(symbol, Expiry.EndOfMonth, InsightDirection.Flat, None, None, None, 0)) risk_off_weight += 0.16 insights.append(Insight.Price(self.riskOffAsset, Expiry.EndOfMonth, InsightDirection.Up, None, None, None, risk_off_weight)) return insights def OnSecuritiesChanged(self, algorithm, changes): for added in changes.AddedSecurities: weight = self.tickerWeightPairs[str(added)] self.symbolDataBySymbol[added.Symbol] = SymbolData(added.Symbol, weight, algorithm, self.smaLength, self.resolution) for removed in changes.RemovedSecurities: symbolData = self.symbolDataBySymbol.pop(removed.Symbol, None) if symbolData: # Remove consolidator symbolData.dispose() class SymbolData: def __init__(self, symbol, weight, algorithm, smaLength, resolution): self.Symbol = symbol self.Weight = weight self.MovingAverage = SimpleMovingAverage(smaLength) self.Momentum = None self.MOMPone = MomentumPercent(21) self.MOMPthree = MomentumPercent(63) self.MOMPsix = MomentumPercent(126) self.MOMPtwelve = MomentumPercent(252) self.algorithm = algorithm # Warm up MA history = algorithm.History([self.Symbol], 253, resolution).loc[self.Symbol] # Use history to build our SMA for time, row in history.iterrows(): self.MovingAverage.Update(time, row["close"]) self.MOMPone.Update(time, row["close"]) self.MOMPthree.Update(time, row["close"]) self.MOMPsix.Update(time, row["close"]) self.MOMPtwelve.Update(time, row["close"]) # Setup indicator consolidator self.consolidator = TradeBarConsolidator(timedelta(1)) self.consolidator.DataConsolidated += self.CustomDailyHandler algorithm.SubscriptionManager.AddConsolidator(self.Symbol, self.consolidator) def CustomDailyHandler(self, sender, consolidated): self.MovingAverage.Update(consolidated.Time, consolidated.Close) self.MOMPone.Update(consolidated.Time, consolidated.Close) self.MOMPthree.Update(consolidated.Time, consolidated.Close) self.MOMPsix.Update(consolidated.Time, consolidated.Close) self.MOMPtwelve.Update(consolidated.Time, consolidated.Close) self.Momentum = self.MOMPone.Current.Value * 12 + self.MOMPthree.Current.Value * 4 + \ self.MOMPsix.Current.Value * 2 + self.MOMPtwelve.Current.Value def dispose(self): self.algorithm.SubscriptionManager.RemoveConsolidator(self.Symbol, self.consolidator)