Overall Statistics
Total Trades
935
Average Win
0.23%
Average Loss
-0.13%
Compounding Annual Return
2.702%
Drawdown
7.500%
Expectancy
0.784
Net Profit
66.665%
Sharpe Ratio
0.631
Probabilistic Sharpe Ratio
2.491%
Loss Rate
36%
Win Rate
64%
Profit-Loss Ratio
1.80
Alpha
0.009
Beta
0.404
Annual Standard Deviation
0.03
Annual Variance
0.001
Information Ratio
-0.146
Tracking Error
0.035
Treynor Ratio
0.048
Total Fees
$2339.62
Estimated Strategy Capacity
$1400000.00
Lowest Capacity Asset
STPZ UFAW82TG117P
"""
- One of the original Agile Strategies. Momentum is not working correctly with the migration.  Returns are much less and drawdown is 1.5x what we showed previously. 
Look at different selection criteria in QC
- Agile Propritary
"""
import math
import sys
import numpy as np
import statistics
from time import sleep

'''
Institutional Bond
'''

class DeterminedLightBrownAlpaca(QCAlgorithm):
        
    #List<SymbolData> SymbolData = new List<SymbolData>();
    
    def Initialize(self):
        # LIVE TRADING
        if self.LiveMode:
            self.Debug("Trading Live!")
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
        
        # Group Trading
        # Use a default FA Account Group with an Allocation Method
        self.DefaultOrderProperties = InteractiveBrokersOrderProperties()
        
        # account group created manually in IB/TWS
        self.DefaultOrderProperties.FaGroup = "Institutional Bond"

        # supported allocation methods are: EqualQuantity, NetLiq, AvailableEquity, PctChange
        self.DefaultOrderProperties.FaMethod = "AvailableEquity"

        # set a default FA Allocation Profile
        # Alex: I commented the following line out, since it would "reset" the previous settings
        #self.DefaultOrderProperties = InteractiveBrokersOrderProperties()

        # allocation profile created manually in IB/TWS
        # self.DefaultOrderProperties.FaProfile = "TestProfileP"
        
        #Algo Start
        self.SetCash(100000)
        self.SetStartDate(2003, 1, 1)
        #self.SetEndDate(2007, 1, 1)
        
        self.SetBenchmark("AGG")
        
        self.SymbolData = []
        
        self.Keeps = int(self.GetParameter("Keeps"))
        self.CashReservePct = float(self.GetParameter("CashReservePct"))
        self.WeightRa = float(self.GetParameter("WeightRa"))
        self.WeightRb = float(self.GetParameter("WeightRb"))
        self.WeightV = float(self.GetParameter("WeightV"))
        self.FilterSMA = int(self.GetParameter("FirstSMA"))
        self.LookbackRa = self.GetParameter("LookbackRa")
        self.LookbackRb = self.GetParameter("LookbackRb")
        self.LookbackV = self.GetParameter("LookbackV")       
        self.Url = ""
        self.RebalancePeriod = self.GetParameter("RebalancePeriod")
        self.GrowthSymbols = []
        self.SafetySymbol = self.AddEquity("SHY", Resolution.Daily).Symbol
        # List<string> GrowthSymbols = new List<String>{
        #     // safety symbol
        #     "SHY"
        # };
        self.first = True
        self.LastRotationTime = self.Time
        #self.LastRotationTime = DateTime.Now
        
        self.SetGrowthSymbols()
        
        self.Debug("WeightRa: " + str(self.WeightRa))
        self.Debug("WeightRb: " + str(self.WeightRb))
        self.Debug("WeightV: " + str(self.WeightV))
        self.Debug("SymbolUrl: " + str(self.Url))
        self.Debug("RebalancePeriod: " + str(self.RebalancePeriod))
        
        periodADays = self.DaysFromLookback(self.LookbackRa)
        periodBDays = self.DaysFromLookback(self.LookbackRb)
        periodVolDays = self.DaysFromLookback(self.LookbackV)
        #self.Debug("LookbackRa: {0} ({1} days)", str(self.LookbackRa), str(periodADays))
        #self.Debug("LookbackRb: {0} ({1} days)", str(self.LookbackRb), str(periodBDays))
        #self.Debug("LookbackV: {0} ({1} days)", str(self.LookbackV), str(periodVolDays))
        
        for symbol in self.GrowthSymbols:
            self.Debug("adding symbol to universe: " + symbol);
            
            symbolObject = self.AddSecurity(SecurityType.Equity, symbol, Resolution.Daily).Symbol
            
            # TODO - add parameters for lookback periods
            periodAPerf = self.MOMP(symbol, periodADays, Resolution.Daily)
            periodBPerf = self.MOMP(symbol, periodBDays, Resolution.Daily)  #// (252/12) * x months
            
            ''' FIXME '''
            #// FIXME -The moving average is calculated using the closing price from the last day of each month(e.g. a 10 month moving average has 10 datapoints)
            #filterSMA = self.SMA(symbol, 21*self.FilterSMA, Resolution.Daily)
            ''' Update this indicator every month '''
            #filterSMA = SimpleMovingAverage(21*self.FilterSMA)
            filterSMA = SimpleMovingAverage(self.FilterSMA)
            consolidator = TradeBarConsolidator(CalendarType.Monthly)
            self.RegisterIndicator(symbolObject, filterSMA, consolidator)

            #// we use log returns instead of definition in replay
            std = StandardDeviation(periodVolDays)
            annualizationFactor = float(math.sqrt(252))
            logReturns = self.LOGR(symbol, periodVolDays, Resolution.Daily)
            
            #CompositeIndicator<IndicatorDataPoint> volatility = std.Of(logReturns).Times(annualizationFactor);
            volatility_ = IndicatorExtensions.Of(std, logReturns)
            volatility = IndicatorExtensions.Times(volatility_, annualizationFactor)
            #sleep(1)
            
            sData = SymbolData(symbol, periodAPerf, periodBPerf, volatility, filterSMA)
            self.SymbolData.append(sData)
            
            
            # SymbolData.Add(new SymbolData
            # {
            #     Symbol = symbol,
            #     ReturnA = periodAPerf,
            #     ReturnB = periodBPerf,
            #     Volatility = volatility,
            #     FilterSMA = filterSMA
            # });
        

        #// assumption we don't look back more than a year
        self.SetWarmup(timedelta(days=365))
        self.lastDay = -1
        self.newWeek = True
        self.newMonth = True
        self.counter = 2
        self.newBiWeek = True
        
        self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
        
        self.Schedule.On(self.DateRules.MonthStart("SPY"),
                 self.TimeRules.At(0, 0),       
                 self.TrackMonth)
        
        self.Schedule.On(self.DateRules.WeekStart("SPY"),
                 self.TimeRules.At(0, 0),       
                 self.TrackWeek)
        
        self.Schedule.On(self.DateRules.WeekStart("SPY"),
                 self.TimeRules.At(0, 0),       
                 self.TrackBiWeekly)
        
    def TrackMonth(self):
        self.newMonth = True
        
    def TrackWeek(self):
        self.newWeek = True
        
    def TrackBiWeekly(self):
        if self.counter == 2:
            self.newBiWeek = True
            self.counter = 1
        else:
            self.counter += 1
        
    def DaysFromLookback(self, period):
        strAmount = period[0:len(period)-1]
        #strAmount = period.substr(0, len(period)-1)
        amount = np.int32(strAmount)
        #amount = int32.Parse(strAmount)
        multiplier = period.endswith("M")
        if multiplier == False:
            multiplier = 1
        else:
            multiplier = 21
        
        return multiplier * amount
    
    def SetGrowthSymbols(self):
        self.GrowthSymbols.append("BIL")
        self.GrowthSymbols.append("FLOT")
        self.GrowthSymbols.append("FLTR")
        self.GrowthSymbols.append("GBF")
        self.GrowthSymbols.append("GOVT")
        self.GrowthSymbols.append("GSY")
        self.GrowthSymbols.append("GVI")
        self.GrowthSymbols.append("IEI")
        self.GrowthSymbols.append("LQD")
        self.GrowthSymbols.append("MBB")
        self.GrowthSymbols.append("MINT")
        self.GrowthSymbols.append("SHM")
        self.GrowthSymbols.append("SHY")
        self.GrowthSymbols.append("SPIP")
        self.GrowthSymbols.append("SPTI")
        self.GrowthSymbols.append("STIP")
        self.GrowthSymbols.append("STPZ")
        self.GrowthSymbols.append("USIG")
        self.GrowthSymbols.append("VCSH")
        #self.GrowthSymbols.append("TBF")
        #self.GrowthSymbols.append("TBX")
        self.GrowthSymbols.append("TLT")
        
        
    def GetEtfReplayRankings(self):
        
        #orderedReturnA = self.SymbolData.OrderByDescending(x.ReturnA)
        orderedReturnA = sorted(self.SymbolData, key = lambda x: x.ReturnA.Current.Value, reverse = True)
        #orderedReturnB = self.SymbolData.OrderByDescending(x.ReturnB)
        orderedReturnB = sorted(self.SymbolData, key = lambda x: x.ReturnB.Current.Value, reverse = True)
        #orderedVol = self.SymbolData.OrderBy(x.Volatility)
        orderedVol = sorted(self.SymbolData, key = lambda x: x.Volatility.Current.Value, reverse = False)
        rankings = {}

        #// add all the symbols
        for sd in self.SymbolData:
            rankings[sd.Symbol] = 0.0

        for i in range(0, len(orderedReturnA), 1):
            current = orderedReturnA[i]
            rankings[current.Symbol] += i * self.WeightRa
        
        for i in range(0, len(orderedReturnB), 1):
            current = orderedReturnB[i]
            rankings[current.Symbol] += i * self.WeightRb

        for i in range(0, len(orderedVol), 1):
            current = orderedVol[i]
            rankings[current.Symbol] += i * self.WeightV
        
        return rankings
        
        
    def OnData(self, data):
        
        if self.IsWarmingUp: return

        self.Log("OnData slice: date=" + str(self.Time))# + ", cnt=" + data.Count);

        try:
            rebalance = False
            if self.first:
                rebalance = True
                self.first = False
            else:
                #// var delta = Time.Subtract(LastRotationTime);
                #// rebalance = delta > RotationInterval;
                
                rebalance = self.LastRotationTime.month != self.Time.month
                if (self.RebalancePeriod == "Quarterly"):
                    #// January - 1
                    #// April - 4
                    #// July - 7
                    #// Oct - 10
                    rebalance = (rebalance and 
                            ((self.Time.month == 1) or
                            (self.Time.month == 4) or
                            (self.Time.month == 7) or
                            (self.Time.month == 10)))
                            
                    if rebalance == True:
                        self.Debug("Rebalancing Quarterly on " + str(self.Time))
                
                elif (self.RebalancePeriod == "Daily"):
                    if self.Time.day != self.lastDay:
                        rebalance = True
                        self.lastDay = self.Time.day
                        self.Debug("Rebalancing Daily on " + str(self.Time))
                    else:
                        rebalance = False
                        
                elif (self.RebalancePeriod == "Weekly"):
                    if self.newWeek == True:
                        rebalance = True
                        self.newWeek = False
                        self.Debug("Rebalancing Weekly on " + str(self.Time))
                    else:
                        rebalance = False
                    
                elif (self.RebalancePeriod == "Bi-Monthly"):
                    if self.newBiWeek == True:
                        rebalance = True
                        self.newBiWeek = False
                        self.Debug("Rebalancing Bi Weekly on " + str(self.Time))
                    else:
                        rebalance = False
                
                elif(self.RebalancePeriod == "Monthly"):
                    if self.newMonth == True:
                        rebalance = True
                        self.newMonth = False
                        self.Debug("Rebalancing Monthly on " + str(self.Time))
                    else:
                        rebalance = False

            if rebalance:
                self.LastRotationTime = self.Time

                #// pick which one is best from growth and safety symbols
                #//var orderedObjScores = SymbolData.OrderByDescending(x => x.ObjectiveScore).ToList();
                rankings = self.GetEtfReplayRankings()
                #orderedObjScores = self.SymbolData.OrderBy(rankings[x.Symbol]).ToList()
                orderedObjScores = sorted(self.SymbolData, key = lambda x: rankings[x.Symbol], reverse = False)


                nextHoldings = []
                
                self.Debug("OrderedObjScores Count: " + str(len(orderedObjScores)))
                for i in range(0, self.Keeps, 1):
                    currentSymbolData = orderedObjScores[i]
                    lastClose = self.Securities[currentSymbolData.Symbol].Close
                    maFilter = lastClose > currentSymbolData.FilterSMA.Current.Value
                    if maFilter:
                        #// this meets our investment criteria
                        if self.Portfolio[self.SafetySymbol].Invested:
                            self.Liquidate(self.SafetySymbol)
                        nextHoldings.append(currentSymbolData.Symbol)
                    else:
                        if self.SafetySymbol not in nextHoldings:
                            nextHoldings.append(self.SafetySymbol)
                    
                    #// FIXME - allocate to "Safety" symbol, right now saftey symbol is part of the growth symbols so it should bubble up but that doesn't match replay
                    #else:
                        # if not self.Portfolio[self.SafetySymbol].Invested:
                        #     self.Liquidate()
                        #     self.SetHoldings(self.SafetySymbol, 1)
                        #     break
                        # if self.Portfolio[self.SafetySymbol].Invested:
                        #     break
                        
                        
                if self.Portfolio[self.SafetySymbol].Invested:
                    return

                self.Log(">>NextPositions<<")
                for position in nextHoldings:
                    self.Log("\t>>" + str(position))

                if len(nextHoldings) == 0:
                    #// goto 100% cash
                    self.Log("LIQUIDATE>>CASH")
                    self.Liquidate()

                else:
                    for kvp in self.Portfolio.Securities.Values:
                        #// liquidate anything we are currently invested in but not part of our next portfolio state
                        if kvp.Invested and not (kvp.Symbol in nextHoldings):
                            self.Log("LIQUIDATE>>" + str(kvp.Symbol))
                            self.Liquidate(kvp.Symbol)

                    allocationPercentage = (1.0 - self.CashReservePct) / self.Keeps
                    for symbol in nextHoldings:
                        self.Log("BUY>>" + str(symbol))
                        self.SetHoldings([PortfolioTarget(symbol, allocationPercentage)])
        except:
            print("Unexpected error:", sys.exc_info()[0])
            raise
            #Error("OnData: " + ex.Message + "\r\n\r\n" + ex.StackTrace);
        
class SymbolData:
    def __init__(self, symbol, ReturnA, ReturnB, Volatility, FilterSMA):
        self.Symbol = symbol
        self.ReturnA = ReturnA
        self.ReturnB = ReturnB
        self.Volatility = Volatility
        self.FilterSMA = FilterSMA