Overall Statistics
Total Trades
966
Average Win
0.94%
Average Loss
-0.22%
Compounding Annual Return
5.788%
Drawdown
17.400%
Expectancy
0.605
Net Profit
70.604%
Sharpe Ratio
0.502
Probabilistic Sharpe Ratio
4.120%
Loss Rate
69%
Win Rate
31%
Profit-Loss Ratio
4.23
Alpha
0.078
Beta
-0.177
Annual Standard Deviation
0.106
Annual Variance
0.011
Information Ratio
-0.439
Tracking Error
0.202
Treynor Ratio
-0.301
Total Fees
$977.92
Estimated Strategy Capacity
$410000.00
Lowest Capacity Asset
SJB UV40M49093Z9
import math
import numpy as np
from RiskManager import StopLimit


class DeterminedLightBrownAlpaca(QCAlgorithm):

    def Initialize(self):

        self.SetCash(10000)
        self.SetStartDate(2012, 1, 1)
        self.AddRiskManagement( StopLimit(self, UpPct = 0.15, DownPct = 1.0) )

        self.initials = {}
        self.baseline_set = False
        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 = [
            #"HYG", # corporate
            "IEF", # 7-10
            #"IGIB", # 5-10
            #"JNK",
            "LQD",
            "TBF",
            #"TBX",
            "TLT"
        ]

        self.Inverses = [
            "TYO",
            #"DFVS", # data issue
            #"JNK",
            "SJB",
            "TMV",
            #"TBX", # data issue
            "TTT"
        ]
        
        for i in self.Inverses:
            self.AddEquity(i)
        
        self.TargetDict = dict(zip(self.GrowthSymbols, self.Inverses))
        
        #TODO: not in love with this
        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)

        # 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.SetBenchmark('SPY')

        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)
                         
        self.Schedule.On(self.DateRules.EveryDay("SPY"),
                         self.TimeRules.AfterMarketOpen('SPY', 0), # plot performance from prev day
                         self.PlotStuff)

    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
            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:
                        # exit shy
                        self.Liquidate(self.SafetySymbol)
                        
                    # short inverse mapped to this ticker
                    k = str(currentSymbolData.Symbol).split(' ')[0]
                    v = self.TargetDict[k]
                    nextHoldings.append(v)
                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:
                # go to 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)])

    def PlotStuff(self):
        # normalize data for plotting
        if not self.baseline_set:
            self.initials['SPY'] = self.Securities['SPY'].Price
            if self.initials['SPY'] != 0:
                self.baseline_set = True
        if not self.baseline_set:
            return

        self.Plot("Performance", 'Portfolio', (self.Portfolio.TotalPortfolioValue - 10000) / 10000 * 100)
        self.Plot("Performance", 'SPY',
                  (self.Securities['SPY'].Price - self.initials['SPY']) / self.initials['SPY'] * 100)

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)
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTarget
from QuantConnect.Algorithm.Framework.Risk import RiskManagementModel

class StopLimit(RiskManagementModel):
    '''Provides an implementation of IRiskManagementModel that limits the drawdown per holding to the specified percentage'''

    def __init__(self, algo, UpPct = 0.05, DownPct = 0.05):
        '''Initializes a new instance of the MaximumDrawdownPercentPerSecurity class
        Args:
            maximumDrawdownPercent: The maximum percentage drawdown allowed for any single security holding'''
        self.maxDown = -abs(DownPct)
        self.maxUp = abs(UpPct)
        self.algo = algo

    def ManageRisk(self, algorithm, targets):
        '''Manages the algorithm's risk at each time step
        Args:
            algorithm: The algorithm instance
            targets: The current portfolio targets to be assessed for risk'''
        targets = []
        for kvp in algorithm.Securities:
            security = kvp.Value

            if not security.Invested:
                continue

            pnl = security.Holdings.UnrealizedProfitPercent
            if pnl < self.maxDown:
                # liquidate
                self.algo.Debug(f'Cut Loss {security.Symbol}. Loss: {self.algo.Portfolio[security.Symbol].UnrealizedProfitPercent*100:.2f}%')
                targets.append(PortfolioTarget(security.Symbol, 0))
            elif pnl > self.maxUp:
                # liquidate
                self.algo.Debug(f'Take Profit {security.Symbol}. Profit: {self.algo.Portfolio[security.Symbol].UnrealizedProfitPercent*100:.2f}%')
                targets.append(PortfolioTarget(security.Symbol, 0))

        return targets