Interest Rate Swaps
===================
*Very nascent*.
Interest rate swaps are a first step towards including **rate-sensitive
instruments** in the modeling and valuation spectrum of DX Analytics.
The model used in the following is the **square-root diffusion process**
by Cox-Ingersoll-Ross (1985). Data used are UK/London OIS and Libor
rates.
.. code:: python
import dx
import datetime as dt
import time
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()
%matplotlib inline
OIS Data & Discounting
----------------------
We start by importing **OIS term structure data** (source:
http://www.bankofengland.co.uk) for risk-free discounting. We also
adjust the data structure somewhat for our purposes.
.. code:: python
# UK OIS Spot Rates Yield Curve
oiss = pd.read_excel('data/ukois09.xls', 'oiss')
# use years as index
oiss = oiss.set_index('years')
# del oiss['years']
.. code:: python
# only date information for columns, no time
oiss.columns = [d.date() for d in oiss.columns]
.. code:: python
oiss.tail()
.. raw:: html
|
2014-10-01 |
2014-10-02 |
2014-10-03 |
2014-10-06 |
2014-10-07 |
2014-10-08 |
2014-10-09 |
2014-10-10 |
2014-10-13 |
2014-10-14 |
2014-10-15 |
2014-10-16 |
years |
|
|
|
|
|
|
|
|
|
|
|
|
4.666667 |
1.550947 |
1.525838 |
1.570203 |
1.533901 |
1.479044 |
1.445234 |
1.436023 |
1.409962 |
1.337567 |
1.283932 |
1.063205 |
1.234125 |
4.750000 |
1.563661 |
1.538616 |
1.583198 |
1.546459 |
1.491152 |
1.457593 |
1.448627 |
1.422015 |
1.349782 |
1.296052 |
1.074773 |
1.246277 |
4.833333 |
1.576142 |
1.551158 |
1.595952 |
1.558789 |
1.503050 |
1.469768 |
1.461040 |
1.433887 |
1.361832 |
1.308025 |
1.086254 |
1.258307 |
4.916667 |
1.588400 |
1.563474 |
1.608472 |
1.570898 |
1.514746 |
1.481764 |
1.473269 |
1.445585 |
1.373724 |
1.319859 |
1.097650 |
1.270219 |
5.000000 |
1.600442 |
1.575572 |
1.620768 |
1.582794 |
1.526247 |
1.493588 |
1.485320 |
1.457116 |
1.385461 |
1.331557 |
1.108962 |
1.282014 |
Next we replace the **year fraction index** by a **DatetimeIndex**.
.. code:: python
# generate time index given input data
# starting date + 59 months
date = oiss.columns[-1]
index = pd.date_range(date, periods=60, freq='M') # , tz='GMT')
index = [d.replace(day=date.day) for d in index]
index = pd.DatetimeIndex(index)
oiss.index = index
Let us have a look at the **most current data**, i.e. the term
structure, of the data set.
.. code:: python
oiss.iloc[:, -1].plot(figsize=(10, 6))
.. parsed-literal::
.. image:: 10_dx_interest_rate_swaps_files/10_dx_interest_rate_swaps_13_1.png
This data is used to instantiate a ``deterministic_short_rate`` **model
for risk-neutral discounting** purposes.
.. code:: python
# generate deterministic short rate model based on UK OIS curve
ois = dx.deterministic_short_rate('ois', zip(oiss.index, oiss.iloc[:, -1].values / 100))
.. code:: python
# example dates and corresponding discount factors
dr = pd.date_range('2015-1', periods=4, freq='6m').to_pydatetime()
ois.get_discount_factors(dr)[::-1]
.. parsed-literal::
([0.98982514869237104, 0.99227549016270356, 0.99562377741583252, 1.0],
array([datetime.datetime(2015, 1, 31, 0, 0),
datetime.datetime(2015, 7, 31, 0, 0),
datetime.datetime(2016, 1, 31, 0, 0),
datetime.datetime(2016, 7, 31, 0, 0)], dtype=object))
Libor Market Data
-----------------
We want to model a **3 month Libor-based interest rate swap**. To this
end, we need Libor term structure data, i.e. forward rates in this case
(source: http://www.bankofengland.co.uk), to calibrate the valuation to.
The data importing and adjustments are the same as before.
.. code:: python
# UK Libor foward rates
libf = pd.read_excel('data/ukblc05.xls', 'fwds')
# use years as index
libf = libf.set_index('years')
.. code:: python
# only date information for columns, no time
libf.columns = [d.date() for d in libf.columns]
.. code:: python
libf.tail()
.. raw:: html
|
2014-10-01 |
2014-10-02 |
2014-10-03 |
2014-10-06 |
2014-10-07 |
2014-10-08 |
2014-10-09 |
2014-10-10 |
2014-10-13 |
2014-10-14 |
2014-10-15 |
2014-10-16 |
years |
|
|
|
|
|
|
|
|
|
|
|
|
4.666667 |
2.722915 |
2.686731 |
2.678433 |
2.681385 |
2.639166 |
2.542012 |
2.528768 |
2.489063 |
2.470154 |
2.349732 |
2.372435 |
2.157431 |
4.750000 |
2.733588 |
2.697887 |
2.690366 |
2.692824 |
2.650841 |
2.554583 |
2.543066 |
2.502357 |
2.483842 |
2.366052 |
2.387671 |
2.176186 |
4.833333 |
2.744126 |
2.708848 |
2.702083 |
2.704147 |
2.662380 |
2.567071 |
2.557216 |
2.515531 |
2.497446 |
2.382298 |
2.402829 |
2.194784 |
4.916667 |
2.754540 |
2.719634 |
2.713604 |
2.715368 |
2.673794 |
2.579486 |
2.571229 |
2.528592 |
2.510973 |
2.398468 |
2.417915 |
2.213221 |
5.000000 |
2.764842 |
2.730264 |
2.724949 |
2.726501 |
2.685095 |
2.591835 |
2.585117 |
2.541548 |
2.524432 |
2.414562 |
2.432939 |
2.231493 |
.. code:: python
# generate time index given input data
# starting date + 59 months
date = libf.columns[-1]
index = pd.date_range(date, periods=60, freq='M') # , tz='GMT')
index = [d.replace(day=date.day) for d in index]
index = pd.DatetimeIndex(index)
libf.index = index
And the short end of the **Libor term sturcture** visualized.
.. code:: python
libf.iloc[:, -1].plot(figsize=(10, 6))
.. parsed-literal::
.. image:: 10_dx_interest_rate_swaps_files/10_dx_interest_rate_swaps_24_1.png
Model Calibration
-----------------
Next, equipped with the Libor data, we calibrate the **square-root
diffusion short rate model**. A bit of data preparation:
.. code:: python
t = libf.index.to_pydatetime()
f = libf.iloc[:, -1].values / 100
initial_value = 0.005
A **mean-squared error (MSE)** function to be minimized during
calibration.
.. code:: python
def srd_forward_error(p0):
global initial_value, f, t
if p0[0] < 0 or p0[1] < 0 or p0[2] < 0:
return 100
f_model = dx.srd_forwards(initial_value, p0, t)
MSE = np.sum((f - f_model) ** 2) / len(f)
return MSE
And the **calibration** itself.
.. code:: python
from scipy.optimize import fmin
.. code:: python
opt = fmin(srd_forward_error, (1.0, 0.7, 0.2),
maxiter=1000, maxfun=1000)
.. parsed-literal::
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 371
Function evaluations: 649
The **optimal parameters** (kappa, theta, sigma) are:
.. code:: python
opt
.. parsed-literal::
array([ 0.00544441, 1.80697228, 0.23689443])
The **model fit** is not too bad in this case.
.. code:: python
plt.figure(figsize=(10, 6))
plt.plot(t, f, label='market forward rates')
plt.plot(t, dx.srd_forwards(initial_value, opt, t), 'r.', label='model forward rates')
plt.gcf().autofmt_xdate(); plt.legend(loc=0)
.. parsed-literal::
.. image:: 10_dx_interest_rate_swaps_files/10_dx_interest_rate_swaps_36_1.png
Floating Rate Modeling
----------------------
The optimal parameters from the calibration are used to model the
**floating rate (3m Libor rate)**.
.. code:: python
# market environment
me_srd = dx.market_environment('me_srd', dt.datetime(2014, 10, 16))
.. code:: python
# square-root diffusion
me_srd.add_constant('initial_value', 0.02)
me_srd.add_constant('kappa', opt[0])
me_srd.add_constant('theta', opt[1])
me_srd.add_constant('volatility', opt[2])
me_srd.add_curve('discount_curve', ois)
# OIS discounting object
me_srd.add_constant('currency', 'EUR')
me_srd.add_constant('paths', 10000)
me_srd.add_constant('frequency', 'w')
me_srd.add_constant('starting_date', me_srd.pricing_date)
me_srd.add_constant('final_date', dt.datetime(2020, 12, 31))
.. code:: python
srd = dx.square_root_diffusion('srd', me_srd)
Let us have a look at some **simulated rate paths**.
.. code:: python
paths = srd.get_instrument_values()
.. code:: python
plt.figure(figsize=(10, 6))
plt.plot(srd.time_grid, paths[:, :6])
.. parsed-literal::
[,
,
,
,
,
]
.. image:: 10_dx_interest_rate_swaps_files/10_dx_interest_rate_swaps_44_1.png
Interest Rate Swap
------------------
Finally, we can model the **interest rate swap** itself.
Modeling
~~~~~~~~
First, the market environment with all the **parameters** needed.
.. code:: python
# market environment for the IRS
me_irs = dx.market_environment('irs', me_srd.pricing_date)
me_irs.add_constant('fixed_rate', 0.01)
me_irs.add_constant('trade_date', me_srd.pricing_date)
me_irs.add_constant('effective_date', me_srd.pricing_date)
me_irs.add_constant('payment_date', dt.datetime(2014, 12, 27))
me_irs.add_constant('payment_day', 27)
me_irs.add_constant('termination_date', me_srd.get_constant('final_date'))
me_irs.add_constant('currency', 'EUR')
me_irs.add_constant('notional', 1000000)
me_irs.add_constant('tenor', '6m')
me_irs.add_constant('counting', 'ACT/360')
# discount curve from mar_env of floating rate
The instantiation of the **valuation object**.
.. code:: python
irs = dx.interest_rate_swap('irs', srd, me_irs)
Valuation
~~~~~~~~~
The **present value** of the interest rate swap given the assumption, in
particular, of the fixed rate.
.. code:: python
%time irs.present_value(fixed_seed=True)
.. parsed-literal::
CPU times: user 185 ms, sys: 14 ms, total: 199 ms
Wall time: 196 ms
.. parsed-literal::
482804.62804830389
You can also generate a **full output of all present values** per
simulation path.
.. code:: python
irs.present_value(full=True).iloc[:, :6]
.. raw:: html
|
0 |
1 |
2 |
3 |
4 |
5 |
2014-12-27 |
20919.908406 |
3765.545880 |
-10000.000000 |
20447.662984 |
15671.171542 |
-461.905549 |
2015-06-27 |
27796.391566 |
67649.371268 |
-7952.865109 |
21069.238749 |
6547.642190 |
56254.997722 |
2015-12-27 |
72797.267752 |
65085.851729 |
-9495.534310 |
7184.517383 |
-6249.109421 |
72603.409748 |
2016-06-27 |
146587.773739 |
60725.900678 |
-9450.695252 |
10630.874710 |
1457.279233 |
96390.826927 |
2016-12-27 |
112006.856512 |
11685.574401 |
-9365.011855 |
36777.060898 |
40752.233786 |
42437.807055 |
2017-06-27 |
89091.008350 |
19143.973791 |
-8652.442452 |
10394.933700 |
81112.588234 |
49316.809090 |
2017-12-27 |
153802.099096 |
37433.554707 |
-2231.031239 |
3046.024441 |
81218.496071 |
89235.982850 |
2018-06-27 |
114033.046751 |
-4200.273599 |
8204.181410 |
86020.232903 |
92146.494366 |
44010.774119 |
2018-12-27 |
165327.788708 |
-7186.102024 |
-8829.883386 |
46785.300398 |
171037.865434 |
14615.688086 |
2019-06-27 |
101110.008932 |
-9038.246291 |
-6883.724709 |
29794.380064 |
183168.704902 |
-4599.638746 |
2019-12-27 |
57890.028467 |
-8998.424023 |
-8573.907447 |
12491.846129 |
151080.678798 |
-8960.333669 |
2020-06-27 |
95516.235053 |
-8968.295497 |
-8963.127240 |
15234.994497 |
67282.503413 |
-7763.697572 |
2020-12-27 |
4651.947620 |
-4500.468014 |
-6316.280222 |
39899.010233 |
107971.761933 |
-8568.679338 |
**Copyright, License & Disclaimer**
© Dr. Yves J. Hilpisch \| The Python Quants GmbH
DX Analytics (the "dx library") is licensed under the GNU Affero General
Public License version 3 or later (see http://www.gnu.org/licenses/).
DX Analytics comes with no representations or warranties, to the extent
permitted by applicable law.
http://www.pythonquants.com \| analytics@pythonquants.com \|
http://twitter.com/dyjh
**Python Quant Platform** \| http://quant-platform.com
**Derivatives Analytics with Python (Wiley Finance)** \|
http://derivatives-analytics-with-python.com
**Python for Finance (O'Reilly)** \|
http://shop.oreilly.com/product/0636920032441.do