Overall Statistics
Total Orders
13
Average Win
0.03%
Average Loss
-1.13%
Compounding Annual Return
27.773%
Drawdown
6.300%
Expectancy
-0.488
Start Equity
1000000
End Equity
1277443.06
Net Profit
27.744%
Sharpe Ratio
1.477
Sortino Ratio
1.978
Probabilistic Sharpe Ratio
76.739%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
0.02
Alpha
0.07
Beta
0.506
Annual Standard Deviation
0.111
Annual Variance
0.012
Information Ratio
-0.195
Tracking Error
0.111
Treynor Ratio
0.324
Total Fees
$122.43
Estimated Strategy Capacity
$0
Lowest Capacity Asset
AMD R735QTJ8XC9X
Portfolio Turnover
0.42%
from AlgorithmImports import *

class MomentumRSIBBAlgorithm(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2019, 1, 1)
        self.SetEndDate(2020, 1, 1)
        self.SetCash(1000000)
        
        # Define stocks
        self.symbols = [self.AddEquity("AMD", Resolution.Daily).Symbol, 
                        self.AddEquity("JNJ", Resolution.Daily).Symbol]
        
        # Initialize indicators
        self.indicators = {
            symbol: {
                'rsi': self.RSI(symbol, 14),  ## 14-day lookback
                'bb': self.BB(symbol, 20, 2, MovingAverageType.Simple),  ## 2 standard deviations, 20-day period
                'sma': self.SMA(symbol, 50) ## 50-day SMA
            } for symbol in self.symbols
        }
        
        # Schedule daily rebalancing after market opens
        self.Schedule.On(self.DateRules.EveryDay(), 
                         self.TimeRules.AfterMarketOpen("AMD", 10),  ## when we specify "AMD" here, it's just used as a reference time. The rebalancing happens for both stocks. It's like saying "10 minutes after the market opens based on AMD's trading hours" 
                         self.Rebalance)

    def Rebalance(self):
        for symbol in self.symbols:
            if not all(indicator.IsReady for indicator in self.indicators[symbol].values()):
                continue  # Skip if indicators aren't ready
            
            # Get indicator values
            price = self.Securities[symbol].Price
            rsi = self.indicators[symbol]['rsi'].Current.Value
            upper_bb = self.indicators[symbol]['bb'].UpperBand.Current.Value
            lower_bb = self.indicators[symbol]['bb'].LowerBand.Current.Value
            sma = self.indicators[symbol]['sma'].Current.Value
            
            # Determine trend direction
            trend = "Up" if price > sma else "Down" 
            
            # trend reversing element:
            ## Long when price is below BB lower band, and RSI shows oversold (< 30)
            ## Short when price is above BB Upper band, and RSI shows overbought (> 70)


            # trend-following element with the SMA:
            ## Only goes long if price > SMA (uptrend) and only goes short if price < SMA (downtrend)

            # Trading logic
            if trend == "Up" and rsi < 30 and price < lower_bb:         ## indicate oversold if stock starts passing lower than 30 
                self.SetHoldings(symbol, 0.5)  # Buy signal: 50% long
            elif trend == "Down" and rsi > 70 and price > upper_bb:     ## indicate overbought if stock starts passing higher than 70
                self.SetHoldings(symbol, -0.25)  # Sell signal: 25% short

            # Ensure minimum 25% absolute weight per stock
            current_weight = abs(self.Portfolio[symbol].HoldingsValue / self.Portfolio.TotalPortfolioValue)
            if current_weight < 0.25:
                self.SetHoldings(symbol, 0.25 if trend == "Up" else -0.25)