Overall Statistics
Total Trades
827
Average Win
0.62%
Average Loss
-0.41%
Compounding Annual Return
7.258%
Drawdown
13.100%
Expectancy
0.606
Net Profit
173.418%
Sharpe Ratio
0.674
Probabilistic Sharpe Ratio
6.185%
Loss Rate
37%
Win Rate
63%
Profit-Loss Ratio
1.53
Alpha
0.035
Beta
0.224
Annual Standard Deviation
0.078
Annual Variance
0.006
Information Ratio
-0.188
Tracking Error
0.147
Treynor Ratio
0.235
Total Fees
$891.94
Estimated Strategy Capacity
$190000.00
Lowest Capacity Asset
BIL TT1EBZ21QWKL
#region imports
from AlgorithmImports import *
#endregion
# from clr import AddReference
# AddReference("System.Core")
# AddReference("QuantConnect.Common")
# AddReference("QuantConnect.Algorithm")

# from System import *
# from QuantConnect import *
# from QuantConnect.Algorithm import QCAlgorithm
# from QuantConnect.Data.UniverseSelection import *
import decimal as d
from datetime import datetime, timedelta
from decimal import Decimal
import numpy as np
import pandas as pd

class ProtectiveAssetAllocationAlgo(QCAlgorithm):

    def Initialize(self):
        self.SetCash(25000)
        self.SetStartDate(2008,1,1)
        
        ##Parameters for algorithm 
        #self.lookback = 4  ##Lookback in months
        self.lookback = 6  ##Lookback in months
        self.protection = 1 ##Protection factor = 0(low), 1, 2 (high)
        #self.topM = 6 ##topM is the max number of equities
        self.topM = 4 ##topM is the max number of equities
        self.n_levels = 2 ##number of discrete levels for bond_fraction (>=2)
        #self.SafetySymbols = ["IEF"] ##risk free asset to move into for protection 
        self.SafetySymbols = ["TIP", "IEF", "BIL", "TLT", "SPDN"] ##risk free asset to move into for protection 
        self.N_safe = int(len(self.SafetySymbols))

        # these are the growth symbols we"ll rotate through
        self.GrowthSymbols =["SPY", "QQQ", "RSP", "TLT",
                             "VGK", "EWJ", "EEM", 
                             "DBC"]
                        
        self.N_eq = len(self.GrowthSymbols)
        # these are the safety symbols we go to when things are looking bad for growth

        #self.AddSecurity(SecurityType.Equity, "IEF", Resolution.Minute)
        #self.AddSecurity(SecurityType.Equity, "BIL", Resolution.Minute)
        #self.AddSecurity(SecurityType.Equity, "SPDN", Resolution.Minute)
        
        self.syl_objs = []
        self.syl_objs_safe = []
        
        # we'll hold some computed data in these guys
        for symbol in list(self.GrowthSymbols):
            self.syl_objs.append(self.AddSecurity(SecurityType.Equity, symbol, Resolution.Minute).Symbol)
        for symbol in list(self.SafetySymbols):
            self.syl_objs_safe.append(self.AddSecurity(SecurityType.Equity, symbol, Resolution.Minute).Symbol)
        
        for syl_obj in self.syl_objs:
            syl_obj.lookbackMovingAverage = self.SMA(syl_obj, 21*self.lookback, Resolution.Daily)
            syl_obj.mom21 = self.MOMP(syl_obj, 21, Resolution.Daily)
            syl_obj.mom64 = self.MOMP(syl_obj, 64, Resolution.Daily)
            syl_obj.mom128 = self.MOMP(syl_obj, 128, Resolution.Daily)
            
        #for syl_obj_safe in self.syl_objs_safe:
        for i, item in enumerate(self.syl_objs_safe):
            self.syl_objs_safe[i].Ticker = item.Value
            self.syl_objs_safe[i].lookbackMovingAverage = self.SMA(item, 21*self.lookback, Resolution.Daily)
            self.syl_objs_safe[i].mom21 = self.MOMP(item, 21, Resolution.Daily)
            self.syl_objs_safe[i].mom64 = self.MOMP(item, 64, Resolution.Daily)
            self.syl_objs_safe[i].mom128 = self.MOMP(item, 128, Resolution.Daily)

        #self.SetWarmup(21*self.lookback+1)
        self.SetWarmup(130+1)
        
        self.Schedule.On(self.DateRules.MonthStart("SPY"),
                self.TimeRules.At(9,45),
                Action(self.Rebalance))

    def OnData(self, data):
        pass 
    
    def Rebalance(self):
        
        # poll the Growth Symbols set to determine the number of assets with positive momentum

        n = 0
        for syl_obj in self.syl_objs:
            price = self.Securities[syl_obj].Price
            sma = syl_obj.lookbackMovingAverage.Current.Value
            if price > sma: n += 1
            #if combmom > 0: n += 1
        
            
        # Calculate the bond fraction based on N_eq, prot, and n
        # This is the portion to be invested in safe harbor
        # Calculate equity fraction and weight per equity (frac_eq, w_eq) 
        # Limit bond_fraction to a discrete number of levels (n_levels >=2)
        
        n1 = int(int(self.protection) * int(self.N_eq) / 4.0)
        bond_fraction = float(min(1.0, float(float(self.N_eq) - float(n)) / float(float(self.N_eq) - float(n1))))
        #n_steps = float(self.n_levels) - 1.0
        #bond_fraction = float(bond_fraction*n_steps)/n_steps
    
        w_safe = float(bond_fraction)
        self.Log("Safe Weight "+str(w_safe))
        
        #
        # calculate the MOM for each equity
        # determine the number of equities to be purchases
        #
     
        N = 0
        for syl_obj in self.syl_objs:
            price = self.Securities[syl_obj].Price
            sma = syl_obj.lookbackMovingAverage.Current.Value
            combmom = (syl_obj.mom64.Current.Value)
            syl_obj.MOM = combmom
            #syl_obj.MOM = (price / sma) - 1
            #if syl_obj.MOM > 0.0: N+=1
            if combmom > 0.0: N+=1
        
        for i, item in enumerate(self.syl_objs_safe):
            combmom = (item.mom64.Current.Value)
            self.syl_objs_safe[i].MOM = combmom

        frac_eq = float(1.0 - w_safe)
        n_eq = min(N, self.topM)
        w_eq = 0.0
        if N > 0: w_eq = float(float(frac_eq) / float(n_eq))
        mom_threshold = sorted([i.MOM for i in self.syl_objs], reverse=True)[n_eq - 1]
        
        if frac_eq > 0.0:
            for syl_obj in self.syl_objs:
                if syl_obj.MOM >= float(mom_threshold):
                    self.SetHoldings(syl_obj, w_eq)
                else:
                    if self.Portfolio[syl_obj].HoldStock:
                        self.Liquidate(syl_obj)
            sortedSafe = sorted([(item.Ticker, item.MOM) for item in self.syl_objs_safe], reverse=True)[-1]
            #self.SetHoldings(self.SafetySymbols[0], w_safe)
            self.SetHoldings(sortedSafe[0], w_safe)
        else:
            for syl_obj in self.syl_objs:
                if self.Portfolio[syl_obj].HoldStock:
                    self.Liquidate(syl_obj)

            df_safe = pd.DataFrame(self.syl_objs_safe)
            sortedSafe = sorted([(item.Ticker, item.MOM) for item in self.syl_objs_safe], reverse=True)[-1]
            self.SetHoldings(sortedSafe[0], 1)
            #self.SetHoldings(self.SafetySymbols[-1], .5)