Single Futures Roll


Introduction

Working with futures contracts has the unique problem that a given contract has an expiration date. To ensure the adequacy of the data, we need to account for the “switching” between the contracts that expire and the ones that take their place.

Simply performing an auto-roll and stitching the contracts together doesn’t account for the price difference between the old contract and the new one when the expiry date actually comes. Often this difference is quite small, however, for some contracts, it can be quite substantial (especially if the underlying asset has high carry costs).

Ignoring the price difference may lead to false spikes/pitfalls around the expiry data that, in their turn, may falsely signify a trading opportunity where actually there is none.

The answer to the problem is the “ETF trick” from the Advances in Financial Machine Learning by professor Marcos Lopez de Prado.

Procedure

“The ETF trick can handle the rolls of a single futures contract, as a particular case of a 1-legged spread. However, when dealing with a single futures contract, an equivalent and more direct approach is to form a time series of cumulative roll gaps, and detract that gaps series from the price series.”

Note

On further expirations the adjustment factor need to be accounted for as follows:

  • Absolute returns: adjustment_factor = previous_adjustment_factor + new_roll_gap.

  • Relative returns: adjustment_factor = previous_adjustment_factor * new_roll_gap.

To generate the correct continuous contract, a 3-step procedure needs to be performed:

  1. Get absolute and relative roll gaps.

  2. Filter out rows with price info of the nearest contract only.

  3. Apply the roll gaps on the copy of the first contract.

Implementation:

get_futures_roll_series(data_df: DataFrame, open_col: str, close_col: str, sec_col: str, current_sec_col: str, roll_backward: bool = False, method: str = 'absolute') Series

Function for generating rolling futures series from data frame of multiple futures.

Parameters:
  • data_df – (pd.DataFrame) Pandas DataFrame containing price info, security name and current active futures column.

  • open_col – (str) Open prices column name.

  • close_col – (str) Close prices column name.

  • sec_col – (str) Security name column name.

  • current_sec_col – (str) Current active security column name. When value in this column changes it means rolling.

  • roll_backward – (bool) True for subtracting final gap value from all values.

  • method – (str) What returns user wants to preserve, ‘absolute’ or ‘relative’.

Returns:

(pd.Series) Futures roll adjustment factor series.

Example

>>> import pandas as pd
>>> import matplotlib.pyplot as plt
>>> from mlfinlab.multi_product.etf_trick import get_futures_roll_series
>>> # Load data
>>> url = "https://raw.githubusercontent.com/hudson-and-thames/example-data/main/futures_stitched.csv"
>>> aggregated_df = pd.read_csv(url, index_col=0, parse_dates=[0])  
>>> len(aggregated_df)
450
>>> aggregated_df.columns  
Index(['close', 'open', 'high', 'low', 'volume', 'ticker', 'nearest_contract'],...
>>> # Get roll gaps (absolute and relative)
>>> roll_gaps_absolute = get_futures_roll_series(
...    aggregated_df,
...    open_col="open",
...    close_col="close",
...    sec_col="ticker",
...    current_sec_col="nearest_contract",
...    method="absolute",
... )
>>> roll_gaps_absolute  
date
2021-01-04 0.00
...
>>> roll_gaps_relative = get_futures_roll_series(
...    aggregated_df,
...    open_col="open",
...    close_col="close",
...    sec_col="ticker",
...    current_sec_col="nearest_contract",
...    method="relative",
... )
>>> roll_gaps_relative  
date
2021-01-04 1.00...
>>> # Filter out rows with price info of the nearest contract only
>>> # This forms the basis of the continuous contract
>>> continuous_contract = aggregated_df[
...    aggregated_df.ticker == aggregated_df.nearest_contract
... ]
>>> len(continuous_contract)
450
>>> continuous_contract.columns  
Index(['close', 'open', 'high', 'low', 'volume', 'ticker', 'nearest_contract'],...
>>> # Make a copy of the first contract
>>> continuous_contract_absolute_method = continuous_contract.copy()
>>> continuous_contract_relative_method = continuous_contract.copy()
>>> # Apply the roll gaps
>>> continuous_contract_absolute_method["close"] -= roll_gaps_absolute
>>> continuous_contract_relative_method["close"] /= roll_gaps_relative
>>> # Plot
>>> fig = plt.figure(figsize=(20, 10))
>>> _ = continuous_contract.close.plot(label="Autoroll Series")
>>> _ = continuous_contract_absolute_method.close.plot(label="Absolute Return Roll")
>>> _ = continuous_contract_relative_method.close.plot(label="Relative Return Roll")
>>> _ = plt.legend(loc="best")
>>> fig  
<Figure...>
futures_roll_4.png

Research Notebook

The following research notebooks can be used to better understand the Futures Roll.

  • Futures Roll Trick


References