Overall Statistics
from AlgorithmImports import *
class GapUpAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        # 1) Basic settings
        self.set_start_date(2025, 5, 25)
        self.set_end_date(2025, 6, 8)
        self.set_cash(100000)
        # Store dictionaries for tracking
        self._open_prices: Dict[Symbol, float] = {}
        self._entry_prices: Dict[Symbol, float] = {}
        self._watchlist: List[Symbol] = []
        # Set brokerage / resolution / timezone / etc.
        self.set_brokerage_model(BrokerageName.DEFAULT, AccountType.MARGIN)
        self.universe_settings.resolution = Resolution.DAILY
        self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW
        self.set_time_zone(TimeZones.NEW_YORK)
        # Add a daily SPY to anchor our scheduling rules
        self.add_equity("SPY", Resolution.DAILY)
        # Add Universe with both coarse and fine selection functions
        self.add_universe(self.coarse_selection_function, self.fine_selection_function)
        # Schedule the morning scan at 0 minutes after market open (can adjust to 10 seconds if preferred)
        self.schedule.on(
            self.date_rules.every_day("SPY"),
            self.time_rules.after_market_open("SPY", 0),
            self.morning_scan
        )
        # Schedule liquidation before market close (1 minute before close)
        self.schedule.on(
            self.date_rules.every_day("SPY"),
            self.time_rules.before_market_close("SPY", 1),
            self.end_of_day_liquidation
        )
        # We want second-resolution updates for the watchlist
        self.set_security_initializer(lambda s: s.set_data_normalization_mode(DataNormalizationMode.RAW))
    def coarse_selection_function(self, coarse: List[CoarseFundamental]) -> List[Symbol]:
        # Filter US equities priced >= 0.95
        return [
            c.symbol for c in coarse
            if c.has_fundamental_data and c.price >= 0.95 and c.symbol.id.market == Market.USA
        ]
    def fine_selection_function(self, fine: List[FineFundamental]) -> List[Symbol]:
        # For this strategy, just return all symbols passed from coarse
        return [f.symbol for f in fine]
    def morning_scan(self) -> None:
        # 2) Morning scan: compute gap for all active securities vs previous close
        if len(self.active_securities) == 0:
            self.debug("No active securities in universe for morning scan.")
            return
        symbols = [sec.symbol for sec in self.active_securities.values]
        self.debug(f"Morning scan with {len(symbols)} symbols")
        if not symbols:
            return
        # Get last 2 daily bars for each symbol to compute gap
        history = self.history(symbols, 2, Resolution.DAILY)
        if history.empty:
            self.debug("No historical data returned.")
            return
        gap_info = []
        # Handle multi-index data from pandas
        unique_syms = set(history.index.get_level_values(0))
        for sym in symbols:
            if sym not in unique_syms:
                continue
            sym_hist = history.xs(sym, level=0)
            if len(sym_hist) < 2:
                continue
            # Last two daily bars
            prev_bar = sym_hist.iloc[-2]
            curr_bar = sym_hist.iloc[-1]
            prev_close = float(prev_bar["close"])
            today_open = float(curr_bar["open"])
            if prev_close <= 0:
                continue
            gap_percent = (today_open - prev_close) / prev_close
            # Only consider if open >= $0.95
            if today_open >= 0.95:
                gap_info.append((sym, gap_percent, today_open))
        # Sort descending by gap
        gap_info.sort(key=lambda x: x[1], reverse=True)
        # Select top 100
        top_gap = gap_info[:10]
        # Store watchlist and open prices
        self._watchlist = [x[0] for x in top_gap]
        self._open_prices = {x[0]: x[2] for x in top_gap}
        self.debug(f"Selected top gap up stocks: {len(self._watchlist)}")
        # Subscribe for second data
        for symbol, _, _ in top_gap:
            self.add_equity(symbol.value, Resolution.SECOND)
    def on_data(self, slice: Slice) -> None:
        # 3) Per-second monitoring
        if not self._watchlist:
            return
        for symbol in self._watchlist:
            bar = slice.bars[symbol] if symbol in slice.bars else None
            if bar is None:
                continue
            current_price = bar.close
            open_price = self._open_prices.get(symbol, 0.0)
            if open_price <= 0:
                continue
            invested = self.portfolio[symbol].invested
            # If not invested, check if current price >= 1.4 * open
            if not invested and current_price >= 1.4 * open_price:
                quantity = self.calculate_order_quantity(symbol, 0.01)
                if quantity != 0:
                    self.market_order(symbol, quantity)
                    self._entry_prices[symbol] = current_price
                    self.debug(f"BUY {symbol} at {current_price}")
            # If invested, check for 30% up or down from entry
            if invested:
                entry_price = self._entry_prices.get(symbol, 0.0)
                if entry_price > 0:
                    # Up 30%?
                    if current_price >= 1.3 * entry_price:
                        self.liquidate(symbol)
                        self.debug(f"TAKE PROFIT {symbol} at {current_price}")
                    # Down 30%?
                    elif current_price <= 0.7 * entry_price:
                        self.liquidate(symbol)
                        self.debug(f"STOP LOSS {symbol} at {current_price}")
    def end_of_day_liquidation(self) -> None:
        # 5) Liquidate positions and reset daily state
        self.debug("EndOfDayLiquidation: Liquidating all positions.")
        self.liquidate()
        self._watchlist.clear()
        self._open_prices.clear()
        self._entry_prices.clear()
    def on_securities_changed(self, changes: SecurityChanges) -> None:
        # 7) Event triggered when securities are added/removed
        pass