Overall Statistics
Total Trades
1830
Average Win
0.17%
Average Loss
-0.18%
Compounding Annual Return
3.234%
Drawdown
12.100%
Expectancy
0.320
Net Profit
80.130%
Sharpe Ratio
0.622
Probabilistic Sharpe Ratio
3.579%
Loss Rate
32%
Win Rate
68%
Profit-Loss Ratio
0.93
Alpha
0.026
Beta
0.018
Annual Standard Deviation
0.044
Annual Variance
0.002
Information Ratio
-0.451
Tracking Error
0.173
Treynor Ratio
1.547
Total Fees
$30179.74
Estimated Strategy Capacity
$9300000.00
Lowest Capacity Asset
CLY UIB7ED20G0V9
import datetime
import math
import sys
import numpy as np
import statistics
from time import sleep


class DeterminedLightBrownAlpaca(QCAlgorithm):
        

    
    def Initialize(self):
        
        self.SetCash(1000000)
        self.SetStartDate(2003, 1, 1)
        #self.SetEndDate(2007, 1, 1)
        
        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 = ["AGZ","BIL","HYG","IEF","IEI","IGIB","IGLB","JNK","LQD","MBB","MINT","SHY","TBF","TBX","TIP","TLT"]
        self.SafetySymbol = self.AddEquity("SHY", Resolution.Daily).Symbol
        # List<string> GrowthSymbols = new List<String>{
        #     // safety symbol
        #     "SHY"
        # };
        self.rebalance = True
        self.LastRotationTime = self.Time
        

        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)
        
        for symbol in self.GrowthSymbols:
            self.Debug("adding symbol to universe: " + symbol);
            
            symbolObject = self.AddSecurity(SecurityType.Equity, symbol, Resolution.Daily).Symbol
            
            sData = SymbolData(self,symbolObject, periodADays, periodBDays, periodVolDays, self.FilterSMA)
            self.SymbolData.append(sData)

        #// 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 not multiplier :
            multiplier = 1
        else:
            multiplier = 21
        
        return multiplier * amount
    
    
        
    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);

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

            if self.rebalance:
                self.rebalance = False
                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]
                    #if currentSymbolData.Symbol not in data.Bars:continue
                    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)])
        
        
class SymbolData:
    def __init__(self,algo, symbol, periodADays, periodBDays, periodVolDays, FilterSMA):
        self.Symbol = symbol
        self.algo = algo
        self.ReturnA = self.algo.MOMP(symbol, periodADays, Resolution.Daily)
        self.ReturnB = self.algo.MOMP(symbol, periodBDays, Resolution.Daily)
        #self.Volatility = Volatility
        
        self.FilterSMA = SimpleMovingAverage(FilterSMA)
        consolidator = TradeBarConsolidator(CalendarType.Monthly)
        self.algo.RegisterIndicator(self.Symbol, self.FilterSMA, consolidator)
        
        std = StandardDeviation(periodVolDays)
        annualizationFactor = float(math.sqrt(252))
        logReturns = self.algo.LOGR(symbol, periodVolDays, Resolution.Daily)
            
        #CompositeIndicator<IndicatorDataPoint> volatility = std.Of(logReturns).Times(annualizationFactor);
        volatility_ = IndicatorExtensions.Of(std, logReturns)
        self.Volatility = IndicatorExtensions.Times(volatility_, annualizationFactor)