Daily Stock Momentum Selection with Intraday Entry Filtering
Summary
This equity strategy ranks stocks by their trailing 252-day returns, after screening for average dollar volume above $10 million over 30 days. Each day before the market opens, it selects the three highest-ranked stocks. At a scheduled rebalance 30 minutes before the close, it exits holdings that have fallen out of the selection and considers new positions among the current winners.
For each candidate, the strategy checks its intraday return and skips entry if the stock is up on the day. Otherwise it targets an allocation of one-sixth of portfolio capital using a market order. The source code describes the mechanics and sets SPY as a benchmark, but it includes no backtest results or evidence of returns. It also does not describe stop losses or other explicit risk controls; the example is a compact implementation, not evidence that the ranking and entry rules work across markets or periods.
Key ideas
- The pipeline screens stocks by recent average dollar volume and ranks the remainder by trailing returns.
- The daily portfolio selects the three stocks with the strongest trailing returns.
- New positions are considered near the close only when the stock is not up intraday.
- Each eligible new position targets one-sixth of portfolio capital through a market order.
- The code sets SPY as a benchmark but supplies no performance results or explicit stop-loss rules.
Tags
Full text
# winners
# winners
## Source (Apache-2.0)
```python
# Copyright 2024 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.pipeline import Pipeline
from zipline.pipeline.factors import AverageDollarVolume, Returns
from zipline.finance.execution import MarketOrder
MOMENTUM_WINDOW = 252
def initialize(context: algo.Context):
"""
Called once at the start of a backtest, and once per day in
live trading.
"""
# Attach the pipeline to the algo
algo.attach_pipeline(make_pipeline(), 'pipeline')
algo.set_benchmark(algo.symbol('SPY'))
# Rebalance every day, 30 minutes before market close.
algo.schedule_function(
rebalance,
algo.date_rules.every_day(),
algo.time_rules.market_close(minutes=30),
)
def make_pipeline():
"""
Create a pipeline that filters by dollar volume and
calculates return.
"""
pipeline = Pipeline(
columns={
"returns": Returns(window_length=MOMENTUM_WINDOW),
},
screen=AverageDollarVolume(window_length=30) > 10e6
)
return pipeline
def before_trading_start(context: algo.Context, data: algo.BarData):
"""
Called every day before market open.
"""
factors = algo.pipeline_output('pipeline')
# Get the top 3 stocks by return
returns = factors["returns"].sort_values(ascending=False)
context.winners = returns.index[:3]
def rebalance(context: algo.Context, data: algo.BarData):
"""
Execute orders according to our schedule_function() timing.
"""
# calculate intraday returns for our winners
current_prices = data.current(context.winners, "price")
prior_closes = data.history(context.winners, "close", 2, "1d").iloc[0]
intraday_returns = (current_prices - prior_closes) / prior_closes
positions = context.portfolio.positions
# Exit positions we no longer want to hold
for asset, position in positions.items():
if asset not in context.winners:
algo.order_target_value(asset, 0, style=MarketOrder())
# Enter long positions
for asset in context.winners:
# if already long, nothing to do
if asset in positions:
continue
# if the stock is up for the day, don't enter
if intraday_returns[asset] > 0:
continue
# otherwise, allocate 1/6th of capital per asset
algo.order_target_percent(asset, 1/6, 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.