Skip to content

Commit

Permalink
* added example to get_curve_fit
Browse files Browse the repository at this point in the history
  • Loading branch information
sonntagsgesicht committed May 31, 2022
1 parent 391afce commit e5db575
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 10 deletions.
7 changes: 4 additions & 3 deletions dcf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
# A Python library for generating discounted cashflows.
#
# Author: sonntagsgesicht, based on a fork of Deutsche Postbank [pbrisk]
# Version: 0.7, copyright Wednesday, 11 May 2022
# Version: 0.7, copyright Sunday, 22 May 2022
# Website: https://github.com/sonntagsgesicht/dcf
# License: Apache License 2.0 (see LICENSE file)


__doc__ = 'A Python library for generating discounted cashflows.'
__version__ = '0.7'
__dev_status__ = '4 - Beta'
__date__ = 'Sunday, 22 May 2022'
__date__ = 'Tuesday, 31 May 2022'
__author__ = 'sonntagsgesicht, based on a fork of Deutsche Postbank [pbrisk]'
__email__ = 'sonntagsgesicht@icloud.com'
__url__ = 'https://github.com/sonntagsgesicht/' + __name__
Expand Down Expand Up @@ -64,4 +64,5 @@
from .ratingclass import RatingClass # noqa E401 E402

from .pricer import get_present_value, get_fair_rate, get_interest_accrued, \
get_yield_to_maturity, get_basis_point_value # noqa E401 E402
get_yield_to_maturity, get_basis_point_value, \
get_curve_fit # noqa E401 E402
65 changes: 60 additions & 5 deletions dcf/pricer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# A Python library for generating discounted cashflows.
#
# Author: sonntagsgesicht, based on a fork of Deutsche Postbank [pbrisk]
# Version: 0.7, copyright Wednesday, 11 May 2022
# Version: 0.7, copyright Sunday, 22 May 2022
# Website: https://github.com/sonntagsgesicht/dcf
# License: Apache License 2.0 (see LICENSE file)

Expand Down Expand Up @@ -402,7 +402,7 @@ def get_bucketed_delta(cashflow_list, discount_curve, valuation_date=None,
def get_curve_fit(cashflow_list, discount_curve, valuation_date=None,
fitting_curve=None, fitting_grid=None, present_value=0.0,
precision=1e-7, bounds=(-0.1, .2)):
"""fit curve to cashflow_list prices (bootstrapping)
r"""fit curve to cashflow_list prices (bootstrapping)
:param cashflow_list: list (!) of cashflow_list. products to match prices
:param discount_curve: discount factors are obtained from this curve
Expand All @@ -411,7 +411,7 @@ def get_curve_fit(cashflow_list, discount_curve, valuation_date=None,
(optional; default is **discount_curve**)
:param fitting_grid: domain to fit prices to
(optional; default **fitting_curve.domain**)
:param present_value: tuple of prices for product in **cashflow_list**,
:param present_value: list (!) of prices of products in **cashflow_list**,
each one to be met by discounting
(optional; default is list of 0.0)
:param precision: max distance of present value to par
Expand All @@ -421,13 +421,67 @@ def get_curve_fit(cashflow_list, discount_curve, valuation_date=None,
:return: tuple(float) **fitting_data** as curve values to build curve
together with curve points from **fitting_grid**
"""
Bootstrapping is a sequential approach to set curve values $y_i$
in order to match present values of cashflow products $X_j$
to given prices $p_j$.
This is done sequentially, i.e. for a
consecutive sequence of curve points $t_i$, $i=1 \dots n$.
Starting at $i=1$ at curve point $t_1$ the value $y_1$
is varied by
`simple bracketing <https://en.wikipedia.org/wiki/Nested_intervals>`_
such that the present value
$$v_0(X_j) = p_j \text{ for $X_j$ maturing before or at $t_j$.}$$
Here, the **fitting_curve** can be the discount curve
but also any forward curve or even a volatility curve.
Once $y_1$ is found the next dated $t_2$ in **fitting_grid** is handled.
This is kept going on until all prices $p_j$ and all points $t_i$ match.
Note, in order to conclude successfully,
the valuations $v_0(X_j)$ must be sensitive
to changes of curve value $y_i$, i.e. at least one $j$ must hold
$$\frac{d}{dy_i}v_0(X_j) \neq 0$$
with in the bracketing bounds of $y_i$ as set by **bounds**.
Example
-------
First, setup dates and schedule
>>> from businessdate import BusinessDate, BusinessSchedule
>>> today = BusinessDate(20161231)
>>> schedule = BusinessSchedule(today + '1y', today + '5y', '1y')
of products
>>> from dcf import RateCashFlowList, get_present_value
>>> cashflow_list = [RateCashFlowList([s for s in schedule if s <= d], 1e6, origin=today, fixed_rate=0.01) for d in schedule]
and prices to match to.
>>> from dcf import ZeroRateCurve
>>> rates = [0.01, 0.009, 0.012, 0.014, 0.011]
>>> curve = ZeroRateCurve(schedule, rates)
>>> present_value = [get_present_value(cfs, curve, today) for cfs in cashflow_list]
Then fit a plain curve
>>> from dcf import get_curve_fit
>>> target = ZeroRateCurve(schedule, [0.01, 0.01, 0.01, 0.01, 0.01])
>>> data = get_curve_fit(cashflow_list, target, today, fitting_curve=target, present_value=present_value)
>>> [round(d, 6) for d in data]
[0.01, 0.009, 0.012, 0.014, 0.011]
""" # noqa 501
if isinstance(present_value, (int, float)):
present_value = [present_value] * len(cashflow_list)

fitting_curve = discount_curve if fitting_curve is None else fitting_curve
fitting_grid = \
fitting_grid if fitting_grid is None else fitting_curve.domain
fitting_curve.domain if fitting_grid is None else fitting_grid

# copy fitting_curve but set al values to 0.0
fitting_curve.spread = fitting_curve.__class__(fitting_grid, fitting_curve)
Expand All @@ -437,6 +491,7 @@ def get_curve_fit(cashflow_list, discount_curve, valuation_date=None,
pp_list = tuple(zip(cashflow_list, present_value))
for d in fitting_curve.spread.domain:
# prepare products and prices
# todo: better use sensitivity to current curve point than maturity
filtered_pp_list = list(p for p in pp_list if max(p[0].domain) <= d)

# set error function
Expand Down
6 changes: 4 additions & 2 deletions test/unittests/pricer_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,6 @@ def setUp(self):
self.rate = 0.01
rates = [(self.rate + 0.001 * i * (-1)**i) for i in range(lens)]
self.df = ZeroRateCurve(self.schedule, rates)
self.curve = ZeroRateCurve(self.schedule, [self.rate] * lens)

def test_discount_curve_fitting(self):
notional = 1e6
Expand All @@ -229,6 +228,9 @@ def test_discount_curve_fitting(self):
products.append(swp)
pvs.append(pv)

data = get_curve_fit(products, self.curve, self.today, self.curve, self.schedule, pvs)
curve = ZeroRateCurve(self.schedule, [self.rate] * len(self.schedule))
data = get_curve_fit(products, curve, self.today, present_value=pvs)
# curve = ZeroRateCurve([self.today], [self.rate])
# data = get_curve_fit(products, curve, self.today, curve, self.schedule, pvs)
for p, q in zip(data, self.df(self.df.domain)):
self.assertAlmostEqual(p, q)

0 comments on commit e5db575

Please sign in to comment.