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)