Overall Statistics |
Total Trades 8 Average Win 6.86% Average Loss -1.69% Compounding Annual Return 8.021% Drawdown 8.800% Expectancy 1.525 Net Profit 10.118% Sharpe Ratio 0.62 Probabilistic Sharpe Ratio 29.879% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 4.05 Alpha -0.024 Beta 0.636 Annual Standard Deviation 0.096 Annual Variance 0.009 Information Ratio -1.005 Tracking Error 0.071 Treynor Ratio 0.094 Total Fees $9.41 Estimated Strategy Capacity $1800000000.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(2021, 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.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 def OnData(self, data): if self.tli not in data: return # wait 30 days after last exit if (self.Time - self.stopMarketOrderFillTime).days < 2: return price = self.Securities[self.symbol].Price # send long entry limit order if not self.Portfolio.Invested and not self.Transactions.GetOpenOrders(self.symbol): if data[self.tli].Value > 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.entryTime = self.Time # send short entry limit order if data[self.tli].Value < 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.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.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.95 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.05 self.stopMarketTicketShort.Update(updateFields) #self.Debug(updateFields.StopPrice) def OnOrderEvent(self, orderEvent): if orderEvent.Status != OrderStatus.Filled: return # send long stop loss order if entry limit order is filled if self.entryTicketLong is not None and self.entryTicketLong.OrderId == orderEvent.OrderId: self.stopMarketTicketLong = self.StopMarketOrder(self.symbol, -self.entryTicketLong.Quantity, 0.95 * self.entryTicketLong.AverageFillPrice) # send Short stop loss order if entry limit order is filled if self.entryTicketShort is not None and self.entryTicketShort.OrderId == orderEvent.OrderId: self.stopMarketTicketShort = self.StopMarketOrder(self.symbol, self.entryTicketShort.Quantity, 1.05 * self.entryTicketShort.AverageFillPrice) # 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/zlm00njnufrhnko/TLI.csv?dl=1" 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