Overall Statistics
Total Trades
1
Average Win
0%
Average Loss
0%
Compounding Annual Return
8.739%
Drawdown
11.800%
Expectancy
0
Net Profit
20.763%
Sharpe Ratio
0.748
Probabilistic Sharpe Ratio
31.107%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0.036
Beta
0.183
Annual Standard Deviation
0.085
Annual Variance
0.007
Information Ratio
-0.451
Tracking Error
0.185
Treynor Ratio
0.346
Total Fees
$1.22
Estimated Strategy Capacity
$740000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
#region imports
from AlgorithmImports import *
#endregion
class TrailingStopLoss(QCAlgorithm):
    
    def Initialize(self):
        startingCash = 100000
        self.SetCash(startingCash)  # Set Strategy Cash
        
        self.SetStartDate(2020,1 , 1)
        self.SetEndDate(2022, 4, 1)
        
        symbol = "SPY"
        
        self.symbol = self.AddEquity(symbol, Resolution.Daily).Symbol
        self.tli = self.AddData(TLI, "tli", Resolution.Daily).Symbol
        self.tli1 = 0 #Variable to track tli1
        
        self.longEntryThreshhold = 0.15
        self.shortEntryThreshhold = -0.15
        self.longAllocation = 1 # 100% long
        self.shortAllocation = -1 # 100% short
        
        self.entryTicketLong = None
        self.stopMarketTicketLong = None
        
        self.entryTicketShort = None
        self.stopMarketTicketShort = None
        
        self.entryTime = datetime.min
        self.stopMarketOrderFillTime = datetime.min
        
        
        self.highestPrice = 0
        self.lowestPrice = 0
        self.str1 = "hello"


    def OnData(self, data):
        
              
        price = self.Securities[self.symbol].Price
        #self.Debug("Price : " + str(price))
        curDT = self.Time
        if self.tli in data:
            tlitime = data[self.tli].Time
            self.str1 = curDT.strftime("%m/%d/%Y, %H:%M:%S") + ", tliTime " + tlitime.strftime("%m/%d/%Y, %H:%M:%S") + ", tli val "+ str(data[self.tli].Value) + ", price" + str(price) 
            self.tli1 = data[self.tli].Value
            self.Debug(str(data[self.tli].Value))
            
        # Check for long entry 
        if not self.Portfolio.Invested:
            
            if self.tli1 >= self.longEntryThreshhold:
                quantity = self.CalculateOrderQuantity(self.symbol, 0.9)
                #self.entryTicketLong = self.LimitOrder(self.symbol, quantity, price, "Entry Order Long, TLI: " + str(data[self.tli].Value) + " TLI_time" +str(data[self.tli].Time))
                self.Debug("Entering long")
                self.entryTicketLong = self.MarketOrder(self.symbol, quantity)
                self.entryTime = self.Time    
                
                #By this time there entryTicketLong should be completed and stopMarketTicketLong should exist
                #self.str1 = "Order1, quantity:"+ str(quantity)+ " entryTicketLong status: " + str(self.entryTicketLong.Order) + ", stopMarketTicketLong status: "+ str(self.stopMarketTicketLong.Status)
                #self.Debug(self.str1)
        
                
                    
            if self.tli1 <= self.shortEntryThreshhold:
                quantity = self.CalculateOrderQuantity(self.symbol, 0.9)
                #self.entryTicketShort = self.LimitOrder(self.symbol, quantity, price, "Entry Order short, TLI: " + str(data[self.tli].Value) + " TLI_time" +str(data[self.tli].Time))
                self.Debug("Entering short")
                self.entryTicketShort = self.MarketOrder(self.symbol, -quantity)
                self.entryTime = self.Time
                
        



        #self.str1 = "tliTime " + tlitime.strftime("%m/%d/%Y, %H:%M:%S") + ", tli val "+ str(data[self.tli].Value) + ", price " + str(price) + ", Invested " +str(self.Portfolio.Invested) + ", openorders " + str(self.Transactions.GetOpenOrders(self.symbol))
        #self.str1 = curDT.strftime("%m/%d/%Y, %H:%M:%S") + ", price" + str(price) 
        #self.Debug(self.str1)
        
        
        
        # move long limit price if not filled after 1 day
        #if (self.Time - self.entryTime).days > 1 and (self.entryTicketLong is not None) and self.entryTicketLong.Status != OrderStatus.Filled:
        #    self.entryTime = self.Time
        #    updateFields = UpdateOrderFields()
        #    updateFields.LimitPrice = price
        #    self.entryTicketLong.Update(updateFields)
        
        # move Short limit price if not filled after 1 day
        #if (self.Time - self.entryTime).days > 1 and (self.entryTicketShort is not None) and self.entryTicketShort.Status != OrderStatus.Filled:
        #    self.entryTime = self.Time
        #    updateFields = UpdateOrderFields()
        #    updateFields.LimitPrice = price
        #    self.entryTicketShort.Update(updateFields)
        
        # move long stop limit
        if self.stopMarketTicketLong is not None and self.Portfolio.Invested:
            # move up trailing stop price
            if price > self.highestPrice:
                self.highestPrice = price
                updateFields = UpdateOrderFields()
                updateFields.StopPrice = price * 0.98
                self.stopMarketTicketLong.Update(updateFields)
                self.Debug(updateFields.StopPrice)
                
        # move short stop limit
        if self.stopMarketTicketShort is not None and self.Portfolio.Invested:
            # move down trailing stop price
            if price < self.lowestPrice:
                self.lowestPrice = price
                updateFields = UpdateOrderFields()
                updateFields.StopPrice = price * 1.02
                self.stopMarketTicketShort.Update(updateFields)
                self.Debug(updateFields.StopPrice)

    def CancelAllOrders(self):

        openOrders = self.Transactions.GetOpenOrders()
        if len(openOrders)> 0:
            for x in openOrders:
                self.Transactions.CancelOrder(x.Id)

    def OnOrderEvent(self, orderEvent):
        

        self.Debug(str(orderEvent.OrderId) + " " + str(orderEvent.Status))
        if (orderEvent.Status == OrderStatus.PartiallyFilled): # or orderEvent.Status == OrderStatus.Invalid):
            #self.str1 = "Order1, quantity:"+ str(quantity)+ " entryTicketLong status: " + str(self.entryTicketLong.Status) + ", stopMarketTicketLong status: "+ str(self.stopMarketTicketLong.Status)
            self.Debug("partiallyfilled order!!!")
        
            #what was the original order for

            #Cancel the rest of the order

            return
        
        # send long stop loss order if entry limit order is filled
        if self.entryTicketLong is not None:
            self.debug(str(entryTicketLong.OrderId))

        if self.entryTicketLong is not None and self.entryTicketLong.OrderId == orderEvent.OrderId and orderEvent.Status==OrderStatus.Filled:
            self.Debug("entering stopMarketTicketLong")
            self.stopMarketTicketLong = self.StopMarketOrder(self.symbol, -self.entryTicketLong.Quantity, 0.98 * self.entryTicketLong.AverageFillPrice)
            #self.str1 = "stopMarketTicketLong, quanity " + str(-self.entryTicketLong.Quantity) + ", price " +str(0.98 * self.entryTicketLong.AverageFillPrice)
            self.Debug(self.str1)
        
        # send Short stop loss order if entry limit order is filled
        if self.entryTicketShort is not None and self.entryTicketShort.OrderId == orderEvent.OrderId and orderEvent.Status==OrderStatus.Filled:
            self.Debug("entering stopMarketTicketShort")
            self.stopMarketTicketShort = self.StopMarketOrder(self.symbol, self.entryTicketShort.Quantity, 1.02 * self.entryTicketShort.AverageFillPrice)
            
            #self.Debug(self.symbol +", " + str(self.entryTicketShort.Quantity))
            # save fill time of Long stop loss order (and reset highestPrice lowestPrice)

        if (self.stopMarketTicketLong is not None) and (self.stopMarketTicketLong.OrderId == orderEvent.OrderId): 
            self.stopMarketOrderFillTime = self.Time
            self.highestPrice = 0
            self.lowestPrice = 0
            
        # save fill time of short stop loss order (and reset highestPrice lowestPrice)
        if (self.stopMarketTicketShort is not None) and (self.stopMarketTicketShort.OrderId == orderEvent.OrderId): 
            self.stopMarketOrderFillTime = self.Time
            self.highestPrice = 0
            self.lowestPrice = 0
            
            
            
class TLI(PythonData):

    def GetSource(self, config, date, isLive):
        
        
        source = "https://www.dropbox.com/s/q4njfg7ihs2cwb0/TLI_20220415.csv?dl=1"
        return SubscriptionDataSource(source, SubscriptionTransportMedium.RemoteFile);

    def Reader(self, config, line, date, isLive):
        if not (line.strip() and line[0].isdigit()):
            return None
        
        data = line.split(',')
        tli = TLI()
        
        try:
            tli.Symbol = config.Symbol
            # make data available Monday morning (Friday 16:00 + 66 hours) 
            # since we can't trade on weekend anyway
            tli.Time = datetime.strptime(data[0], '%Y-%m-%d %H:%M:%S') + timedelta(hours=66)
            
            tli.Value = data[1]
            
        except ValueError:
            return None
        
        return tli