Overall Statistics
Total Orders
1
Average Win
0%
Average Loss
0%
Compounding Annual Return
11.991%
Drawdown
30.200%
Expectancy
0
Start Equity
100000
End Equity
504093.25
Net Profit
404.093%
Sharpe Ratio
0.577
Sortino Ratio
0.589
Probabilistic Sharpe Ratio
6.614%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
-0.004
Beta
0.911
Annual Standard Deviation
0.131
Annual Variance
0.017
Information Ratio
-0.772
Tracking Error
0.015
Treynor Ratio
0.083
Total Fees
$4.35
Estimated Strategy Capacity
$84000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
0.02%
from AlgorithmImports import *

class MultiTimeframeTrendStrategy(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2010, 1, 1)
        # self.SetEndDate(2015, 1, 1)
        self.SetCash(100_000)

        spy = self.AddEquity("SPY", Resolution.Hour)
        spy.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.spySymbol = spy.Symbol

        self.SetBenchmark("SPY")
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)

        # Initialize opening and previous bar prices for multiple time frames
        self.weeklyOpenPrice = None
        self.dailyOpenPrice  = None
        self.hourlyOpenPrice = None
        self.lastDailyHigh   = None
        self.lastDailyLow    = None
        self.lastHourlyHigh  = None
        self.lastHourlyLow   = None

        self.lastHour = self.Time.hour

        # Schedule functions to update weekly, daily, and hourly opening prices
        self.lastTradeWeek = -1  # Initialize to an invalid week number
        self.Schedule.On(self.DateRules.EveryDay(self.spySymbol), self.TimeRules.AfterMarketOpen(self.spySymbol, 1), self.CheckIfNewWeek)
        self.Schedule.On(self.DateRules.EveryDay(self.spySymbol), self.TimeRules.At(9, 30), self.DailyOpen)


    def OnData(self, data):
        # Ensure the algorithm isn't warming up and that SPY data is available.
        if self.IsWarmingUp or self.spySymbol not in data or data[self.spySymbol] is None:
            return

        # Update hourly price data and track high/low for Inside Bar detection.
        if self.Time.hour != self.lastHour:
            if data.Bars.ContainsKey(self.spySymbol):
                self.lastHour = self.Time.hour
                self.insideBarDetected = False  # Reset inside bar detection for the new hour

                bar = data.Bars[self.spySymbol]
                self.hourlyOpenPrice = bar.Open
                self.lastHourlyHigh  = bar.High
                self.lastHourlyLow   = bar.Low

        # Detect an Inside Bar based on the last hourly high and low.
        if self.lastHourlyHigh is not None and self.lastHourlyLow is not None:
            if self.hourlyOpenPrice < self.lastHourlyHigh and self.hourlyOpenPrice > self.lastHourlyLow:
                self.Debug("Detected an Inside Bar at the hourly time frame")
                self.insideBarDetected = True

        # self.Log(f' IsWarmingUp {self.IsWarmingUp}, insideBar {self.insideBarDetected}, lastHour {self.lastHour}, hourly Price: {self.hourlyOpenPrice:,.2f}, HourlyHigh {self.lastHourlyHigh:,.2f}, HourlyLow {self.lastHourlyLow:,.2f}, weeklyPrice {self.weeklyOpenPrice:,.2f}, dailyPrice {self.dailyOpenPrice:,.2f}')

        # Ensure all necessary prices are set before proceeding.
        if None in [self.weeklyOpenPrice, self.dailyOpenPrice, self.hourlyOpenPrice]:
            return

        # if 0.00 in [self.weeklyOpenPrice, self.dailyOpenPrice, self.hourlyOpenPrice]:
        #     return

        # Current price from the data.
        price = data[self.spySymbol].Price

        # Execute trades based on Inside Bar breakout and trend continuity.
        if self.insideBarDetected:

            spyInvested = self.Portfolio[self.spySymbol].Invested

            # Entry condition for a long position when current price breaks above the Inside Bar's high
            self.Log(f'price {price:,.2f}, HourlyHigh {self.lastHourlyHigh:,.2f}, HourlyLow {self.lastHourlyLow:,.2f}, weeklyPrice {self.weeklyOpenPrice:,.2f}, dailyPrice {self.dailyOpenPrice:,.2f}')
            if price >= self.lastHourlyHigh and price > self.weeklyOpenPrice and price > self.dailyOpenPrice and price > self.hourlyOpenPrice:
            # if price > self.lastHourlyLow and price > self.weeklyOpenPrice and price > self.dailyOpenPrice and price > self.hourlyOpenPrice:
                if not spyInvested:
                    self.Log("Inside Bar broken upwards, aligning with TFC for a long position")
                    self.SetHoldings(self.spySymbol, 1)

            # Exit condition if the price breaks below the Inside Bar's low
            elif spyInvested and price < self.lastHourlyLow:
                self.Log("Inside Bar broken downwards, exiting position.")
                self.Liquidate(self.spySymbol)


    def CheckIfNewWeek(self):
        currentWeek = self.Time.isocalendar()[1]
        if currentWeek != self.lastTradeWeek:
            self.lastTradeWeek = currentWeek
            self.WeeklyOpen()

    def WeeklyOpen(self):
        self.weeklyOpenPrice = self.Securities[self.spySymbol].Price
        self.Log(f"Weekly open price updated on {self.Time.strftime('%Y-%m-%d')}: {self.weeklyOpenPrice}")

    def DailyOpen(self):
        bar = self.Securities[self.spySymbol]
        self.dailyOpenPrice = bar.Price
        self.lastDailyHigh  = bar.High
        self.lastDailyLow   = bar.Low
        self.Log(f"Daily open price updated on {self.Time.strftime('%Y-%m-%d')}: {self.dailyOpenPrice}")