Overall Statistics
Total Trades
31
Average Win
4.44%
Average Loss
-3.11%
Compounding Annual Return
11.779%
Drawdown
14.200%
Expectancy
0.618
Net Profit
39.692%
Sharpe Ratio
0.69
Sortino Ratio
0.556
Probabilistic Sharpe Ratio
38.631%
Loss Rate
33%
Win Rate
67%
Profit-Loss Ratio
1.43
Alpha
0.046
Beta
0.199
Annual Standard Deviation
0.095
Annual Variance
0.009
Information Ratio
-0.171
Tracking Error
0.175
Treynor Ratio
0.329
Total Fees
$79.47
Estimated Strategy Capacity
$73000000.00
Lowest Capacity Asset
QQQ RIWIV7K5Z9LX
Portfolio Turnover
2.54%
from AlgorithmImports import *

class TrailingStopLoss(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2018, 1, 1)
        self.SetEndDate(2021, 1, 1)
        self.SetCash(100000)
        self.qqq = self.AddEquity("QQQ", Resolution.Hour).Symbol
        
        self.entryTicket = None
        self.stopMarketTicket = None
        self.entryTime = datetime.min
        self.stopMarketOrderFillTime = datetime.min
        self.highestPrice = 0

    def OnData(self, data):
        
        # wait 30 days after last exit
        if (self.Time - self.stopMarketOrderFillTime).days < 30:
            return
        
        price = self.Securities[self.qqq].Price
        
        # send entry limit order
        if not self.Portfolio.Invested and not self.Transactions.GetOpenOrders(self.qqq):
            quantity = self.CalculateOrderQuantity(self.qqq, 0.9)
            self.entryTicket = self.LimitOrder(self.qqq, quantity, price, "Entry Order")
            self.entryTime = self.Time
        
        # move limit price if not filled after 1 day
        if (self.Time - self.entryTime).days > 1 and self.entryTicket.Status != OrderStatus.Filled:
            self.entryTime = self.Time
            updateFields = UpdateOrderFields()
            updateFields.LimitPrice = price
            self.entryTicket.Update(updateFields)
        
        if self.stopMarketTicket 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.stopMarketTicket.Update(updateFields)
                #self.Debug(updateFields.StopPrice)

    def OnOrderEvent(self, orderEvent):
        
        if orderEvent.Status != OrderStatus.Filled:
            return
        
        # send stop loss order if entry limit order is filled
        if self.entryTicket is not None and self.entryTicket.OrderId == orderEvent.OrderId:
            self.stopMarketTicket = self.StopMarketOrder(self.qqq, -self.entryTicket.Quantity, 0.95 * self.entryTicket.AverageFillPrice)
        
        # save fill time of stop loss order (and reset highestPrice)
        if self.stopMarketTicket is not None and self.stopMarketTicket.OrderId == orderEvent.OrderId: 
            self.stopMarketOrderFillTime = self.Time
            self.highestPrice = 0