Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
100000
End Equity
100000
Net Profit
0%
Sharpe Ratio
0
Sortino Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
-0.734
Tracking Error
0.151
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
Drawdown Recovery
0
from AlgorithmImports import *

class MonthlyLowRsiSelection(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2016, 1, 1)
        self.set_end_date(2025, 9, 7)
        self.set_cash(100000)
        self.universe_settings.resolution = Resolution.DAILY
        self.add_equity("SPY")  # To warm up the data
        self.spy = self.add_equity("SPY", Resolution.DAILY).symbol

        self.data = {}  # Store indicators
        self.selection_flag = False

        # Universe Selection for S&P 500 (simulated)
        self.add_universe(self.coarse_selection_function)
        
        self.last_month = -1

        self.rsi_period = 14
        self.sma_period = 14

        self.selected_symbols = []

        self.set_warm_up(self.rsi_period + 1)
        self.selection_flag = False
        self.changes = None

        self.schedule.on(
            self.date_rules.month_start("SPY"),
            self.time_rules.after_market_open("SPY", 30),
            self.monthly_rebalance
        )
        
        # No commissions
        self.set_brokerage_model(BrokerageName.DEFAULT, AccountType.MARGIN)
        self.settings.free_portfolio_value_percentage = 0  # Use all capital

    def coarse_selection_function(self, coarse):
        selected = [x.symbol for x in coarse if x.has_fundamental_data and x.price > 5 and x.dollar_volume > 1e7]
        return selected

    def universe_selection(self, symbols):
        for symbol in symbols:
            if symbol not in self.data:
                rsi = self.rsi(symbol, self.rsi_period, MovingAverageType.WILDERS, Resolution.DAILY)
                sma = self.sma(symbol, self.sma_period, Resolution.DAILY)
                self.data[symbol] = {'rsi': rsi, 'sma': sma}
        return symbols

    def on_securities_changed(self, changes):
        self.changes = changes
        for security in changes.removed_securities:
            symbol = security.symbol
            if symbol in self.data:
                self.data.pop(symbol)

    def monthly_rebalance(self):
        if self.is_warming_up:
            return

        # Filter to symbols with indicators ready
        filtered = [sym for sym in self.data.keys() 
                    if self.data[sym]['rsi'].is_ready and self.data[sym]['sma'].is_ready]
        
        # Compute RSI sorting lowest to highest
        stocks = []
        for sym in filtered:
            price = self.securities[sym].price
            sma = self.data[sym]['sma'].current.value
            rsi = self.data[sym]['rsi'].current.value
            # Filter for price above SMA
            if price > sma:
                stocks.append((sym, rsi))
        stocks.sort(key=lambda x: x[1])  # Sort by RSI

        # Pick the 10 lowest RSI stocks
        selected = [x[0] for x in stocks[:10]]
        self.selected_symbols = selected

        # Liquidate everything first
        self.liquidate()

        # Evenly allocate to 10 positions
        count = len(self.selected_symbols)
        if count == 0:
            return

        weight = 1.0 / count
        for symbol in self.selected_symbols:
            if self.securities[symbol].is_tradable:
                self.set_holdings(symbol, weight)

    def on_data(self, data):
        # No per-bar trading, all handled in scheduled monthly_rebalance
        pass