Skip to content
All library documents

Intraday Short Strategy for Stocks Gapping Below Prior Lows

Article Strategy library · Author: QuantRocket

Summary

This intraday equity strategy selects candidates with a pipeline, then looks for stocks that open below the previous day’s low by more than a volatility measure and below their moving average. It ranks qualifying stocks by that measure and keeps up to ten. The strategy schedules the screen shortly after the open, places short market orders later in the morning, and closes positions shortly before the close.

The example also specifies a fixed dollar target per position, zero per-share commission, and fixed basis-point slippage. In live trading it collects real-time market data for candidate stocks. The document provides implementation details, not performance results or evidence that the signal is profitable. The pipeline’s candidate-selection calculations are referenced but not shown, and the volatility measure is not defined here. The fixed position value and market orders also leave borrow availability, fill quality, and portfolio-level risk controls to the surrounding system.

Key ideas

  • The strategy shorts stocks whose opening price falls sufficiently below the prior session’s low and remains below a moving average.
  • It ranks qualifying candidates by a volatility-related measure and limits the list to ten stocks.
  • Short entries are scheduled after the opening screen, with positions closed before the market close.
  • The example defines slippage and commission assumptions but does not report backtest results.

Tags

Full text
# sell-gap


# sell-gap









## Source (Apache-2.0)

```python
# Copyright QuantRocket LLC - All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import zipline.api as algo
from zipline.finance.execution import MarketOrder
from zipline.finance import slippage, commission
from quantrocket.realtime import collect_market_data
from codeload.sell_gap.pipeline import make_pipeline

def initialize(context: algo.Context):
    """
    Called once at the start of a backtest, and once per day at
    the start of live trading.
    """
    # Attach the pipeline to the algo
    algo.attach_pipeline(make_pipeline(), 'pipeline')

    # Set SPY as benchmark
    algo.set_benchmark(algo.symbol("SPY"))

    # identify down gaps immediately after the opening
    algo.schedule_function(
        find_down_gaps,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(minutes=1),
    )

    # at 9:40, short stocks that gapped down
    algo.schedule_function(
        short_down_gaps,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(minutes=10),
    )

    # close positions 5 minutes before the close
    algo.schedule_function(
        close_positions,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(minutes=5),
    )

    # Set commissions and slippage
    algo.set_commission(
        commission.PerShare(cost=0.0))
    algo.set_slippage(
        slippage.FixedBasisPointsSlippage(
            basis_points=3.0))

def before_trading_start(context: algo.Context, data: algo.BarData):
    """
    Called every day before market open. Gathers today's pipeline
    output and initiates real-time data collection (in live trading).
    """
    context.candidates = algo.pipeline_output('pipeline')
    context.assets_to_short = []
    context.target_value_per_position = -50e3

    # Start real-time data collection if we are in live trading
    if algo.get_environment("arena") == "trade":

        # start real-time tick data collection for our candidates...
        sids = [asset.real_sid for asset in context.candidates.index]

        if sids:

            # collect the trade/volume data
            collect_market_data(
                "us-stk-realtime",
                sids=sids,
                until="09:32:00 America/New_York")

            # ...and point Zipline to the derived aggregate db
            # For Interactive Brokers databases:
            algo.set_realtime_db(
                "us-stk-realtime-1min",
                fields={
                    "close": "LastPriceClose",
                    "open": "LastPriceOpen",
                    "high": "LastPriceHigh",
                    "low": "LastPriceLow",
                    "volume": "LastSizeSum"})
            # For Alpaca databases:
            # algo.set_realtime_db(
            #     "us-stk-realtime-1min",
            #     fields={
            #         "close": "MinuteCloseClose",
            #         "open": "MinuteOpenOpen",
            #         "high": "MinuteHighHigh",
            #         "low": "MinuteLowLow",
            #         "volume": "MinuteVolumeSum"})

def find_down_gaps(context: algo.Context, data: algo.BarData):
    """
    Identify stocks that gapped down below their moving average.
    """

    if len(context.candidates) == 0:
        return

    today_opens = data.current(context.candidates.index, 'open')
    prior_lows = context.candidates["prior_low"]
    stds = context.candidates["std"]

    # find stocks that opened sufficiently below the prior day's low...
    gapped_down = today_opens < (prior_lows - stds)

    # ...and are now below their moving averages
    are_below_mavg = (today_opens < context.candidates["mavg"])

    assets_to_short = context.candidates[
        gapped_down
        & are_below_mavg
        ]

    # Limit to the top 10 by std
    assets_to_short = assets_to_short.sort_values(
        "std", ascending=False).iloc[:10].index

    context.assets_to_short = assets_to_short

def short_down_gaps(context: algo.Context, data: algo.BarData):
    """
    Short the stocks that gapped down.
    """
    for asset in context.assets_to_short:

        # Sell with market order
        algo.order_value(
            asset,
            context.target_value_per_position,
            style=MarketOrder() # for IBKR, specify exchange (e.g. exchange="SMART")
        )

def close_positions(context: algo.Context, data: algo.BarData):
    """
    Closes all positions.
    """
    for asset, position in context.portfolio.positions.items():
        algo.order(
            asset,
            -position.amount,
            style=MarketOrder()
        )

```

Shown in full with attribution under the source's licence. Licence: Apache-2.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.