Overall Statistics
Total Orders
1688
Average Win
0.55%
Average Loss
-0.59%
Compounding Annual Return
1.827%
Drawdown
10.200%
Expectancy
0.096
Start Equity
100000
End Equity
156393.35
Net Profit
56.393%
Sharpe Ratio
-0.215
Sortino Ratio
-0.128
Probabilistic Sharpe Ratio
0.003%
Loss Rate
43%
Win Rate
57%
Profit-Loss Ratio
0.93
Alpha
-0.012
Beta
0.081
Annual Standard Deviation
0.04
Annual Variance
0.002
Information Ratio
-0.336
Tracking Error
0.151
Treynor Ratio
-0.106
Total Fees
$8532.84
Estimated Strategy Capacity
$130000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
18.66%
from AlgorithmImports import *

class SPYBreakoutStrategy(QCAlgorithm):
    
    ## Initialize the algorithm, set up data feeds, and schedule functions.
    def Initialize(self):
    
        self.SetStartDate(2000, 1, 1)  # Set start date
        self.SetCash(100000)  # Set initial capital

        # Add SPY data
        self.spy = self.AddEquity("SPY", Resolution.Hour).Symbol

        # Create a RollingWindow to store the last 2 daily bars
        self.dailyBars = RollingWindow[TradeBar](2)

        # Schedule the daily check function
        self.Schedule.On(self.DateRules.EveryDay(self.spy),
                         self.TimeRules.AfterMarketOpen(self.spy, 2),
                         self.DailyCheck)

        # Initialize flags and variables
        self.checkForEntry = False
        self.previousDayHigh = 0

        # Schedule the function to exit positions at the beginning or end of the day
        self.Schedule.On(self.DateRules.EveryDay(self.spy),
                        #  self.TimeRules.BeforeMarketClose(self.spy, 1),
                         self.TimeRules.AfterMarketOpen(self.spy, 1),
                         self.ExitPositions)

    ## Event handler called for each new data point.
    def OnData(self, data):
    
        if not self.dailyBars.IsReady \
           or (self.spy not in data) \
           or (data[self.spy] is None):
            return

        if self.checkForEntry and not self.Portfolio.Invested:
            if data[self.spy].Close > self.previousDayHigh:
                self.SetHoldings(self.spy, 1)
                self.Debug(f"Entered long position in SPY at {data[self.spy].Close}")


    ## Get yesterday's candle
    def GetYesterdaysCandle(self):
       
        history = self.History(self.spy, 1, Resolution.Daily)

        if history.empty or 'close' not in history.columns:
            return None

        for index, row in history.loc[self.spy].iterrows():            
            tradeBar        = TradeBar()
            tradeBar.Close  = row['close']
            tradeBar.Open   = row['open']
            tradeBar.High   = row['high']
            tradeBar.Low    = row['low']
            tradeBar.Volume = row['volume']
            tradeBar.Time   = index
            tradeBar.Period = timedelta(1)
            
        return tradeBar

    ## Perform daily check for entry conditions.
    def DailyCheck(self):
       
        lastBar = self.GetYesterdaysCandle()
        if lastBar is None: 
            return
        self.dailyBars.Add(lastBar)

        if not self.dailyBars.IsReady: 
            return

        yesterday = self.dailyBars[0]
        previousDay = self.dailyBars[1]

        if yesterday.Low < previousDay.Low and yesterday.High < previousDay.High:
            self.checkForEntry = True
            self.previousDayHigh = yesterday.High
            self.Debug(f"Set checkForEntry to True. Previous day's high: {self.previousDayHigh}")
        else:
            self.checkForEntry = False

    ## Exit all positions - Called at the end or begining of the trading day.
    def ExitPositions(self):
    
        if self.Portfolio.Invested:
            self.Liquidate(self.spy)
            self.Debug("Exited all positions at end of day")