Overall Statistics
Total Orders
8
Average Win
0.15%
Average Loss
-0.40%
Compounding Annual Return
-5.833%
Drawdown
1.000%
Expectancy
-0.313
Start Equity
10000
End Equity
9949.63
Net Profit
-0.504%
Sharpe Ratio
-2.775
Sortino Ratio
-1.281
Probabilistic Sharpe Ratio
20.610%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
0.37
Alpha
-0.045
Beta
0.145
Annual Standard Deviation
0.035
Annual Variance
0.001
Information Ratio
2.524
Tracking Error
0.101
Treynor Ratio
-0.664
Total Fees
$8.00
Estimated Strategy Capacity
$50000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
25.12%
# region imports
from AlgorithmImports import *
# endregion

# Strategy for 30m timeframe
    # RSI 14 (threshold on 25)
    # BB  30, stdv 2
    # BBwidth = (upper - lower) / middle
    # ATR 14

    # Conditions:
    # Previous candle = Close < Lower BB
    # Previous candle = RSI < 25
    # Current candle is green and closes above high of previous candle
    # BBwidth > 0.15

    # Exit:
    # Timeout after 2 bars OR TP: 1.2atr SL: 1.6atr
    
class SeriousBBRSI(QCAlgorithm): 
    def Initialize(self):
        # Set the start and end dates for backtesting
        self.SetStartDate(2024, 4, 1) 
        self.SetEndDate(2024, 5, 1)

        # Set the initial cash for the algorithm
        self.SetCash(10000) 

        # Add the SPY asset with minute resolution
        self.security = self.AddEquity("SPY", Resolution.Minute) 

        # Consolidate the SPY data into 30-minute bars and call OnDataConsolidated method for each consolidated bar
        self.Consolidate("SPY", timedelta(minutes=30), self.OnDataConsolidated) 

        # Set the brokerage model to Interactive Brokers with a margin account
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) 

        # Create a rolling window of 2 TradeBars for security
        self.securityWin = RollingWindow[TradeBar](2)

        # Set SPY as the benchmark
        self.SetBenchmark("SPY") 

        # Create a 14-period RSI indicator with minute resolution for security
        self.rsi30 = self.RSI(self.security.Symbol, 14, Resolution.Minute) 
        
        # Create a 30-period 2 stdev BB indicator with minute resolution for security
        self.bb30 = self.bb(self.security.Symbol, 30, 2, Resolution.Minute)

        # Create a rolling window of 2 TradeBars for RSI
        self.rsiWin = RollingWindow[IndicatorDataPoint](2)

        # Create a rolling window of 2 TradeBars for BB
        self.bbLowerWin = RollingWindow[float](30)

    def OnDataConsolidated(self, consolidated):
        # Create plot of 30m Close price
        self.Plot("TradePlot", "close", consolidated.Close)

        # Add the Close price of the consolidated data to securityWin
        self.securityWin.Add(consolidated)

        # Add the rsi new consolidated bar to the rolling window
        self.rsiWin.Add(self.rsi30.Current)

        # Add the bb new consolidated bar to the rolling window
        # self.bbWin.Add(self.bb30)
        self.bbLowerWin.Add(self.bb30.LowerBand.Current.Value)

        # If the RSI and the rolling window are not ready, return
        if not (self.rsi30.IsReady and self.bb30.IsReady and self.securityWin.IsReady and self.rsiWin.IsReady and self.bbLowerWin.IsReady): 
            return 

        # Get the right data for conditions
        Close = self.securityWin[0].Close
        previousClose = self.securityWin[1].Close
        current_rsi = self.rsi30.Current.Value
        previous_rsi = self.rsiWin[1].Value
        previous_bb = self.bbLowerWin[1]

        # Plot the RSI value
        self.Plot("30minRSI", "rsi30", current_rsi) 

        # Plot the BB value
        self.Plot("TradePlot", "MiddleBand", self.bb30.MiddleBand.Current.Value)
        self.Plot("TradePlot", "UpperBand", self.bb30.UpperBand.Current.Value)
        self.Plot("TradePlot", "LowerBand", self.bb30.LowerBand.Current.Value)

        # If the SPY is in the portfolio
        if self.Portfolio[self.security.Symbol].Invested: 
            # If the RSI is above 50, liquidate the SPY
            if current_rsi > 50: 
                self.Liquidate(self.security.Symbol) 
                self.Debug("Sell") 
        # If the RSI is below 25 AND close is below the bblower, buy the SPY
        elif (previous_rsi < 25 and previousClose < previous_bb):
            self.SetHoldings(self.security.Symbol, 1.0) 
            self.Debug("Buy")