v8 Framework
Search…
⌃K

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.
Python
Output
irs_std.floating_frequency
'6M'
Python
Output
irs_std.carry_roll_down(dtm.date(2017, 6, 20),
[dtm.date(2018, 6, 20), dtm.date(2018, 9, 20)])
Python
Output
irs_std.swap_details(dtm.date(2018, 4, 18), notional=250000000)
{'float_leg_pv': -2289013.305043739,
'fixed_leg_pv': 2290709.7144424,
'pv01': 125241.32304366023,
'float_leg_pv_with_notional': -251495366.7555697,
'fixed_leg_pv_with_notional': 251497063.16496837,
'npv': 1696.4093986610549,
'fixed_rate': 0.001829036662,
'fair_rate': 0.0018276821494817399,
'params': {'float_day_count': 'Actual/360',
'fixed_day_count': '30/360 (Bond Basis)',
'start_date': datetime.date(2017, 6, 20),
'maturity': datetime.date(2022, 6, 20)},
'fixed_flows': [(datetime.date(2018, 6, 20), 457259.1654999902),
(datetime.date(2019, 6, 20), 457259.1654999902),
(datetime.date(2020, 6, 22), 459799.49419722165),
(datetime.date(2021, 6, 21), 455989.0011514023),
(datetime.date(2022, 6, 20), 455989.0011514023)],
'floating_flows': [(datetime.date(2018, 6, 20), -342513.8888888889),
(datetime.date(2018, 12, 20), -323176.10197057924),
(datetime.date(2019, 6, 20), -259151.09509436207),
(datetime.date(2019, 12, 20), -87019.50997874742),
(datetime.date(2020, 6, 22), 145395.57947229609),
(datetime.date(2020, 12, 21), 436014.95916051336),
(datetime.date(2021, 6, 21), 693461.8849646634),
(datetime.date(2021, 12, 20), 917337.710956978),
(datetime.date(2022, 6, 20), 1110030.9636272732)]}
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
}
)
Python
Output
irs_3m.floating_frequency
'3M'

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.
Python
Output
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)
);
Python
Output
discounting_curve.discount_factor(dtm.date(2020, 2, 3), dtm.date(2020, 3, 3))
0.9987149786544263
Python
Output
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:
Python
Output
pd.DataFrame(forecasting_curve_data).head()
Below is an example of how to retrieve the raw values of the projection curve.
Python
Output
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
Depos {'1d': 4.29875, '2d': 4.31576, '1w': 4.31938, ...
Futures {'mar-2006': 95.2425, 'jun-2006': 95.1875, 'se...
Convexity {'mar-2006': 0.0001, 'jun-2006': 0.0004, 'sep-...
Fras {}
Swaps {'1y': 4.83, '2y': 4.794, '3y': 4.788, '4y': 4...
SwapFrequency {'1y': 'Annual', '2y': 'Semi Annual', '3y': 'S...
SwapDayCounts {'1y': 'ACT_360', '2y': '30_360', '3y': '30_36...
dtype: object
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:
Python
Output
irs_std.history_fields
['LastPrice', 'Rate', 'PV01']
Below the rate as a time series is generated:
Python
Output
rate = pd.concat({
'6M Euribor Swap Rate': irs_std.history('Rate'),
'3M Euribor Swap Rate': irs_3m.history('Rate')}, axis=1)
rate.iloc[0]
6M Euribor Swap Rate 0.173544
3M Euribor Swap Rate 0.666249
Name: 2017-01-03 00:00:00, dtype: float64
Below the net present value as a time series is generated:
Python
Output
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
6M Euribor Swap NPV 0.000471
3M Euribor Swap NPV 0.001303
Name: 2017-01-03 00:00:00, dtype: float64
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)
Python
Untitled
s5s10s[['5Y PV','10Y PV']].plot(figsize=(17, 5))
Python
Output
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']
Python
Output
rate.iloc[:, :2].plot(figsize=(17, 5))
Python
Output
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
Python
Output
pds = pd.Series(fcst_curve.history().loc[dt].get('DiscountFactors')).plot(
title='Forecasting Curve Discount Factors');
Python
Output
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.
Python
Output
print(sig.obj.get('USD.D CURVE').get_swap_rate('5Y'))
5Y
trading_datetime
2006-01-02 4.44865
2006-01-03 4.31762
2006-01-04 4.31263
2006-01-05 4.31150
2006-01-06 4.30889
Python
Output
print(sig.InterestRateSwapGroup.fair_swap_rate(currency='USD',
tenor='3Y',
fwd_tenor='2Y',
start_date=dtm.date(2010,2,3)
))
Rate 5Y PV01 5Y PnL 5Y
2017-04-03 1.976200 0.000480 NaN
2017-04-04 2.006050 0.000479 -0.001398
2017-04-05 1.968100 0.000480 0.001853
2017-04-06 1.975799 0.000480 -0.000249
2017-04-07 2.026850 0.000479 -0.002421
2017-04-10 1.997280 0.000479 0.001444
We have the option to change the interpolation method as shown below.
Python
Output
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:
Python
Output
ois_a.history_fields
['LastPrice', 'FairRate', 'PV01']
Last modified 23d ago