Slippage Research

The provided trading strategy code models one-way slippage of 3 basis points (see the SLIPPAGE_BPS parameter). This notebook investigates spreads with more precision, as they vary by leveraged ETF. To do so, we collect 1 month of bid/ask data for the leveraged ETFs and calculate the spreads at 2:15 PM (the time we enter positions).

Spread analysis uses data from Interactive Brokers.

Data collection

First, create a database for the historical data:

In [1]:
from quantrocket.history import create_ibkr_db
create_ibkr_db("leveraged-etf-quotes-1min", 
              universes="leveraged-etf", 
              bar_size="1 min", 
              bar_type="BID_ASK", 
              start_date="2019-01-01", 
              end_date="2019-01-31",
              shard="off")
Out[1]:
{'status': 'successfully created quantrocket.v2.history.leveraged-etf-quotes-1min.sqlite'}

Then collect the data:

In [2]:
from quantrocket.history import collect_history
collect_history("leveraged-etf-quotes-1min")
Out[2]:
{'status': 'the historical data will be collected asynchronously'}

Monitor flightlog for the completion message:

quantrocket.history: INFO [leveraged-etf-quotes-1min] Collecting history from IBKR for 14 securities in leveraged-etf-quotes-1min
quantrocket.history: INFO [leveraged-etf-quotes-1min] Saved 114660 total records for 14 total securities to quantrocket.v2.history.leveraged-etf-quotes-1min.sqlite

Spread analysis

Now we can load the bid/ask data and check the spreads.

In [3]:
from quantrocket import get_prices
prices = get_prices("leveraged-etf-quotes-1min", fields=["Open","Close"], times="14:15:00")

For this bar type, the "Open" contains the time-average bid and the "Close" contains the time-average ask.

In [4]:
bids = prices.loc["Open"]
asks = prices.loc["Close"]

Compute the spreads in basis points:

In [5]:
spreads = (asks - bids)
spreads_in_bps = (spreads/bids).astype(float)

Replace sids with symbols for easier readability:

In [6]:
from quantrocket.master import get_securities_reindexed_like
symbols = get_securities_reindexed_like(bids, fields="Symbol").loc["Symbol"]
symbols = symbols.iloc[0].to_dict()
spreads_in_bps = spreads_in_bps.rename(columns=symbols)

Finally, check the spreads at 2:15 PM:

In [7]:
spreads_in_bps.xs("14:15:00", level="Time").mean() * 1e4
Out[7]:
Sid
SPXL     3.140246
TNA      4.769774
ERX      5.654112
TMF      5.985715
UPRO     4.097632
DRN     70.678831
YINN     5.168800
UDOW     3.957265
URTY     9.336790
FAS      7.547277
EDC     10.734769
RUSL    22.694030
JNUG    10.018724
NUGT     5.738911
dtype: float64

The spreads range from 3 basis points to 70 basis points. Because the strategy enters during the trading session and exits on-the-close, we expect to pay half the spread on our entries but we do not expect to incur any slippage on our exits. Thus, we should average the spread of the securities we intend to trade, then divide by 2 since we only expect to pay half the spread, then divide by 2 again since we only expect to incur slippage on our entries, not our exits.