In [ ]:
#@title Licensed under the Apache License, Version 2.0 (the "License"); { display-mode: "form" }
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
TensorFlow Finance has built-in library for unconstrained numerical optimization. This notebook shows how to use it.
|
|
In [ ]:
#@title Install TF Quant Finance
!pip install 'tensorflow>=2.1.0'
!pip install tf-quant-finance
In [2]:
#@title Imports
from datetime import datetime
import tensorflow as tf
import tensorflow_probability as tfp
import functools
import matplotlib.colors as colors
import tf_quant_finance as tff
import tf_quant_finance.experimental.dates as dates
from matplotlib import pyplot as plt
from IPython.core.pylabtools import figsize
# Import seaborn if you want to have fancy visualization
# import seaborn as sns
# sns.set(style="darkgrid")
figsize(12, 8)
The following functions are used to build the cashflows on the fixed and floating legs for curve fitting.
In [3]:
#@title 1. Cash instruments
def build_spot_cash_instrument(tenor, close, dtype, **kwargs):
"""Build spot cash instrument.
Args:
tenor: `dates.PeriodTensor`. Defined the tenor of the floating rate.
close: `float64`. Defined the close rate of the instrument.
Returns:
A `dict` containing the inputs for swap_curve_fit, e.g.
`float_leg_start_time`.
"""
assert tenor.period_type() == dates.PeriodType.MONTH
tenor = tenor.quantity().numpy() / 12.0
return {
'float_leg_start_time': tf.constant([0.0], dtype=dtype),
'float_leg_end_time': tf.constant([tenor], dtype=dtype),
'float_leg_daycount': tf.constant([tenor], dtype=dtype),
'fixed_leg_start_time': tf.constant([0.0], dtype=dtype),
'fixed_leg_end_time': tf.constant([tenor], dtype=dtype),
'fixed_leg_daycount': tf.constant([tenor], dtype=dtype),
'fixed_leg_cashflow': tf.constant([-close], dtype=dtype),
'pv': 0.0,
'initial_curve_rate': 1e-6,
}
Forward instruments, similar to cash instruments, generate the cashflow by simple spot rate on a specified forward date, i.e.
$$[1 + R \cdot \tau(t_1, t_2)] \cdot DF(t_2) - DF(t_1) = 0$$where $t_1$ is the forward start time, $t_2$ is the maturity time, $\tau(t_1, t_2)$ is the tenor of the instrument, and R is the market rate.
The difference between the forward and future rates is the convexity adjustment. Since the difference is minimal for instrument less then 2Y maturity, it is assumed to be the same for simplicity here.
In [4]:
#@title 2. Forward and Future instruments
def build_futures_instrument(
settle_date, maturity_date, close, tenor, dtype, **kwargs):
"""Build futures instrument.
Args:
settle_date: `datetime.datetime`. Defines the date when the instrument
is settled.
maturity_date: `datetime.datetime`. Defines the maturity date of the
instrument.
close: `float64`. Defined the close rate of the instrument.
tenor: `dates.PeriodTensor`. Defined the tenor of the floating rate.
Returns:
A `dict` containing the inputs for swap_curve_fit, e.g.
`float_leg_start_time`.
"""
assert tenor.period_type() == dates.PeriodType.MONTH
tenor = tenor.quantity().numpy() / 12.0
start = daycount(
start_date=dates.from_datetimes([settle_date]),
end_date=dates.from_datetimes([maturity_date]),
dtype=dtype
)
return {
'float_leg_start_time': start,
'float_leg_end_time': start + tenor,
'float_leg_daycount': tf.constant([tenor], dtype=dtype),
'fixed_leg_start_time': start,
'fixed_leg_end_time': start + tenor,
'fixed_leg_daycount': tf.constant([tenor], dtype=dtype),
'fixed_leg_cashflow': tf.constant([-close], dtype=dtype),
'pv': 0.0,
'initial_curve_rate': 1e-6,
}
Given a pre-defined notional principal $K$, the present value of the fixed leg can be expressed as
$$ PV(fixed) = \sum_{i=1}^{N_{fixed}} [K \cdot R \cdot \tau(i) \cdot DF(t_i)] + K \cdot DF(t_{N_{fixed}}) $$where $R$ is the market rate, $N_{fixed}$ is the total number of fixed leg payments, $\tau(i)$ is the day count fraction that determines the $i^{th}$ payment amount, $DF(t_1)$ is the discount factor at $i^{th}$ payment time t.
Same for the present value of the floating leg
$$ PV(floating) = \sum_{i=1}^{N_{floating}} [K \cdot R(i) \cdot \tau(i) \cdot DF(t_i)] + K \cdot DF(t_{N_{floating}}) $$where $R(i)$ is an effective rate to cover $i^{th}$ payment on the floating leg.
Under the no-arbitrage rule, the present value of the fixed and floating leg must be equal, i.e.
$$ PV(fixed) = PV(floating) $$
In [5]:
#@title 3. Swap instruments
def build_swap_instrument(
settle_date, maturity_date, close, fixed_tenor, float_tenor, daycount,
roll_convention, dtype, **kwargs):
"""Build futures instrument.
Args:
settle_date: `datetime.datetime`. Defines the date when the instrument
is settled.
maturity_date: `datetime.datetime`. Defines the maturity date of the
instrument.
close: `float64`. Defined the close rate of the instrument.
fixed_tenor: `float64`. Defined the tenor of the fixed rate.
float_tenor: `float64`. Defined the tenor of the floating rate.
daycount: `callable`. Defines the daycount convention function imported
from `dates.daycounts`.
roll_convention: `dates.BusinessDayConvention`. Defines the roll
convention.
dtype: `tf.Dtype`. If supplied the dtype for the (elements of)
`float_leg_start_times`, and `fixed_leg_start_times`.
Default value: None which maps to the default dtype inferred by
TensorFlow.
Returns:
A `dict` containing the inputs for swap_curve_fit, e.g.
`float_leg_start_time`.
"""
settle_date = dates.from_datetimes([settle_date])
maturity_date = dates.from_datetimes([maturity_date])
float_leg_dates = dates.PeriodicSchedule(
start_date=settle_date,
end_date=maturity_date,
tenor=float_tenor,
roll_convention=roll_convention,
backward=False).dates()[0]
fixed_leg_dates = dates.PeriodicSchedule(
start_date=settle_date,
end_date=maturity_date,
tenor=fixed_tenor,
roll_convention=roll_convention,
backward=False).dates()[0]
return {
'float_leg_start_time': daycount(
start_date=settle_date,
end_date=float_leg_dates[:-1],
dtype=dtype),
'float_leg_end_time': daycount(
start_date=settle_date,
end_date=float_leg_dates[1:],
dtype=dtype),
'float_leg_daycount': daycount(
start_date=float_leg_dates[:-1],
end_date=float_leg_dates[1:],
dtype=dtype),
'fixed_leg_start_time': daycount(
start_date=settle_date,
end_date=fixed_leg_dates[:-1],
dtype=dtype),
'fixed_leg_end_time': daycount(
start_date=settle_date,
end_date=fixed_leg_dates[1:],
dtype=dtype),
'fixed_leg_daycount': daycount(
start_date=fixed_leg_dates[:-1],
end_date=fixed_leg_dates[1:],
dtype=dtype),
'fixed_leg_cashflow': tf.repeat(
tf.constant(-close, dtype=dtype),
repeats=fixed_leg_dates.shape[0] - 1,
),
'pv': 0.0,
'initial_curve_rate': 1e-6,
}
The data is retrieved from Bloomberg on 2020-02-14 to fit the USD LIBOR 3M curve.
The following market instruments are used
LIBOR Fixed 3 Month: Spot LIBOR 3M rate
ED Futures: Eurodollar Futures with expiry up to 2Y
Interest Rate Swap: Liquid interest rate swap instruments with tenors from 2 years to 50 years
In [6]:
float_leg_start_times = []
float_leg_end_times = []
float_leg_daycounts = []
fixed_leg_start_times = []
fixed_leg_end_times = []
fixed_leg_daycounts = []
fixed_leg_cashflows = []
pvs = []
initial_curve_rates = []
# Global dtype
dtype = tf.float64
# Global daycount - Fit on USD curve
daycount = tff.experimental.dates.daycounts.actual_360
# Global roll convention - Backward
roll_convention = dates.BusinessDayConvention.PRECEDING
# The fixed leg payment frequency is 6M
fixed_tenor = dates.periods.months(6)
# The floating rate is always USD LIBOR 3M
float_tenor = dates.periods.months(3)
instruments = [
{'settle_date': '2020-02-19', 'maturity_date': '2020-05-21',
'close': 0.01696000000, 'name': 'USD3MD'},
{'settle_date': '2020-02-19', 'maturity_date': '2020-06-17',
'close': 0.01669900000, 'name': 'EDH0'},
{'settle_date': '2020-02-19', 'maturity_date': '2020-09-16',
'close': 0.01544400000, 'name': 'EDM0'},
{'settle_date': '2020-02-19', 'maturity_date': '2020-12-16',
'close': 0.01468600000, 'name': 'EDU0'},
{'settle_date': '2020-02-19', 'maturity_date': '2021-03-17',
'close': 0.01427500000, 'name': 'EDZ0'},
{'settle_date': '2020-02-19', 'maturity_date': '2021-06-16',
'close': 0.01326200000, 'name': 'EDH1'},
{'settle_date': '2020-02-19', 'maturity_date': '2021-09-15',
'close': 0.01299700000, 'name': 'EDM1'},
{'settle_date': '2020-02-19', 'maturity_date': '2021-12-15',
'close': 0.01277900000, 'name': 'EDU1'},
{'settle_date': '2020-02-19', 'maturity_date': '2022-03-16',
'close': 0.01295900000, 'name': 'EDZ1'},
{'settle_date': '2020-02-19', 'maturity_date': '2022-06-15',
'close': 0.01288600000, 'name': 'EDH2'},
{'settle_date': '2020-02-19', 'maturity_date': '2022-09-21',
'close': 0.01295900000, 'name': 'EDM2'},
{'settle_date': '2020-02-19', 'maturity_date': '2022-12-21',
'close': 0.01308200000, 'name': 'EDU2'},
{'settle_date': '2020-02-19', 'maturity_date': '2023-03-15',
'close': 0.01335500000, 'name': 'EDZ2'},
{'settle_date': '2020-02-19', 'maturity_date': '2024-02-21',
'close': 0.01378410000, 'name': 'USDAM3L4Y'},
{'settle_date': '2020-02-19', 'maturity_date': '2025-02-21',
'close': 0.01384037500
, 'name': 'USDAM3L6Y'},
{'settle_date': '2020-02-19', 'maturity_date': '2027-02-22',
'close': 0.01419340000, 'name': 'USDAM3L7Y'},
{'settle_date': '2020-02-19', 'maturity_date': '2028-02-22',
'close': 0.01443062500, 'name': 'USDAM3L8Y'},
{'settle_date': '2020-02-19', 'maturity_date': '2029-02-21',
'close': 0.01468025000, 'name': 'USDAM3L9Y'},
{'settle_date': '2020-02-19', 'maturity_date': '2030-02-21',
'close': 0.01494412500, 'name': 'USDAM3L10Y'},
{'settle_date': '2020-02-19', 'maturity_date': '2032-02-23',
'close': 0.01542162500, 'name': 'USDAM3L12Y'},
{'settle_date': '2020-02-19', 'maturity_date': '2035-02-21',
'close': 0.01592825000, 'name': 'USDAM3L15Y'},
{'settle_date': '2020-02-19', 'maturity_date': '2040-02-21',
'close': 0.01644683333, 'name': 'USDAM3L20Y'},
{'settle_date': '2020-02-19', 'maturity_date': '2045-02-21',
'close': 0.01666350000, 'name': 'USDAM3L25Y'},
{'settle_date': '2020-02-19', 'maturity_date': '2050-02-22',
'close': 0.01662650000, 'name': 'USDAM3L30Y'},
{'settle_date': '2020-02-19', 'maturity_date': '2060-02-23',
'close': 0.01613000000, 'name': 'USDAM3L40Y'},
{'settle_date': '2020-02-19', 'maturity_date': '2070-02-21',
'close': 0.01534000000, 'name': 'USDAM3L50Y'},
]
for instrument in instruments:
instrument['settle_date'] = (
datetime.strptime(instrument['settle_date'], '%Y-%m-%d')
)
instrument['maturity_date'] = (
datetime.strptime(instrument['maturity_date'], '%Y-%m-%d')
)
if instrument['name'] == 'USD3MD':
# Spot instrument
instrument = build_spot_cash_instrument(
tenor=float_tenor,
dtype=dtype,
**instrument
)
elif instrument['name'][:2] == 'ED':
# Futures instrument
instrument = build_futures_instrument(
dtype=dtype,
tenor=float_tenor,
**instrument
)
elif instrument['name'].startswith('USDAM3L'):
# Swap instrument
instrument = build_swap_instrument(
fixed_tenor=fixed_tenor,
float_tenor=float_tenor,
roll_convention=roll_convention,
daycount=daycount,
dtype=dtype,
**instrument
)
else:
raise NotImplementedError(
'Cannot recognize the type of instrument %s'
% instrument['name']
)
float_leg_start_times.append(instrument['float_leg_start_time'])
float_leg_end_times.append(instrument['float_leg_end_time'])
float_leg_daycounts.append(instrument['float_leg_daycount'])
fixed_leg_start_times.append(instrument['fixed_leg_start_time'])
fixed_leg_end_times.append(instrument['fixed_leg_end_time'])
fixed_leg_daycounts.append(instrument['fixed_leg_daycount'])
fixed_leg_cashflows.append(instrument['fixed_leg_cashflow'])
pvs.append(instrument['pv'])
initial_curve_rates.append(instrument['initial_curve_rate'])
curve = tff.rates.swap_curve_fit(
float_leg_start_times=float_leg_start_times,
float_leg_end_times=float_leg_end_times,
float_leg_daycount_fractions=float_leg_daycounts,
fixed_leg_start_times=fixed_leg_start_times,
fixed_leg_end_times=fixed_leg_end_times,
fixed_leg_cashflows=fixed_leg_cashflows,
fixed_leg_daycount_fractions=fixed_leg_daycounts,
present_values=tf.constant(pvs, dtype=dtype),
initial_curve_rates=tf.constant(initial_curve_rates, dtype))
In [7]:
plt.plot(curve.times, curve.rates)
plt.title('Discount Rate Curve', fontsize=20)
plt.xlabel('Time', fontsize=18)
plt.ylabel('Discount Rate', fontsize=18)
plt.legend()
plt.show()
[1] Bloomberg, Building the Bloomberg Interest Rate Curve – Definitions and Methodology, Mar 2016