Swaps

Primer on the swap instrument.

Introduction

The purpose of this primer is to show how to create and work with interest rate swaps.

A notebook containing all the code used in this page can be accessed in the research environment: Example notebooks.

Environment

Setting up your environment takes three steps:

  • Import the relevant internal and external libraries

  • Configure the environment parameters

  • Initialise the environment

import sigtech.framework as sig
from sigtech.framework.instruments.ir_otc import IRSwapMarket

import datetime as dtm
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

sns.set(rc={'figure.figsize':(18,6)})
env = sig.init()

Interest Rate Swap Instrument

The instantiation of an interest rate swap is shown below, where a standard 6M EURIBOR IRS 30/360 semi annual a receiver (receives fixed, pays floating) is created.

irs_std = sig.InterestRateSwap(
    currency='EUR',
    tenor='5Y',
    trade_date=dtm.date(2017, 1, 4),
    start_date=dtm.date(2017, 6, 20),
    value_post_start=True
    )

From this swap, various historic details are calculated. These are plotted below.

irs_std.floating_frequency
irs_std.carry_roll_down(dtm.date(2017, 6, 20),
                        [dtm.date(2018, 6, 20), dtm.date(2018, 9, 20)])
irs_std.swap_details(dtm.date(2018, 4, 18), notional=250000000)

Below a 3M ERIBOR receiver (receives fixed, pays floating) - to include the notional amount use swap_details method) is created.

irs_3m = sig.InterestRateSwap(
    currency='EUR',
    tenor='10Y',
    trade_date=dtm.date(2017, 1, 4),
    start_date=dtm.date(2017, 6, 20),
    value_post_start=True,
    overrides={'forecast_curve':'EUR.F3M CURVE', 
               'fixes_index':'EUR003M INDEX',  # what fixing to get
               'float_frequency': '3M', # This field determines the index tenor
              }
    )
irs_3m.floating_frequency

Curves

The swaps make use of historic forecasting and discounting curves.

Discounting methodology - LIBOR replacement

Discounting is performed from standard curves such us USD.D, EUR.D and GBP.D. For USD discounting, the curve is constructed from EFFR (Effective Fed Fund Rate) up to 18th Oct 2020. After this date it is being replaced by SOFR (Secured Overnight Financing Rate) from 19th Oct 2020. Similarly, for EUR.D curve, EONIA (Euro OverNight Index Average) is the reference rate up to 26th Jul 2020 and then replaced by ESTR (Euro short-term rate) from 27th Jul 2020.

discounting_curve = sig.obj.get(IRSwapMarket.discounting_curve_name('USD'))

dt = pd.Timestamp(2019, 12, 30)
discounting_curve_history = discounting_curve.history()
discounting_curve_data = discounting_curve_history.loc[dt]
discounting_curve_df = pd.Series(discounting_curve_data)

discounting_curve_data.plot(
    title=discounting_curve.name + ' Discount Factors as of ' + str(dt)
);
discounting_curve.discount_factor(dtm.date(2020, 2, 3), dtm.date(2020, 3, 3))
forecasting_curve = sig.obj.get(IRSwapMarket.forecasting_curve_name('USD'))

dt = pd.Timestamp(2019, 12, 30)
forecasting_curve_history = forecasting_curve.history()
forecasting_curve_data = forecasting_curve_history.loc[dt]

forecasting_curve_df = pd.Series(
    index=forecasting_curve_data['Dates'],
    data=forecasting_curve_data['DiscountFactors']
)

forecasting_curve_df.plot(title=forecasting_curve.name + ' Discount Factors');

Below is the forecasting curve as a table:

pd.DataFrame(forecasting_curve_data).head()

Below is an example of how to retrieve the raw values of the projection curve.

raw_data = env.data_service.projection_curve(
    'USD.F3M CURVE', 
    fields=['LastPrice'], 
    data_point='EOD'
)
raw_values = pd.Series(raw_data.iloc[8].values[0])
raw_values

The performance of a swaps over time can be retrieved via the history() method. The relevant time series data fields for a swap are the following:

irs_std.history_fields

Below the rate as a time series is generated:

rate = pd.concat({
    '6M Euribor Swap Rate': irs_std.history('Rate'),
    '3M Euribor Swap Rate': irs_3m.history('Rate')}, axis=1)
rate.iloc[0]

Below the net present value as a time series is generated:

npv = pd.concat({
    '6M Euribor Swap NPV': irs_std.history('LastPrice'),
    '3M Euribor Swap NPV': irs_3m.history('LastPrice')}, axis=1)
npv.iloc[0]  # value per unit

Below is an example of how to create a 5s10s steepener.

irs_std_10y = sig.InterestRateSwap(
    currency='EUR',
    tenor='10Y',
    trade_date=dtm.date(2017, 1, 4),
    start_date=dtm.date(2017, 6, 20),
    value_post_start=True
)
s5s10s = pd.concat({
    '5Y PV': irs_std.history(), 
    '10Y PV': irs_std_10y.history(),
    'PV01 5Y': irs_std.history('PV01'), 
    'PV01 10Y': irs_std_10y.history('PV01')}, axis=1)
s5s10s[['5Y PV','10Y PV']].plot(figsize=(17, 5))
s5s10s[['PV01 5Y','PV01 10Y']].plot(figsize=(17, 2))

Below is an example of how to visualise the basis between different tenors.

rate = pd.concat({
    'EUR6M Rate': irs_std_10y.history('Rate'),
    'EUR3M Rate': irs_3m.history('Rate')}, axis=1)
rate['spread'] = rate['EUR6M Rate'] - rate['EUR3M Rate']
rate.iloc[:, :2].plot(figsize=(17, 5))
rate.iloc[:, -1:].plot(figsize=(17, 2))

Shifting Valuation Date

The quant library allows the rate spread to be determined between two dates and keeping the curve fixed.

Given a forecasted curve fcst_curve and a discounted curve disc_curve

pds = pd.Series(fcst_curve.history().loc[dt].get('DiscountFactors')).plot(
    title='Forecasting Curve Discount Factors');
disc_curve.history().loc[dt].plot(title='Discounting Curve Discount Factors');

Historic Swap Rates

We can use the curve history to evaluate the swap rates for various tenors throughout the history.

We can retrieve a single tenor historical time series and forward starting swaps fair rates.

print(sig.obj.get('USD.D CURVE').get_swap_rate('5Y'))
print(sig.InterestRateSwapGroup.fair_swap_rate(currency='USD',
                                     tenor='3Y',
                                     fwd_tenor='2Y',
                                     start_date=dtm.date(2010,2,3)
                                    ))

We have the option to change the interpolation method as shown below.

dts = forecasting_curve.history().loc[dtm.date(2010, 2, 1):].index
swap_crv = {}
for d in dts[21::21]:
    try:
        swp = pd.Series(data=[sig.InterestRateSwap.fair_swap_rate(
            env, 'EUR', str(x) + 'Y', d)
            for x in range(1, 30)], index=list(range(1, 30)))
        swap_crv[d.date()] = swp
    except:
        pass
        
swap_rates = 100 * pd.concat(swap_crv, axis=1).iloc[::-1]

swap_rates.interpolate('spline',order=3).iloc[:,-1].plot()
swap_rates.interpolate('spline',order=3).iloc[:,-2].plot()
plt.legend()

Overnight Index Swap Instrument

The instantiation of an overnight interest rate swap with different payment frequencies is shown below.

from sigtech.framework.instruments.ois_swap import OISSwap

ois_q = OISSwap(currency='USD',
               tenor='10Y',
               start_date=dtm.date(2016, 7, 5),
               frequency = 'Q')
               
ois_a = OISSwap(currency='USD',
                tenor='10Y',
                start_date=dtm.date(2016, 7, 5),
                frequency = 'A')
                
ois_s = OISSwap(currency='USD',
                tenor='10Y',
                start_date=dtm.date(2016, 7, 5),
                frequency = 'SA')

The performance of a swaps over time can be retrieved via the history() method. The relevant time series data fields for a swap are the following:

ois_a.history_fields

Last updated

© 2023 SIG Technologies Limited