Slippage Research

Given the trading strategy's sensitivity to slippage, it is a good idea to research typical EUR.USD spreads at the time of day we intend to trade. To do this, we will collect a month's worth of 1-minute bid/ask data from IBKR. We use the BID_ASK bar type, which provides the time-average bid and ask for the bar's duration. (Alternatively, we could collect the BID and ASK separately, see the usage guide for available bar types.)

Data collection

First, create a database for the historical data:

In [1]:
from quantrocket.history import create_ibkr_db
create_ibkr_db("fiber-quotes-1min", 
              sids="FXEURUSD", 
              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.fiber-quotes-1min.sqlite'}

Then collect the data:

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

Monitor flightlog for the completion message:

quantrocket.history: INFO [fiber-quotes-1min] Collecting history from IBKR for 1 securities in fiber-quotes-1min
quantrocket.history: INFO [fiber-quotes-1min] Saved 27270 total records for 1 total securities to quantrocket.v2.history.fiber-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("fiber-quotes-1min", sids="FXEURUSD", fields=["Open","Close"])

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"]

# Squeeze single-column DataFrame to Series
bids = bids.squeeze()
asks = asks.squeeze()

Compute the spreads in basis points:

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

Get the average spread by time:

In [6]:
times = spreads_in_bps.index.get_level_values("Time")
spreads_in_bps_by_time = spreads_in_bps.groupby(times).mean()

Finally, check the spreads at the time of days we intend to trade:

In [7]:
spreads_in_bps_by_time.loc[["03:00:00","11:00:00","16:00:00"]]
Out[7]:
Time
03:00:00    0.000014
11:00:00    0.000012
16:00:00    0.000018
Name: FXEURUSD, dtype: float64
In [8]:
spreads_in_bps_by_time.loc[["03:00:00","11:00:00","16:00:00"]].mean()
Out[8]:
1.4756156893222112e-05

The average spread is about 0.15 basis points. Since the prices used in our backtest reflect the midpoint, we only expect to pay half the spread, or 0.075 basis points. Thus, our slippage estimate of 0.1 basis point seems reasonable.


Next Up

Part 7: Manual Trading