Insights
June 22, 2022
About the author

Deyan Ulevinov

Deyan graduated from Imperial College to join a top-tier systematic hedge fund in London, where he spent 5 years building a state-of-the-art platform to simulate and execute quantitative trading ideas. He subsequently joined a Cambridge spin-off PolyAI backed by Point72 and Khosla Ventures where he built infrastructure for machine learning data collection.

Visualising position build-up in FOMC forward IRS

Introduction

Last week the FED announced its most aggressive interest rate hike since 1994 alongside the BOE going ahead with its fifth consecutive hike as inflation soars globally. Expectedly, the repercussions in global markets have been significant with Treasury yields spiking to levels not seen since the financial crisis and global stocks seeing their worst slide since 2020.

Central bank dated OIS swaps whose intrinsic value is attached to the outcome of central bank meetings are a popular way for market participants to express their view on monetary policy changes. Such instruments are traded over the counter (OTC) and with the disappearance of FRAs (and IBOR) are becoming an increasingly more popular tool for market participants to implement their views on short term rates. Historically, there has been little to no hard data on how much forward IRS is traded in terms of number of transactions or DV01 and at what price.

In this notebook we demonstrate how data available through Enterprai's API can be used to simulate how risk in the IRS market has been accumulating for each central bank meeting over time. The results quickly uncover that last week's FOMC has been the 3rd most heavily traded central bank outcome since 2013. See the full animation of how positions are built up and unwound below.

Set-up

In today's instalment of Enterprai Data Series we will be joining two unique time series related to FOMC dated OIS swaps to animate the build up and unwinding of positions for each FOMC meeting in the period from 2014 to 2023.

Our objective is to paint the big picture in this market and enable users to explore the development of longer term patterns. For this purpose we use daily data, however hourly and minutely is also available through the API and can be used to observe the same phenomena intraday.

If you're interested to explore the full menu of data sets we have made available via our groundbreaking work in the OTC space please request access to our data catalogue by emailing workstation@enterprai.com.

Let's get started.

Defining some constants and utilities

First, we define our imports as well as a few constants that would be useful throughout the rest of this notebook.


import pandas as pd
import plotly.express as px
import requests
from datetime import datetime
from ratelimit import limits, sleep_and_retry
from tqdm import tqdm
from typing import Dict


NOW = datetime.utcnow()
CURRENCY = 'USD'

BASE_URL = 'https://api.enterprai.com'
API_KEY = '[YOUR API KEY]'

The API by default imposes some restrictions on the number of requests per minute that can come from a single API key. In order to respect those limits we create a wrapper utility that will be used in subsequent sections to make requests to the API.


ONE_MINUTE = 60
RATE_LIMIT = 100

@sleep_and_retry
@limits(calls=RATE_LIMIT, period=ONE_MINUTE)
def request(url: str, params: Dict[str, str]) -> pd.DataFrame:
    headers = {
        'Accept': 'application/json',
        'Authorization': API_KEY,
    }

    response = requests.get(url, headers=headers, params=params)
    if response.status_code != 200:
        raise Exception(f'Request failed with code {response.status_code}: {response.json()["message"]}')

    # successful requests have their payload in a property called "data"
    return pd.DataFrame.from_dict(response.json()['data'])

Data Download

Having our constants and utilities defined we're ready to pull some data.

As we will find out in a moment all endpoints that will be queried throughout this notebook expect a cb_meeting_date query parameter. To spare the hassle of typing out such dates manually we have exposed a utility endpoint in the API that returns a list of central bank meeting dates over a given period, which in this experiment is set to 2014-2023.


url = f'{BASE_URL}/v1/utilities/calendars/mpm'
params = {
    'currency': CURRENCY,
    'start_date': '2014-01-01',
    'end_date': '2023-01-01',
}

response = request(url=url, params=params)
fomc_dates = response['date'].tolist()

Open Interest

Open interest is a statistic that has historically been associated with the futures market as before Enterprai it was unthinkable to obtain such information for OTC instruments for two main reasons:

  1. no transactional data has been available prior to the implementation of the reporting rules mandated by Dodd-Frank Title VII;
  2. the transactional data that was made available had been stripped off valuable information such as whether a given transaction represents a position opening or closing.

Enterprai is the first to have found a way to identify unwind transactions which puts us in a unique position to provide the most accurate estimation for open interest by adjusting for the volume that has been unwound.

Now that we have a list of FOMC dates we are ready to query the open interest time series for each meeting. We'll make those queries in a tight loop - one API call for each meeting.


url = f'{BASE_URL}/v1/rates/irs/open-interest/cb'
params = {
    'currency': CURRENCY,
    'frequency': '1d',
    # define the start and end date of the requeted timeseries
    'start_datetime': '2013-01-01T00:00:00Z',
    'end_datetime': f'{NOW.strftime("%Y-%m-%d")}T00:00:00Z',
}

dfs = []
for meeting_date in tqdm(fomc_dates):
    params['cb_meeting_date'] = meeting_date
    # central bank swaps stop trading after the meeting date
    # so we don't request open interest after this date
    if datetime.strptime(meeting_date, '%Y-%m-%d') < NOW:
        params['end_datetime'] = f'{meeting_date}T00:00:00Z'

    df_tmp = request(url=url, params=params)

    # API returns localised timestamps, here we strip the timezone info
    df_tmp['timestamp'] = pd.to_datetime(df_tmp['timestamp']).dt.tz_localize(None)
    df_tmp = df_tmp.set_index('timestamp')
    # we namespace each output series using the meeting date
    df_tmp = df_tmp.rename(columns={'value': meeting_date})
    dfs.append(df_tmp)

df_oi = pd.concat(dfs, axis=1)

Volume Unwound

As mentioned earlier open interest is calculated by subtracting the unwound volume from the total. So in order to get a more complete picture of the swap activity around a given central bank meeting we would need to make an additional query for the time series of volume unwound.

Luckily, this is very easy to do using the API. The code is almost identical to the open interest snippet - only changes we have to make are to the url and params variables.


url = f'{BASE_URL}/v1/rates/irs/traded-volume/cb'
params = {
    'currency': CURRENCY,
    'start_datetime': '2013-01-01T00:00:00Z',
    'end_datetime': f'{NOW.strftime("%Y-%m-%d")}T00:00:00Z',
    'frequency': '1d',
    'analytic': 'dv01_sum',
    'position_type': 'unwind',
}

dfs = []
for meeting_date in tqdm(fomc_dates):
    params['cb_meeting_date'] = meeting_date
    if datetime.strptime(meeting_date, '%Y-%m-%d') < NOW:
        params['end_datetime'] = f'{meeting_date}T00:00:00Z'

    df_tmp = request(url=url, params=params)
    df_tmp['timestamp'] = pd.to_datetime(df_tmp['timestamp']).dt.tz_localize(None)
    df_tmp = df_tmp.set_index('timestamp')
    df_tmp = df_tmp.rename(columns={'value': meeting_date})
    dfs.append(df_tmp)

df_unwind = pd.concat(dfs, axis=1)

Data Post-Processing & Visualisation

In this experiment we use the Plotly library due to its ease of use for creating animations.

The library expects inputs in a particular format which is different to how our open interest and unwind data frames are currently laid out. So in the next couple segments of code we massage the data into the desired format.

At the moment our data contains NaNs for days with no transactions. We populate the missing values by forward filling in the case of open interest and filling with zeroes in the case of volume unwound. Additionally, our data is on daily frequency which a bit too granular for the purposes of this experiment so we resample both data frames to weekly or monthly frequency to speed up the animation.


# you can change this to 'W' or '2W'
RESAMPLE = 'M'

df_oi_ = df_oi.ffill().fillna(0.)
df_oi_ = df_oi_.resample(RESAMPLE).last()

df_unwind_ = df_unwind.fillna(0.).cumsum()
df_unwind_ = df_unwind_.resample(RESAMPLE).last()

Next, we reshape the data frames through the stack operation and merge them into one using concat. After we set meaningful names to each column so the data is clearly labelled.


df_oi_ = df_oi_.stack().rename('Volume Outstanding')
df_unwind_ = df_unwind_.stack().rename('Volume Unwound')
df_tmp = pd.concat([df_oi_, df_unwind_], axis=1).stack()

df_tmp = df_tmp.rename('DV01 ($)')
df_tmp.index = df_tmp.index.set_names(['As of', 'FOMC Meeting', 'Analytic'])
df_tmp = df_tmp.reset_index()

df_tmp = df_tmp.loc[df_tmp['As of'] > '2018-01-01']
# plotly doesn't support datetimes in the "animation_frame" column
df_tmp['As of'] = df_tmp['As of'].dt.strftime('%Y-%m-%d')

Finally, we pass the data to Plotly for visualisation and adjust the styling of the plot.


fig = px.bar(
    df_tmp, 
    x='FOMC Meeting', 
    y='DV01 ($)', 
    color='Analytic', 
    animation_frame='As of', 
    template='plotly_white', 
    title='Position build-up in FOMC forward IRS, per meeting in the period 2014-2023')

# improve styling
fig.update_layout(legend=dict(
    yanchor='top',
    y=0.99,
    xanchor='left',
    x=0.01
))
fig.update_layout(sliders=[dict(
    transition=dict(
        duration=100, 
        easing='cubic-in-out'
    ),
    currentvalue=dict(
        font={'size': 20},
        prefix='As of: ',
        xanchor='right',
    ),
)])

fig.show()

Discussion

In just a few lines of code we were able to leverage Enterprai's API to showcase previously opaque market dynamics in one of the most interesting OTC instruments within the current market environment of big monetary policy changes and rising inflation - FOMC forward OIS swaps.

If you have any comments about this work or other questions you would like us to answer using our proprietary data - please leave a comment below or email me directly at deyan@enterprai.com.

Share this post

Subscribe for similar updates

Latest Posts