Overall Statistics
Total Orders
518
Average Win
2.06%
Average Loss
-2.17%
Compounding Annual Return
7.143%
Drawdown
35.400%
Expectancy
0.346
Start Equity
10000
End Equity
62407.06
Net Profit
524.071%
Sharpe Ratio
0.311
Sortino Ratio
0.156
Probabilistic Sharpe Ratio
0.080%
Loss Rate
31%
Win Rate
69%
Profit-Loss Ratio
0.95
Alpha
0.016
Beta
0.239
Annual Standard Deviation
0.106
Annual Variance
0.011
Information Ratio
-0.193
Tracking Error
0.191
Treynor Ratio
0.138
Total Fees
$1239.88
Estimated Strategy Capacity
$99000000.00
Lowest Capacity Asset
QQQ RIWIV7K5Z9LX
Portfolio Turnover
5.33%
#region imports
from AlgorithmImports import *
#endregion

class SimpleSpyClimber(QCAlgorithm):

    """
    Inspired by quantitativo : https://www.quantitativo.com/p/turnaround-tuesdays-on-steroids

    Entry rules:
    -----------------------
    - Today is Tuesday
    - Yesterday's close (Monday) was lower than Friday's
    - Friday's close was lower than Thursday's;
    - Go long at the opening.

    Exit rules
    -----------------------
    - Exit the trade when the close is higher than yesterday's high.
    """

    # Entry Point for the algo.     
    # ===========================
    def Initialize(self):

        ## Init backtest params, etc
        self.ticker = "QQQ"             # Ticker symbol to trade
        self.SetBenchmark(self.ticker)  # Benchmark for reporting (buy and hold)
        self.SetStartDate(1998, 1, 1)   # Backtest start date
        self.SetEndDate(2024, 7, 9)     # Backtest end date
        self.SetCash(10000)             # Starting portfolio balance

        ## Subscrbe to an hourly data feed (hour bars)
        self.symbol = self.AddEquity(self.ticker, Resolution.Hour).symbol

        ## Set up a daily bar consolidator
        self.dailyConsolidator = TradeBarConsolidator(timedelta(days=1))
        self.dailyConsolidator.DataConsolidated += lambda _, dailyBar: self.barsWindow.add(dailyBar)
        self.SubscriptionManager.AddConsolidator(self.symbol, self.dailyConsolidator)

        ## Set up a rollingwindow to store recent bars
        self.barsWindow = RollingWindow[TradeBar](3)

        ## Schedule a weekly chron job to run on tuesday after the first hour of trading
        self.Schedule.On(self.DateRules.Every(DayOfWeek.Tuesday), 
                            self.TimeRules.AfterMarketOpen(self.ticker, 60), \
                            self.CheckForEntry)

        
        # self.barsWindow.add(dailyBar)
        ## Register our technical indicators w/the ticker so they are updated automatically
        # self.MA_66   = self.SMA(self.ticker, 66, Resolution.Daily)  # track moving average of past 66 days
        # self.LOW_3   = self.MIN(self.ticker,  3, Resolution.Daily)  # track min price of past 3 days
        # self.HIGH_19 = self.MAX(self.ticker, 19, Resolution.Daily)  # track max price of past 19 days        
        
        

    # def DailyBarHandler(self, sender: object, dailyBar: TradeBar):
    #     self.barsWindow.add(dailyBar)
        
    def CheckForEntry(self):
        """
        - Yesterday's close (Monday) was lower than Friday's
        - Friday's close was lower than Thursday's;
        - Go long at the opening.
        """
        if self.barsWindow.IsReady:
            if not self.Portfolio.Invested:
                monBar, friBar, thursBar = self.barsWindow[0],self.barsWindow[1],self.barsWindow[2] 
                if( monBar.close < friBar.close < thursBar.close ):
                    self.SetHoldings(self.ticker, 1)
        

    # A handler that's called every time there is a new bar 
    # (ie candlestick) of data. The 'dataSlice' holds all the data.
    # ==============================================================
    def OnData(self, dataSlice):

        ## Make sure we have data for this ticker before we check for our entry conditions
        if( dataSlice.ContainsKey(self.ticker)) and (dataSlice[self.ticker] is not None ):
            
            ## The price that the last bar closed at    
            closePrice = dataSlice[self.ticker].Close

            if self.Portfolio.Invested:
                # Exit the trade when the close is higher than yesterday's high.
                if (self.barsWindow[0].close > self.barsWindow[1].high):
                    self.Liquidate(tag=f"Exit @ last close > prev high: {self.barsWindow[0].close} > {self.barsWindow[1].high}")

            # Exit the trade when the close is higher than yesterday's high.


            # ## If we're not hodling any positions, check for a signal                 
            # if not self.Portfolio.Invested:
                
            #     ## If closing price > 66 day Moving average && is the lowest in past 3 days, take a position.
            #     if(closePrice > self.MA_66.Current.Value) and (closePrice < self.LOW_3.Current.Value):
            #         self.SetHoldings(self.ticker, 1) # allocate 100% of portfolio to ticker

            # else:
            #     # If we're holding, and the day's close price is the highest of the last 19 days, then exit.
            #     if(closePrice > self.HIGH_19.Current.Value):
            #         self.Liquidate()