Overall Statistics
Total Orders
518
Average Win
2.06%
Average Loss
-2.17%
Compounding Annual Return
7.433%
Drawdown
35.400%
Expectancy
0.346
Start Equity
10000
End Equity
62407.06
Net Profit
524.071%
Sharpe Ratio
0.326
Sortino Ratio
0.167
Probabilistic Sharpe Ratio
0.121%
Loss Rate
31%
Win Rate
69%
Profit-Loss Ratio
0.95
Alpha
0.018
Beta
0.239
Annual Standard Deviation
0.108
Annual Variance
0.012
Information Ratio
-0.198
Tracking Error
0.195
Treynor Ratio
0.147
Total Fees
$1239.88
Estimated Strategy Capacity
$99000000.00
Lowest Capacity Asset
QQQ RIWIV7K5Z9LX
Portfolio Turnover
5.54%
from AlgorithmImports import *

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.
    """

    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(1999, 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)
        
    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)
        

    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}")