Daily Long-Short Stock Selection by RSI Rank
Summary
This Zipline example builds a daily long-short equity portfolio from the three assets with the highest RSI and the three with the lowest RSI. It assigns each selected long a target weight of one third and each short a target weight of negative one third, then closes positions that no longer qualify when they can still be traded. A pipeline supplies the ranked RSI groups before the scheduled daily rebalance, and the example sets per-share commission and volume-based slippage assumptions.
The code’s comments describe expected behavior in an automated sample run, including roughly 2.0 gross leverage and a temporary short-count change when a company is delisted. These are implementation checks rather than evidence of profitability. The example provides no RSI lookback discussion, risk limits, or performance analysis, and its own comments note that closing a delisted holding can fail. Equal long and short allocations imply that the portfolio is dollar-neutral, although the test note describes net leverage as roughly 2.0.
Key ideas
- The pipeline ranks assets by RSI and selects the top three and bottom three each day.
- The strategy targets equal-sized long and short positions, each weighted at one third of portfolio value.
- It closes holdings that leave the selected groups when those assets remain tradable.
- The example configures commission and volume-based slippage but does not report investment performance.
- Delisted assets can cause position-closing logic to fail.
Tags
Full text
# momentum_pipeline.py
```py
"""
A simple Pipeline algorithm that longs the top 3 stocks by RSI and shorts
the bottom 3 each day.
"""
from zipline.api import (
attach_pipeline,
date_rules,
order_target_percent,
pipeline_output,
record,
schedule_function,
)
from zipline.finance import commission, slippage
from zipline.pipeline import Pipeline
from zipline.pipeline.factors import RSI
def make_pipeline():
rsi = RSI()
return Pipeline(
columns={
"longs": rsi.top(3),
"shorts": rsi.bottom(3),
},
)
def rebalance(context, data):
# Pipeline data will be a dataframe with boolean columns named 'longs' and
# 'shorts'.
pipeline_data = context.pipeline_data
all_assets = pipeline_data.index
longs = all_assets[pipeline_data.longs]
shorts = all_assets[pipeline_data.shorts]
record(universe_size=len(all_assets))
# Build a 2x-leveraged, equal-weight, long-short portfolio.
one_third = 1.0 / 3.0
for asset in longs:
order_target_percent(asset, one_third)
for asset in shorts:
order_target_percent(asset, -one_third)
# Remove any assets that should no longer be in our portfolio.
portfolio_assets = longs.union(shorts)
positions = context.portfolio.positions
for asset in positions.keys() - set(portfolio_assets):
# This will fail if the asset was removed from our portfolio because it
# was delisted.
if data.can_trade(asset):
order_target_percent(asset, 0)
def initialize(context):
attach_pipeline(make_pipeline(), "my_pipeline")
# Rebalance each day. In daily mode, this is equivalent to putting
# `rebalance` in our handle_data, but in minute mode, it's equivalent to
# running at the start of the day each day.
schedule_function(rebalance, date_rules.every_day())
# Explicitly set the commission/slippage to the "old" value until we can
# rebuild example data.
# github.com/quantopian/zipline/blob/master/tests/resources/
# rebuild_example_data#L105
context.set_commission(commission.PerShare(cost=0.0075, min_trade_cost=1.0))
context.set_slippage(slippage.VolumeShareSlippage())
def before_trading_start(context, data):
context.pipeline_data = pipeline_output("my_pipeline")
def _test_args():
"""
Extra arguments to use when zipline's automated tests run this example.
Notes for testers:
Gross leverage should be roughly 2.0 on every day except the first.
Net leverage should be roughly 2.0 on every day except the first.
Longs Count should always be 3 after the first day.
Shorts Count should be 3 after the first day, except on 2013-10-30, when it
dips to 2 for a day because DELL is delisted.
"""
import pandas as pd
return {
# We run through october of 2013 because DELL is in the test data and
# it went private on 2013-10-29.
"start": pd.Timestamp("2013-10-07"),
"end": pd.Timestamp("2013-11-30"),
"capital_base": 100000,
}
```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.