Overall Statistics
Total Trades
387
Average Win
0.80%
Average Loss
-1.01%
Compounding Annual Return
7.275%
Drawdown
18.500%
Expectancy
0.244
Net Profit
66.637%
Sharpe Ratio
0.579
Probabilistic Sharpe Ratio
9.744%
Loss Rate
31%
Win Rate
69%
Profit-Loss Ratio
0.79
Alpha
0.062
Beta
0.035
Annual Standard Deviation
0.115
Annual Variance
0.013
Information Ratio
-0.341
Tracking Error
0.191
Treynor Ratio
1.886
Total Fees
$1168.52
Estimated Strategy Capacity
$67000.00
Lowest Capacity Asset
DWAS V8DKFGNVD0TH
""" Rules: The assets are ranked according to an average of the asset's 1, 3, 6, and 12-month 
    total returns (Momentum factor). 
    The model invests 33% of capital into the top 3 assets given that the asset's price is greater than 
    the asset's 10-month SMA (Trend factor), else the allocation is held in cash or T-bills.
"""
class EmotionalFluorescentYellowAnt(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2014, 5, 1)
        self.SetCash(100000) 
        self.leverage = 1.0
        
        #\\\\\  ASSETS ////  MTUM=APR2013
        assets = ["VTV", "MTUM", "VBR", "DWAS", "EFA", "EEM", "IEF", "IGOV", "LQD", "TLT", "GSG", "IAU", "VNQ"] 
        self.AddEquity("SPY", Resolution.Daily) #SchedulingTasks
        
        #\\\\\  SIGNALS //// 
        self.DataBySymbol = {}
        for ticker in assets:
            if ticker == "IEF":
                self.ief = self.AddEquity(ticker, Resolution.Daily).Symbol
                self.DataBySymbol[self.ief] = SymbolData(self.ief, self)
            else:
                symbol = self.AddEquity(ticker, Resolution.Daily).Symbol
                self.DataBySymbol[symbol] = SymbolData(symbol, self)
        
        #\\\\\ TIMING ////
        Timing = 60 
        self.Schedule.On(self.DateRules.MonthEnd("SPY"), self.TimeRules.AfterMarketOpen("SPY", Timing), self.MonthlyRebalance)
        
        #\\\\\ RISK MANAGEMENT / LOGGING ////
        #self.SetRiskManagement(MaximumUnrealizedProfitPercentPerSecurity(0.25))
        self.hwm = self.Portfolio.TotalPortfolioValue
        self.marginRemaining = int(self.Portfolio.MarginRemaining)
        self.ClosingPortValue = int(self.Portfolio.TotalPortfolioValue)
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.BeforeMarketClose('SPY', 1), self.CheckDailyPerformance)

    def OnData(self, data):
      
        value = self.Portfolio.TotalPortfolioValue
        if value > self.hwm:
            self.hwm = value
    
    def MonthlyRebalance(self):
        
        # Rank assets according to weighted 1-3-6-12month momentum
        momentum_scores = [self.DataBySymbol[x].Momentum for x in self.DataBySymbol]
        momentum_sort = sorted([k for k,v in self.DataBySymbol.items()], key=lambda x: self.DataBySymbol[x].Momentum, reverse=True)
        targets = momentum_sort[:3]
        
        # Check if assets are above or below MovingAverage
        for symbol in self.DataBySymbol:
            self.DataBySymbol[symbol].Weight = 0
            
            if symbol in targets:
                price = self.Securities[symbol].Price
                
                # If asset is below, hold bonds 33%, else hold asset 33%
                if price != 0 and self.DataBySymbol[symbol].MovAvg.IsReady:
                    if price > self.DataBySymbol[symbol].MovAvg.Current.Value:
                        self.DataBySymbol[symbol].Weight = 0.33
                    else:
                        self.DataBySymbol[self.ief].Weight += 0.33
                else:
                    self.DataBySymbol[self.ief].Weight += 0.33
                        
        self.PlaceTrades()
    
    def PlaceTrades(self):
        
        weights = {}
        for symbol in self.DataBySymbol:
            weights[symbol] = self.DataBySymbol[symbol].Weight
        self.SetHoldings([PortfolioTarget(target, weight*self.leverage) for target, weight in weights.items()])
        self.Notify.Sms("+61411067329", "GTAA13(3) Rebalancing...")
        
    def CheckDailyPerformance(self):
        
        ### Closing Plotting
        leverage = round(self.Portfolio.TotalHoldingsValue / self.Portfolio.TotalPortfolioValue, 2 )
        percent_DD = round( ( self.Portfolio.TotalPortfolioValue / self.hwm - 1 ) * 100,  1)
        perf = round( (self.Portfolio.TotalPortfolioValue / self.ClosingPortValue - 1) * 100, 1)
        self.ClosingPortValue = int(self.Portfolio.TotalPortfolioValue)    
        
        for symbol in self.DataBySymbol:
            if self.DataBySymbol[symbol].Momentum is not None:
                self.Plot('MovAvg', f'{str(symbol)}', self.DataBySymbol[symbol].MovAvg.Current.Value)
                self.Plot('Momentum', f'{str(symbol)}', self.DataBySymbol[symbol].Momentum)
                
        for kvp in self.Portfolio:
            symbol = kvp.Key
            holding = kvp.Value 
            self.Plot('AssetWeights %', f"{str(holding.Symbol)}%", holding.HoldingsValue/self.Portfolio.TotalPortfolioValue)

#\\\\\  INDICATORS //// 
class SymbolData:
    
    def __init__(self, symbol, algorithm):
        self.Symbol = symbol
        self.Weight = None
        self.algorithm = algorithm
        self.MovAvg = SimpleMovingAverage(210)
        self.Momentum = None
        self.MOMPone = MomentumPercent(21)
        self.MOMPthree = MomentumPercent(63)
        self.MOMPsix = MomentumPercent(126)
        self.MOMPtwelve = MomentumPercent(252)
        

        # Warm up MA
        history = algorithm.History([self.Symbol], 253, Resolution.Daily).loc[self.Symbol]
        # Use history to build our SMA
        for time, row in history.iterrows():
            self.MovAvg.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.MovAvg.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)