Monthly Long-Only Stock Selection with a Borrow Fee Filter
Summary
This example builds a monthly rebalanced U.S. common-stock portfolio. Its pipeline screens for stocks in the middle portion of a 90-day average dollar-volume ranking and, when the fee limit is enabled, excludes names whose recorded borrow fee exceeds the configured ceiling. At rebalance, it exits holdings that leave the selected universe and allocates an equal fraction of portfolio value to each remaining stock using market orders.
The code illustrates how to combine liquidity and borrow-cost data in a pipeline universe, then translate that universe into portfolio orders. It supplies no backtest results or evidence that the filters improve returns. The portfolio is long-only despite using borrow-fee data, and the example does not describe transaction-cost, slippage, concentration, or failed-order handling; market availability and the borrow-fee feed may also constrain real use.
Key ideas
- A pipeline limits candidates to common stocks and a specified average dollar-volume percentile range.
- An optional maximum borrow-fee condition removes stocks with fees above the configured threshold.
- The strategy rebalances at the start of each month and exits holdings outside the current selection.
- Selected stocks receive equal target portfolio weights through market orders.
Tags
Full text
# avoid-high-borrow
# avoid-high-borrow
## 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, ibkr, master
from zipline.pipeline.factors import AverageDollarVolume
from zipline.finance.execution import MarketOrder
BUNDLE = "usstock-1d-bundle"
MAX_BORROW_FEE = 0.3
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')
# Rebalance monthly
algo.schedule_function(
rebalance,
algo.date_rules.month_start(),
)
def make_pipeline():
"""
Create a pipeline that filters by dollar volume and borrow fee.
"""
# limit initial universe to common stocks
universe = master.SecuritiesMaster.usstock_SecurityType2.latest.eq("Common Stock")
screen = AverageDollarVolume(window_length=90).percentile_between(50, 75)
if MAX_BORROW_FEE:
screen &= (ibkr.BorrowFees.FeeRate.latest <= MAX_BORROW_FEE)
pipeline = Pipeline(
initial_universe=universe,
screen=screen
)
return pipeline
def rebalance(context: algo.Context, data: algo.BarData):
"""
Execute orders according to our schedule_function() timing.
"""
desired_stocks = algo.pipeline_output('pipeline').index
positions = context.portfolio.positions
# Exit positions we no longer want to hold
for asset, position in positions.items():
if asset not in desired_stocks and data.can_trade(asset):
algo.order_target_value(asset, 0, style=MarketOrder())
# Enter long positions
for asset in desired_stocks:
# allocate 1/Nth of capital per asset
algo.order_target_percent(asset, 1/len(desired_stocks), 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.