Overall Statistics
Total Orders
518
Average Win
2.04%
Average Loss
-1.99%
Compounding Annual Return
8.008%
Drawdown
37.100%
Expectancy
0.403
Start Equity
10000
End Equity
71523.70
Net Profit
615.237%
Sharpe Ratio
0.362
Sortino Ratio
0.188
Probabilistic Sharpe Ratio
0.236%
Loss Rate
31%
Win Rate
69%
Profit-Loss Ratio
1.03
Alpha
0.022
Beta
0.236
Annual Standard Deviation
0.108
Annual Variance
0.012
Information Ratio
-0.177
Tracking Error
0.195
Treynor Ratio
0.165
Total Fees
$1247.31
Estimated Strategy Capacity
$23000000.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.
    """

    ## Initialize 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(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.Minute).symbol

        # Set up a rollingwindow to store consolidated daily bars
        self.dailyBars = RollingWindow[TradeBar](3)

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

        # Schedule a weekly chron job (Tuesdays) to check for entries at the open
        self.Schedule.On(self.DateRules.Every(DayOfWeek.Tuesday), 
                            self.TimeRules.AfterMarketOpen(self.ticker, 5),
                            self.CheckForEntry)

        # Schedule a daily chron job to check for exits at the open
        self.Schedule.On(self.DateRules.EveryDay(), \
                            self.TimeRules.AfterMarketOpen(self.ticker, 5), 
                            self.CheckForExit)

    ## Go long when:    
    ##   - Today is Tuesday
    ##   - Yesterday's close (Monday) was lower than Friday's
    ##   - Friday's close was lower than Thursday's
    ## ------------------------------
    def CheckForEntry(self):
        if self.dailyBars.IsReady:
            if not self.Portfolio.Invested:
                monBar, friBar, thursBar = self.dailyBars[0],self.dailyBars[1],self.dailyBars[2] 
                if( monBar.close < friBar.close < thursBar.close ):
                    self.SetHoldings(self.ticker, 1)

    ## Exit the trade when the last daily close is higher than the previous day's high.
    ## -------------------------------------------------------------------------------
    def CheckForExit(self):
        if self.Portfolio.Invested:
            if (self.dailyBars[0].close > self.dailyBars[1].high):
                self.Liquidate(tag=f"Exit @ last close > prev high: {self.dailyBars[0].close} > {self.dailyBars[1].high}")