Overall Statistics
Total Trades
20
Average Win
7.42%
Average Loss
-3.20%
Compounding Annual Return
24.207%
Drawdown
13.800%
Expectancy
1.369
Net Profit
31.622%
Sharpe Ratio
0.974
Probabilistic Sharpe Ratio
44.428%
Loss Rate
29%
Win Rate
71%
Profit-Loss Ratio
2.32
Alpha
0.032
Beta
1.23
Annual Standard Deviation
0.187
Annual Variance
0.035
Information Ratio
0.549
Tracking Error
0.11
Treynor Ratio
0.148
Total Fees
$34.47
Estimated Strategy Capacity
$45000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
#region imports
from AlgorithmImports import *
#endregion
class WellDressedYellowCat(QCAlgorithm):

    def Initialize(self):
        # parameters:
        startingCash = 100000
        self.trailingStop = 0.05
        self.takeProfit = 0.05
        self.longEntryThreshhold = 0.1
        self.shortEntryThreshhold = -0.1
        self.longAllocation = 1 # 100% long
        self.shortAllocation = -1 # 100% short
        symbol = "SPY"
        
        self.SetStartDate(2021, 1, 1)  # Set Start Date
        self.SetEndDate(2022, 4, 10)
        self.SetCash(startingCash)  # Set Strategy Cash
        self.symbol = self.AddEquity(symbol, Resolution.Minute).Symbol
        self.tli = self.AddData(TLI, "tli", Resolution.Minute).Symbol
        
        self.AddRiskManagement(TrailingStopRiskManagementModel(self.trailingStop))
        
        
        self.entryTicket = None
        self.stopMarketTicket = None
        self.entryTime = datetime.min
        self.stopMarketOrderFillTime = datetime.min
        self.highestPrice = 0
        self.LowestPrice = 0
        
        #setting a long fill time to ensure marketorder is filled - comment out as not needed in backtesting
        #self.Transactions.MarketOrderFillTimeout = timedelta(seconds=10)
        
        
        
        
        


    def OnData(self, data: Slice):
        
        #20220417 Todo: on resolution of indictor (default daily) check if new trigger has been hit 
        
        if not self.Portfolio.Invested:
            
            
            if self.tli in data:
                #TODO 20220416:  Note I would like the trailing stop to actually be the low of the last three bars for the buy
                # These are not the minute bars, these by default are the daily bars, but can be changed to some other resolution
                # The price*volume indicators are calculated on this resolution aswell
                # The TLI is the gate and then the trigger has to be hit to enter.  Once entered and the trade closes out. if the TLI gate is open
                # and a new trigger is hit in the up direction, then re-open.
                
                price = self.Securities[self.symbol].Price    
                quantity = self.CalculateOrderQuantity(self.symbol, 0.9)
                
                #I think I can get away with putting the limit orders straight behind the market order on the SPY or SPX
                #as they are so liquid
                if data[self.tli].Value > self.longEntryThreshhold:
                    self.entryTicket = self.MarketOrder(self.symbol, quantity)
                    self.Debug("Market Order Fill Price: {0}".format(price))
                    stopPrice = price * (1-self.trailingStop) # Abs stop loss
                    limitPrice = price *(1+self.takeProfit)  # Sell equal or better than takeporofit
                    stopLimitTicket = self.StopLimitOrder("SPY", quantity, stopPrice, limitPrice)
                    
                    
                elif data[self.tli].Value < self.shortEntryThreshhold:
                    self.entryTicket = self.MarketOrder(self.symbol,-1*quantity)
                    self.Debug("Market Sell Order Fill Price: {0}".format(price))
                    stopPrice = price * (1-self.takeProfit) # Trigger stop limit when price falls 1%.
                    limitPrice = price * (1+self.trailingStop) # Sell equal or better than 1% > close.
                    stopLimitTicket = self.StopLimitOrder("SPY", quantity, stopPrice, limitPrice)
                
    
    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

class TLI(PythonData):

    def GetSource(self, config, date, isLive):
        source = "https://www.dropbox.com/s/zlm00njnufrhnko/TLI.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