Overall Statistics
Total Trades
94
Average Win
1.22%
Average Loss
-0.80%
Compounding Annual Return
14.536%
Drawdown
4.500%
Expectancy
0.826
Net Profit
35.747%
Sharpe Ratio
1.53
Probabilistic Sharpe Ratio
81.161%
Loss Rate
28%
Win Rate
72%
Profit-Loss Ratio
1.52
Alpha
0.094
Beta
0.043
Annual Standard Deviation
0.066
Annual Variance
0.004
Information Ratio
-0.222
Tracking Error
0.209
Treynor Ratio
2.322
Total Fees
$119.34
Estimated Strategy Capacity
$1100000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
#region imports
from AlgorithmImports import *
#endregion
class TrailingStopLoss(QCAlgorithm):
    
    def Initialize(self):

        self.Transactions.MarketOrderFillTimeout = timedelta(seconds=30)

        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.1
        self.shortEntryThreshhold = -0.1
        self.longAllocation = 0.9 # 100% long
        self.shortAllocation = -0.9 # 100% short
        
        self.entryTicketLong = None
        self.stopMarketTicketLong = None
        self.stopMarketLongPrice = 0
        self.stopMarketShortPrice = 0

        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

        openOrders = self.Transactions.GetOpenOrders(self.symbol)
        if(openOrders):
           self.Debug("There are open orders?")


        # move long stop limit
        if self.entryTicketLong is not None and self.Portfolio.Invested:
            # move up trailing stop price
            if price > self.highestPrice:
                self.highestPrice = price
                self.stopMarketLongPrice = 0.98 * price
                self.Debug("Adjusting stopMarketTicktLong")
            elif price < self.stopMarketLongPrice:
                self.Liquidate(self.symbol)

                
        # move short stop limit
        if self.entryTicketShort is not None and self.Portfolio.Invested:
            # move down trailing stop price
            if price < self.lowestPrice:
                self.lowestPrice = price
                self.stopMarketShortPrice = price * 1.02
            elif price > self.stopMarketShortPrice:
                self.Liquidate(self.symbol)

                
        #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:
                self.Debug("Entering long")
                #str1 = "Entry Order Long, TLI: " + str(data[self.tli].Value) + " TLI_time" + str(data[self.tli].Time)
                self.GoLong(self.symbol, 0.9) #(self, symbol, ratio)
                self.entryTime = self.Time    
                    
            if self.tli1 <= self.shortEntryThreshhold:
                self.Debug("Entering short")
                self.GoShort(self.symbol, 0.9) #(self, symbol, ratio)
                self.entryTime = self.Time
        
        # 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.Portfolio.Invested:
            
            
           
    def CancelAllOrders(self):

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

   
    def GoLong(self, symbol, ratio):

        # Make sure we can trade this
        security = self.Securities[symbol]
        if not security.IsTradable:
            self.Debug("{} is not tradable.".format(symbol))
            return

        # Setup vars
        orderQuantity = self.CalculateOrderQuantity(symbol, ratio)
        limit = 1.001 # +0.1% Limit Order above current price
        limitPrice = round( security.Price * limit, 2 )
        openOrders = self.Transactions.GetOpenOrders(symbol)

        self.Transactions.CancelOpenOrders(symbol)
        self.Log('Going long, canceled all open orders, did a Market Order')
        self.SetHoldings(symbol, ratio)
        self.entryTicketLong = 1
        self.stopMarketLongPrice = 0.98 * security.Price

        #if(openOrders):
        # Cancel all outstanding orders, then do a Market Order
        #    self.Transactions.CancelOpenOrders(symbol)
        #    self.Log('Going long, canceled all open orders, did a Market Order')
        #    self.SetHoldings(symbol, ratio)
     
        #else:
        # No open orders, try a limit order then
        #    self.LimitOrder(symbol, orderQuantity, limitPrice)
        #    self.Log( str(security.Price) )
        #    self.Log('Going long. Placed a limit order at ' + str(limitPrice))
        #    self.Log('Going long, did a Market Order')
        #    self.SetHoldings(symbol, ratio)

    def GoShort(self, symbol, ratio):

        # Make sure we can trade this
        security = self.Securities[symbol]
        if not security.IsTradable:
            self.Debug("{} is not tradable.".format(symbol))
            return

        # Setup vars
        orderQuantity = self.CalculateOrderQuantity(symbol, ratio)
        limit = 0.999 # +0.1% Limit Order above current price
        limitPrice = round( security.Price * limit, 2 )
        openOrders = self.Transactions.GetOpenOrders(symbol)

        self.Transactions.CancelOpenOrders(symbol)
        self.SetHoldings(symbol, -ratio)
        self.entryTicketShort = 1
        self.stopMarketShortPrice = 1.02 * security.Price

        #if(openOrders):
        # Cancel all outstanding orders, then do a Market Order
        #    self.Transactions.CancelOpenOrders(symbol)
        #    self.SetHoldings(symbol, -ratio)
        #    self.Log('Going short, canceled all open orders, did a Market Order')

        #else:
        # No open orders, try a limit order then
        #    self.LimitOrder(symbol, -orderQuantity, limitPrice)
        #    self.Log( str(security.Price) )
        #    self.Log('Going short. Placed a limit order at ' + str(limitPrice))
        #    self.Log('Going short, did a Market Order')
        #    self.SetHoldings(symbol, -ratio)

    
            
            
            
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