API Documentation

Cashflow Objects

Cashflow Payoffs

FixedCashFlowPayOff

fixed cashflow payoff

RateCashFlowPayOff

interest rate cashflow payoff

OptionCashFlowPayOff

European option payoff function

class dcf.payoffs.FixedCashFlowPayOff(pay_date, amount=1.0, forward_curve=None)[source]

Bases: CashFlowPayOff

fixed cashflow payoff

Parameters:
  • pay_date – cashflow payment date \(t\)

  • amount – notional amount \(N\) (might be a callable function)

  • forward_curve – price forward curve \(P(t)\)

A fixed cashflow payoff \(X(t)\) at \(t\) is given directly by the notional amount \(N + P(t)\)

Invoking details method \(X()\) or \(X(t, P)\) with a forward_curve object \(P\) as argument returns the cashflow details as a dict-like object.

>>> from dcf import FixedCashFlowPayOff
>>> cf = FixedCashFlowPayOff(0.25, amount=123.456)
>>> cf.details()
Details(
{'pay date': 0.25, 'cashflow': 123.456}
)

The actual expected cashflow payoff amount of \(X\) (which is again just the fixed amount \(N\)) can be obtained by casting to float.

>>> cf()
123.456
details(valuation_date=None, *, forward_curve=None, **__)[source]
Parameters:
  • valuation_date

  • forward_curve

  • __

Returns:

class dcf.payoffs.RateCashFlowPayOff(pay_date, start, end, amount=1.0, day_count=None, fixing_offset=None, fixed_rate=None, forward_curve=None)[source]

Bases: CashFlowPayOff

interest rate cashflow payoff

Parameters:
  • pay_date – cashflow payment date

  • start – interst accrued period start date \(s\)

  • end – interst accrued period end date \(e\)

  • amount – notional amount \(N\)

  • day_count – function to calculate accrued period year fraction \(\tau\)

  • fixing_offset – time difference between interest rate fixing date and interest period payment date \(\delta\)

  • fixed_rate – agreed fixed rate \(c\)

  • forward_curve – float rate forward curve either as numerical value or function. If forward_curve is None no float rate is applied, even not if dcf.payoffs.RateCashFlowPayOff.details() is invoked with a forward curve. (optional; default is None, i.e. no float rate is applied)

A contigent interest rate cashflow payoff \(X\) is given for a float rate \(f\) at \(T=s-\delta\)

\[X(f(T)) = (f(T) + c)\ \tau(s,e)\ N\]

Invoking \(X(t, f)\) with a forward_curve object \(f\) as argument returns the actual expected cashflow payoff amount of \(X\).

>>> from dcf import RateCashFlowPayOff
>>> cf = RateCashFlowPayOff(pay_date=1.0, start=1.25, end=1.5, amount=1.0, fixed_rate=0.005, forward_curve=0)
>>> cf.details()
Details(
{ 'pay date': 1.0,
  'cashflow': 0.00125,
  'notional': 1.0,
  'is rec': True,
  'fixed rate': 0.005,
  'start date': 1.25,
  'end date': 1.5,
  'year fraction': 0.25,
  'forward rate': 0,
  'fixing date': 1.25,
  'forward-curve-id': ...}
)
>>> cf()
0.00125

suppying an iterest forward curve changes float forward rate

>>> forward_curve = 0.05
>>> cf.details(forward_curve=forward_curve)
Details(
{ 'pay date': 1.0,
  'cashflow': 0.01375,
  'notional': 1.0,
  'is rec': True,
  'fixed rate': 0.005,
  'start date': 1.25,
  'end date': 1.5,
  'year fraction': 0.25,
  'forward rate': 0.05,
  'fixing date': 1.25,
  'forward-curve-id': ...}
)
>>> cf(forward_curve=forward_curve)  # expected cashflow
0.01375

If forward_curve is None (default) no float rate is applied. Even if forward_curve is given for details calculation.

>>> cf = RateCashFlowPayOff(pay_date=1.0, start=1.25, end=1.5, amount=1.0, fixed_rate=0.005)
>>> cf.details(forward_curve=forward_curve)
Details(
{ 'pay date': 1.0,
  'cashflow': 0.00125,
  'notional': 1.0,
  'is rec': True,
  'fixed rate': 0.005,
  'start date': 1.25,
  'end date': 1.5,
  'year fraction': 0.25}
)
details(valuation_date=None, *, forward_curve=None, **__)[source]
Parameters:
  • valuation_date

  • forward_curve

  • __

Returns:

class dcf.payoffs.OptionCashFlowPayOff(pay_date, expiry=None, amount=1.0, strike=None, option_type='call', *, forward_curve=None, option_curve=None)[source]

Bases: CashFlowPayOff

European option payoff function

Parameters:
  • pay_date – cashflow payment date

  • expiry – option exipry date \(T\) (optional; by default pay_date)

  • amount – option notional amount \(N\) (optional: default is 1.0)

  • strike – strike price \(K\) (optional: default is None i.e. at-the-money strike)

  • option_type – str or OptionCashFlowPayOff.OPTION_TYPES enum to prick for option type call, put, cap, floor (optional with default call)

An European call option \(C_K(S(T))\) is the right to buy an agreed amount \(N\) of an asset with future price \(S(T)\) at a future point in time \(T\) (the option exipry date) for a pre-agreed strike price \(K\).

The call option payoff provides the expected profit from such transaction, i.e.

\[C_K(S(T)) = N \cdot E[ \max(S(T)-K,0) ]\]

Resp. a put option \(P_K(S(T))\) is the right to sell an asset at a pre-agreed strike price. Hence, the put option payoff provides the expected profit from such transaction, i.e.

\[P_K(S(T)) = N \cdot E[ \max(K-S(T),0) ]\]

As the asset price \(S(t)\) is unknown at time \(t < T\), the estimation of \(C_K(S(T))\) resp. \(P_K(S(T))\) requires assumptions on the as randomness understood unkown behavior of \(S\) until \(T\).

This is provided by an option_curve instance implementing a option_curve and is invoked by calling an dcf.payoffs.OptionCashFlowPayOff object.

First, setup a classical log-normal Black-Scholes model.

>>> from yieldcurves import OptionPricingCurve
>>> from math import exp
>>> f = lambda t: 100.0 * exp(t * 0.05)  # spot price 100 and yield of 5%
>>> v = lambda *_: 0.1  # flat volatility of 10%
>>> m = OptionPricingCurve.black76(f, volatility=v)

Then, build a call option payoff.

>>> from dcf import OptionCashFlowPayOff
>>> c = OptionCashFlowPayOff(pay_date=0.33, expiry=0.25, strike=110.0)
>>> # get expected option payoff
>>> c.details(option_curve=m)
Details(
{ 'pay date': 0.33,
  'cashflow': 0.107267...,
  'option type': 'call',
  'is put': False,
  'is long': True,
  'notional': 1.0,
  'strike': 110.0,
  'forward': 101.257845...,
  'valuation date': 0,
  'expiry date': 0.25,
  'time to expiry': 0.25,
  'volatility': 0.1,
  'option model': 'Black76',
  'forward-curve-id': ...,
  'volatility-curve-id': ...,
  'option-curve-id': ...}
)
>>> float(c.details(forward_curve=m))
0.107267...

And a put option payoff.

>>> p = OptionCashFlowPayOff(pay_date=0.33, expiry=0.25, strike=110.0, option_type='put')
>>> # get expected option payoff
>>> p.details(option_curve=m)
Details(
{ 'pay date': 0.33,
  'cashflow': 8.849422...,
  'option type': 'put',
  'is put': True,
  'is long': True,
  'notional': 1.0,
  'strike': 110.0,
  'forward': 101.257845...,
  'valuation date': 0,
  'expiry date': 0.25,
  'time to expiry': 0.25,
  'volatility': 0.1,
  'option model': 'Black76',
  'forward-curve-id': ...,
  'volatility-curve-id': ...,
  'option-curve-id': ...}
)
>>> float(p.details(option_curve=m))
8.849422...
>>> p(option_curve=m)
8.849422...
PUT_TYPES = ('put', 'floor')
CALL_TYPES = ('call', 'cap')
OPTION_TYPES = {'call': 'call', 'cap': 'cap', 'floor': 'floor', 'put': 'put'}
details(valuation_date=None, *, forward_curve=None, option_curve=None, **__)[source]
Parameters:
  • valuation_date

  • forward_curve

  • option_curve

  • __

Returns:

CashFlow List

class dcf.payoffs.CashFlowList(iterable=(), /)[source]

Bases: TSList

cashflow payoff container

cashflow payoff container

Parameters:

iterable

classmethod from_fixed_cashflows(payment_date_list, amount_list=1.0, forward_curve=None)[source]

basic cashflow list object

Parameters:
  • payment_date_list – list of cashflow payment dates

  • amount_list – list of cashflow amounts

  • forward_curve – curve to derive forward values

classmethod from_rate_cashflows(payment_date_list, amount_list=1.0, origin=None, day_count=None, fixing_offset=None, pay_offset=None, fixed_rate=None, forward_curve=None)[source]

list of interest rate cashflows

Parameters:
  • payment_date_list – pay dates, assuming that pay dates agree with end dates of interest accrued period

  • amount_list – notional amounts

  • origin – start date of first interest accrued period

  • day_count – day count convention

  • fixing_offset – time difference between interest rate fixing date and interest period payment date

  • pay_offset – time difference between interest period end date and interest payment date

  • fixed_rate – agreed fixed rate

  • forward_curve – interest rate curve for forward estimation

Let \(t_0\) be the list origin and \(t_i\) \(i=1, \dots n\) the payment_date_list with \(N_i\) \(i=1, \dots n\) the notional amount_list.

Moreover, let \(\tau\) be the day_count function, \(c\) the fixed_rate and \(f\) the forward_curve.

Then, the rate cashflow \(cf_i\) payed at time \(t_i\) will be with \(s_i = t_{i-1} - \delta\), \(e_i = t_i -\delta\) as well as \(d_i = s_i - \epsilon\) for pay_offset \(\delta\) and fixing_offset \(\epsilon\),

\[cf_i = N_i \cdot \tau(s_i,e_i) \cdot (c + f(d_i)).\]

Note, the pay_offset \(\delta\) is not applied in case of the first cashflow, then \(s_1=t_0\).

classmethod from_option_cashflows(payment_date_list, amount_list=1.0, strike_list=None, option_type='call', is_digital=False, fixing_offset=None, pay_offset=None, forward_curve=None)[source]

list of European option payoffs

Parameters:
  • payment_date_list – list of cashflow payment dates \(t_k\)

  • amount_list – list of option notional amounts \(N_k\)

  • strike_list – list of option strike prices \(K_k\)

  • option_type – enum to prick for option type call, put, cap, floor (optional with default call)

  • is_digital – bool flag if option is digital/binary option (optional with default False)

  • fixing_offset – offset \(\delta\) between underlying fixing date and cashflow end date

  • pay_offset – offset \(\epsilon\) between cashflow end date and payment date

  • forward_curve – curve to derive underlying forward value

List of dcf.payoffs.OptionCashFlowPayOff or dcf.payoffs.DigitalOptionCashFlowPayOff.

classmethod from_contingent_rate_cashflows(payment_date_list, amount_list=1.0, origin=None, day_count=None, fixing_offset=None, pay_offset=None, fixed_rate=None, cap_strike=None, floor_strike=None, forward_curve=None)[source]

list of contingent collared rate cashflows

Parameters:
  • payment_date_list – pay dates, assuming that pay dates agree with end dates of interest accrued period

  • amount_list – notional amounts

  • origin – start date of first interest accrued period

  • day_count – day count convention

  • fixing_offset – time difference between interest rate fixing date and interest period payment date

  • pay_offset – time difference between interest period end date and interest payment date

  • fixed_rate – agreed fixed rate

  • cap_strike – upper interest rate boundary \(L\)

  • floor_strike – lower interest rate boundary \(K\)

  • forward_curve – curve to derive underlying forward value

Each object consists of a list of dcf.payoffs.RateCashFlowPayOff followed eventually by dcf.payoffs.OptionCashFlowPayOff for any gievn floor_strike and/or cap_strike, i.e. to add up to a collared payoff functions.

\[X_i(f(T_i)) = [\max(K, \min(f(T_i), L)) + c]\ \tau(s,e)\ N\]

with, according to a payment date \(p_i\), \(p_i-\epsilon=e_i\), \(e_i=s_{i+1}\) and \(s_i-\delta=T_i\).

property domain

payment date list

property origin

cashflow list start date

property fixed_rate
details(valuation_date=None, **__)[source]
dataframe()[source]

Valuation Routines

Expected Payoff

dcf.pricer.ecf(cashflow_list: CashFlowPayOff | CashFlowList, valuation_date: Any, *, forward_curve: Callable | float | dict | None = None)[source]

expected cashflow payoffs

Parameters:
  • cashflow_list – list of cashflows

  • valuation_date – date to discount to

  • forward_curve – payoff model (optional; default: None, i.e. model attached to cashflow_list)

Returns:

dict of expected cashflow payoffs with pay_date keys

>>> from dcf import ecf, CashFlowList
>>> cf_list = CashFlowList.from_fixed_cashflows([0., 3.], amount_list=[-100., 100.])
>>> cf_list += CashFlowList.from_rate_cashflows([0., 1., 2., 3.], amount_list=100., fixed_rate=0.05)
>>> ecf(cf_list, valuation_date=0.0)
{0.0: -95.0, 1.0: 5.0, 2.0: 5.0, 3.0: 105.0}
>>> import dcf
>>> dcf.pricer.INCLUDE_VALUATION_DATE = False
>>> ecf(cf_list, valuation_date=0.0)
{1.0: 5.0, 2.0: 5.0, 3.0: 105.0}

Present Value

dcf.pricer.pv(cashflow_list: CashFlowPayOff | CashFlowList, valuation_date: Any | None = None, discount_curve: Callable | float = 0.0, *, forward_curve: Callable | float | dict | None = None)[source]

calculates the present value by discounting cashflows

Parameters:
  • cashflow_list – list of cashflows

  • valuation_date – date to discount to

  • discount_curve – discount factors are obtained from this curve

  • forward_curve – payoff model (optional; default: None, i.e. model attached to cashflow_list)

Returns:

float - as the sum of all discounted future cashflows

Let \(cf_1 \dots cf_n\) be the list of cashflows with payment dates \(t_1, \dots, t_n\).

Moreover, let \(t\) be the valuation date and \(T=\{t_i \mid t \leq t_i \}\).

Then the present value is given as

\[v(t) = \sum_{t_i \in T} df(t, t_i) \cdot cf_i\]

with \(df(t, t_i)\), the discount factor discounting form \(t_i\) to \(t\).

Note, get_present_value includes cashflows at valuation date. Therefor it represents a start-of-day valuation than a end-of-day valuation.

>>> from yieldcurves import YieldCurve
>>> from dcf import ecf, pv, CashFlowList
>>> import dcf
>>> curve = YieldCurve.from_interpolation([0.0], [0.05])
>>> cf_list = CashFlowList.from_fixed_cashflows([0, 1, 2, 3], [100, 100, 100, 100])
>>> dcf.pricer.INCLUDE_VALUATION_DATE = True
>>> sod = pv(cf_list, 0.0, discount_curve=curve)
>>> sod
371.677...
>>> eod = sod - ecf(cf_list, 0.0)[0.0]
>>> eod
271.677...
>>> dcf.pricer.INCLUDE_VALUATION_DATE = False
>>> pv(cf_list, 0.0, discount_curve=curve)
271.677...

Interest Accrued

dcf.pricer.iac(cashflow_list: CashFlowList, valuation_date: Any, *, forward_curve: Callable | float | dict | None = None)[source]

calculates interest accrued for rate cashflows

param cashflow_list:

requires a day_count property

param valuation_date:

calculation date

param forward_curve:

payoff model (optional; default: None, i.e. model attached to cashflow_list)

return:

float - proportion of interest in current interest period

Let \(t\) be the valuation date and \(s, e\) start resp. end date of current rate period, i.e. \(s \leq t < e\).

Let \(\tau\) be the day count function to calculate year fractions.

Finally, let \(cf\) be the next interest rate cashflow.

The accrued interest until \(t\) is given as

\[cf_{accrued} = cf \cdot \frac{\tau(s, t)}{\tau(s, e)}.\]

Note, this function takes even expected payoffs of options incl. caplets and floorlets into account which probably should be excluded.

Example

>>> from dcf import iac, CashFlowList

setup 5y coupon bond

>>> n = 1_000_000
>>> coupon_leg = CashFlowList.from_rate_cashflows([1.,2.,3.,4.,5.], amount_list=n, origin=0., fixed_rate=0.001)
>>> redemption_leg = CashFlowList.from_fixed_cashflows([5.], amount_list=n)
>>> bond = coupon_leg + redemption_leg

bond with cashflow tables

>>> print(coupon_leg)
  pay date    cashflow    notional  is rec      fixed rate    start date    end date    year fraction
----------  ----------  ----------  --------  ------------  ------------  ----------  ---------------
       1.0     1_000.0   1_000_000  True             0.001           0.0         1.0              1.0
       2.0     1_000.0   1_000_000  True             0.001           1.0         2.0              1.0
       3.0     1_000.0   1_000_000  True             0.001           2.0         3.0              1.0
       4.0     1_000.0   1_000_000  True             0.001           3.0         4.0              1.0
       5.0     1_000.0   1_000_000  True             0.001           4.0         5.0              1.0
>>> print(redemption_leg)
  pay date     cashflow
----------  -----------
       5.0  1_000_000.0
>>> iac(bond, valuation_date=3.25)
250.0
>>> iac(bond, valuation_date=3.5)
500.0
>>> iac(bond, valuation_date=4.5)
500.0
>>> # doesn't take fixed cashflows into account
>>> iac(redemption_leg, valuation_date=3.25)
0.0

Yield To Maturity

dcf.pricer.ytm(cashflow_list: CashFlowList, valuation_date: Any, *, forward_curve: Callable | float | dict | None = None, present_value: float = 0.0, compounding_frequency: int | None = None, method: str | Callable = 'secant_method', **kwargs)[source]

yield-to-maturity or effective interest rate

Parameters:
  • cashflow_list – list of cashflows

  • valuation_date – date to discount to (optional; default: cashflow_list.origin)

  • forward_curve – payoff model (optional; default: None, i.e. model attached to cashflow_list)

  • present_value – price to meet by discounting (optional; default: 0.0)

  • compounding_frequency – compounding frequency (optional; default: None, i.e. continous compounding)

  • method – solver method If given as string invokes a method from curves.numerics # noqa E501 otherwise method should be a solver impelementing method(err, **kwargs) return float result and where err is the error function to be solved. kwargs provide arguments for method. (optional: default is secant_method with lower and upper guess of 0.01 and 0.05 and tolerance of 1e-10)

  • args – arguments for method

  • kwargs – keyword arguments for method

Returns:

float - as flat interest rate to discount all future cashflows in order to meet given present_value

Let \(cf_1 \dots cf_n\) be the list of cashflows with payment dates \(t_1, \dots, t_n\).

Moreover, let \(t\) be the valuation date and \(T=\{t_i \mid t \leq t_i \}\).

Then the yield-to-maturity is the interest rate \(y\) such that the present_value \(\hat{v}\) is given as

\[\hat{v} = \sum_{t_i \in T} df(t, t_i) \cdot cf_i\]

with the discount factor either \(df(t, t_i) = \exp(-y \cdot (t_i-t))\) or \(\frac{1}{(1 + y / \tau)^{\tau \cdot (t_i-t)}}\), depending on compounding_frequency \(\tau\), discounting from \(t_i\) to \(t\).

Example

yield-to-matrurity of 5y fixed coupon bond

>>> from yieldcurves import YieldCurve
>>> from dcf import CashFlowList
>>> from dcf import pv, ytm
>>> curve = YieldCurve.from_interpolation([0.0], [0.05]).df
>>> n = 1_000_000
>>> coupon_leg = CashFlowList.from_rate_cashflows([1.,2.,3.,4.,5.], amount_list=n, origin=0.0, fixed_rate=0.001)
>>> redemption_leg = CashFlowList.from_fixed_cashflows([5.], amount_list=n)
>>> bond = coupon_leg + redemption_leg

bond with cashflow tables

>>> print(coupon_leg)
  pay date    cashflow    notional  is rec      fixed rate    start date    end date    year fraction
----------  ----------  ----------  --------  ------------  ------------  ----------  ---------------
       1.0     1_000.0   1_000_000  True             0.001           0.0         1.0              1.0
       2.0     1_000.0   1_000_000  True             0.001           1.0         2.0              1.0
       3.0     1_000.0   1_000_000  True             0.001           2.0         3.0              1.0
       4.0     1_000.0   1_000_000  True             0.001           3.0         4.0              1.0
       5.0     1_000.0   1_000_000  True             0.001           4.0         5.0              1.0
>>> print(redemption_leg)
  pay date     cashflow
----------  -----------
       5.0  1_000_000.0

get yield-to-maturity at par (gives coupon rate)

>>> ytm(bond, 0.0, present_value=n)
0.0009...

get current yield-to-maturity as given by 1.5% risk free rate (gives risk free rate)

>>> present_value = pv(bond, 0.0, curve)
>>> present_value
783115.0894...
>>> ytm(bond, 0.0, present_value=present_value)
0.049999...

Fair Rate

dcf.pricer.fair(cashflow_list: CashFlowList, valuation_date: Any | None = None, discount_curve: Callable | float = 0.0, *, forward_curve: Callable | float | dict | None = None, present_value: float = 0.0, method: str | Callable = 'secant_method', **kwargs)[source]

coupon rate to meet given value

Parameters:
  • cashflow_list – list of cashflows

  • discount_curve – discount factors are obtained from this curve

  • valuation_date – date to discount to

  • forward_curve – payoff model (optional; default: None, i.e. model attached to cashflow_list)

  • present_value – price to meet by discounting (optional: default: 0.0)

  • method – solver method If given as string invokes a method from curves.numerics otherwise method should be a solver impolementing method(err, **kwargs) return float result and where err is the error function to be solved. kwargs provide arguments for method. (optional: default is secant_method with lower and upper guess of 0.01 and 0.05 and tolerance of 1e-10)

  • args – arguments for method

  • kwargs – keyword arguments for method

Returns:

float - the fair coupon rate as fixed_rate of a dcf.payoffs.RateCashFlowPayOff

Let \(cf_i(c) = N_i \cdot \tau(s_i,e_i) \cdot (c + f(d_i))\) be the \(i\)-th cashflow in the cashflow_list.

Here, the fair rate is the fixed_rate \(c=\hat{c}\) such that the present_value \(\hat{v}\) is given as

\[\hat{v} = \sum_{t_i \in T} df(t, t_i) \cdot cf_i(\hat{c})\]

with \(df(t, t_i)\), the discount factor discounting form \(t_i\) to \(t\).

Note, get_fair_rate requires the cashflow_list to have an attribute fixed_rate which is perturbed to find the solution for \(\hat{c}\).

Example

>>> from yieldcurves import YieldCurve
>>> from dcf import pv, fair, CashFlowList

setup 5y coupon bond

>>> curve = YieldCurve.from_interpolation([0.], [0.015]).df
>>> n = 1_000_000
>>> coupon_leg = CashFlowList.from_rate_cashflows([1.,2.,3.,4.,5.], amount_list=n, origin=0., fixed_rate=0.001)
>>> redemption_leg = CashFlowList.from_fixed_cashflows([5.], amount_list=n)
>>> bond = coupon_leg + redemption_leg

find fair rate to give par bond

>>> present_value = pv(redemption_leg, 0.0, curve)
>>> fair_rate = fair(coupon_leg, 0.0, curve, present_value=n-present_value)
>>> fair_rate
0.0151...

check it’s a par bond (pv=notional)

>>> for cf in coupon_leg:
...     cf.fixed_rate = fair_rate
>>> pv(bond, 0.0, curve)
1000000.0...

Basis Point Value

dcf.pricer.bpv(cashflow_list: CashFlowList, valuation_date: Any | None = None, discount_curve: Callable | float = 0.0, *, forward_curve: Callable | float | dict | None = None, delta_curve: Callable | Iterable[Callable] | None = None, shift: float = 0.0001)[source]

basis point value (bpv), i.e. value change by one interest rate shifted one basis point

Parameters:
  • cashflow_list – list of cashflows

  • discount_curve – discount factors are obtained from this curve

  • valuation_date – date to discount to

  • forward_curve – payoff model (optional; default: model attached to cashflow_list)

  • delta_curve – curve (or list of curves) which will be shifted (optional; default: default_curve)

  • shift – shift size to derive bpv (optional; default: 0.0001)

Returns:

float - basis point value (bpv)

Let \(v(t, r)\) be the present value of the given cashflow_list depending on interest rate curve \(r\) which can be used as forward curve to estimate float rates or as zero rate curve to derive discount factors (or both).

Then, with shift_size \(s\), the bpv is given as

\[\Delta(t) = 0.0001 \cdot \frac{v(t, r + s) - v(t, r)}{s}\]

Example

>>> from yieldcurves import YieldCurve
>>> from dcf import pv, bpv, CashFlowList

setup 5y coupon bond

>>> n = 1_000_000
>>> coupon_leg = CashFlowList.from_rate_cashflows([1.,2.,3.,4.,5.], amount_list=n, origin=0., fixed_rate=0.001)
>>> redemption_leg = CashFlowList.from_fixed_cashflows([5.], amount_list=n)
>>> bond = coupon_leg + redemption_leg

together with a flat yield curve

>>> curve = YieldCurve(0.015)
>>> pv(bond, 0.0, curve.df)
932524.5493...

calculate bpv as bond delta

>>> yc = YieldCurve(curve)
>>> bpv(bond, 0.0, yc.df, delta_curve=yc.curve)
-465.1755...

double check by direct valuation

>>> present_value = pv(bond, 0.0, curve.df)
>>> shifted = YieldCurve(0.015 + 0.0001)
>>> pv(bond, 0.0, shifted.df) - present_value
-465.1755...

Bucketed Delta

dcf.pricer.delta(cashflow_list: CashFlowList, valuation_date: Any | None = None, discount_curve: Callable | float = 0.0, *, forward_curve: Callable | float | dict | None = None, delta_curve: Callable | Iterable[Callable] | None = None, delta_grid: Iterable[Any] | None = None, shift: float = 0.0001)[source]

list of bpv delta for partly (bucketed) shifted interest rate curve

Parameters:
  • cashflow_list – list of cashflows

  • discount_curve – discount factors are obtained from this curve

  • valuation_date – date to discount to (optional; default is discount_curve.origin)

  • forward_curve – payoff model (optional; default: model attached to cashflow_list)

  • delta_curve – curve (or list of curves) which will be shifted (optional; default is discount_curve)

  • delta_grid – grid dates to build partly shifts (optional; default is delta_curve.domain)

  • shift – shift size to derive bpv (optional: default is a basis point i.e. 0.0001)

Returns:

list(float) - basis point value for each delta_grid point

Let \(v(t, r)\) be the present value of the given cashflow_list depending on interest rate curve \(r\) which can be used as forward curve to estimate float rates or as zero rate curve to derive discount factors (or both).

Then, with shift_size \(s\) and shifting \(s_j\),

\[\Delta_j(t) = 0.0001 \cdot \frac{v(t, r + s_j) - v(t, r)}{s}\]

and the full bucketed delta vector is \(\big(\Delta_1(t), \Delta_2(t), \dots, \Delta_{m-1}(t) \Delta_m(t)\big)\).

Overall the shifting \(s_1, \dots s_n\) is a partition of the unity, i.e. \(\sum_{j=1}^m s_j = s\).

Each \(s_j\) for \(i=2, \dots, m-1\) is a function of the form of an triangle, i.e. for a delta_grid \(t_1, \dots, t_m\)

\[ s_j(t) = \left\{ \begin{array}{cl} 0 & \text{ for } t < t_{j-1} \\ s \cdot \frac{t-t_{j-1}}{t_j-t_{j-1}} & \text{ for } t_{j-1} \leq t < t_j \\ s \cdot \frac{t_{j+1}-t}{t_{j+1}-t_j} & \text{ for } t_j \leq t < t_{j+1} \\ 0 & \text{ for } t_{j+1} \leq t \\ \end{array} \right. \]

while

\[ s_1(t) = \left\{ \begin{array}{cl} s & \text{ for } t < t_1 \\ s \cdot \frac{t_2-t}{t_2-t_1} & \text{ for } t_1 \leq t < t_2 \\ 0 & \text{ for } t_2 \leq t \\ \end{array} \right. \]

and

\[ s_m(t) = \left\{ \begin{array}{cl} 0 & \text{ for } t < t_{m-1} \\ s \cdot \frac{t-t_{m-1}}{t_m-t_{m-1}} & \text{ for } t_{m-1} \leq t < t_m \\ s & \text{ for } t_m \leq t \\ \end{array} \right. \]

Example

same example as dcf.pricer.bpv() but with buckets

>>> from yieldcurves import YieldCurve
>>> from dcf import pv, bpv, delta, CashFlowList

setup 5y coupon bond

>>> curve = YieldCurve.from_interpolation([0.,1.,2.,3.,4.,5.], [0.01, 0.011, 0.014, 0.012, 0.01, 0.013])
>>> n = 1_000_000
>>> coupon_leg = CashFlowList.from_rate_cashflows([1.,2.,3.,4.,5.], amount_list=n, origin=0., fixed_rate=0.001)
>>> redemption_leg = CashFlowList.from_fixed_cashflows([5.], amount_list=n)
>>> bond = coupon_leg + redemption_leg

calculate bpv as bond delta

>>> yc = YieldCurve(curve)
>>> bucked_bpv = delta(bond, 0.0, yc.df, delta_curve=yc.curve, delta_grid=[0.,1.,2.,3.,4.,5.])
>>> bucked_bpv
(0.0, -0.098901..., -0.194458..., -0.289348..., -0.384238..., -468.885034...)

check by summing up (should give flat bpv)

>>> sum(bucked_bpv)
-469.851981...
>>> bpv(bond, 0.0, yc.df, delta_curve=yc.curve)
-469.851981...

Curve Bootstrapping

class dcf.pricer.fit(cashflow_list: Iterable[CashFlowList], valuation_date: Any | None = None, discount_curve: Callable | float = 0.0, *, forward_curve: Callable | float | dict | None = None, price_list: Iterable[float] | None = None, fitting_curve: Callable | None = None, fitting_grid: Iterable[float] | None = None, interpolation_type: str | Callable | None = None, method: str | Callable = 'secant_method', **kwargs)[source]

Bases:

fit interpolated curve to prices

Parameters:
  • cashflow_list – list of cashflows instruments, i.e. list of lists of cashflows

  • valuation_date – date to discount to

  • discount_curve – discount factors are obtained from this curve

  • forward_curve – payoff model (optional; default: None, i.e. model attached to cashflow_list)

  • price_list – list of prices to match (optional; default assumes all prices to be 0.0)

  • fitting_curve – curve to fit by inplace adding another curve, e.g. see curves.Curve() (otional; default is a yield curve derived from discount_curve)

  • fitting_grid – list of year fractions (float) which define the interpolation grid (optional; default: year fraction to last pay date of cashflow list in cashflow_list. to caluculate year fraction either discount_curve or dcf.daycount.day_count() is used.)

  • interpolation_type – function used for interpolation (optional; default: piecewise_linear as defined in yieldcurves.interpolation package, i.e. constant extrapolation and linear interpolation)

  • method – root finding method, for details see yieldcurves.tools.numerics. (optional; default: secant_method)

  • bounds – inital or bounday values (depending on method) for details see yieldcurves.tools.numerics. (optional; default: (-0.1, 0.2))

  • tolerance – zero tolerance for details see yieldcurves.tools.numerics. (optional; default: 1e-10)

Returns:

dictionary of curve point and value

dcf.pricer.fit() uses the fit function in yieldcurves.interpolation.

Example (with Year Fractions)

>>> from dcf import pv, fit, CashFlowList
>>> from yieldcurves import YieldCurve, DateCurve

build cashflow instruments

>>> today = 0.0
>>> schedule = [1., 2., 3., 4., 5. ]
>>> cashflow_list = []
>>> for d in schedule:
...     pay_dates = [s for s in schedule if s <= d]
...     cashflow_list.append(CashFlowList.from_fixed_cashflows(pay_dates))

setup curve to derive target values and generate data for calibration

>>> curve = YieldCurve.from_interpolation(schedule, [0.01, 0.009, 0.012, 0.014, 0.011])
>>> targets = [pv(c, 0.0, curve.df) for c in cashflow_list]  # target values to match

invoke curve fitting

>>> fit(cashflow_list, today, 1.0, price_list=targets)
{1.0: 0.009999999999999995, 2.0: 0.00900000082473929, 3.0: 0.011999999441079148, 4.0: 0.01400000004983385, 5.0: 0.010999999964484619}

or

>>> yc = YieldCurve(0.0)  # curve to calibrate to
>>> rates = fit(cashflow_list, today, yc.df, price_list=targets)  # curve fitting
>>> rates
{1.0: 0.009999999999999995, 2.0: 0.00900000082473929, 3.0: 0.011999999441079148, 4.0: 0.01400000004983385, 5.0: 0.010999999964484619}

setup new curve

>>> yc2 = YieldCurve.from_interpolation(rates.keys(), rates.values())
>>> yc2
YieldCurve(piecewise_linear([1.0, 2.0, 3.0, 4.0, 5.0], [0.009999999999999995, 0.00900000082473929, 0.011999999441079148, 0.01400000004983385, 0.010999999964484619]))

double check results

>>> err = [abs(pv(cf, 0.0, yc2.df) - v) for cf, v in zip(cashflow_list, targets)]
>>> max(err) < 1e-7
True

The above is acctually the same as

>>> yc = DateCurve(YieldCurve(0.0), origin=0.0)
>>> grid = [yc.year_fraction(max(cf.domain)) for cf in cashflow_list]
>>> fit(cashflow_list, today, yc.df, price_list=targets, fitting_curve=yc.curve.curve, fitting_grid=grid)
{1.0: 0.009999999999999995, 2.0: 0.00900000082473929, 3.0: 0.011999999441079148, 4.0: 0.01400000004983385, 5.0: 0.010999999964484619}

Example (with BusinessDate())

>>> from businessdate import BusinessDate, BusinessSchedule

build cashflow instruments

>>> today = BusinessDate(20240101)
>>> schedule = BusinessSchedule(today + '1y', today + '5y', step='1y')
>>> cashflow_list = []
>>> for i, d in enumerate(schedule):
...     pay_dates = [s for s in schedule if s <= d]
...     cashflow_list.append(CashFlowList.from_fixed_cashflows(pay_dates))

setup curve to derive target values and generate data for calibration

>>> curve = DateCurve(YieldCurve.from_interpolation(schedule, [0.01, 0.009, 0.012, 0.014, 0.011]), origin=today)
>>> targets = [pv(c, today, curve.df) for c in cashflow_list]

invoke curve fitting

>>> yc = DateCurve(YieldCurve(0.0), origin=today)
>>> fit(cashflow_list, today, yc.df, price_list=targets)
{1.002053388090349: 0.009747946614987986, 2.001368925393566: 0.01249726670743294, 3.0006844626967832: 0.013256157081358156, 4.0: 0.010999999998261295, 5.002053388090349: 0.011000000004102856}

The above is acctually the same as

>>> yc = DateCurve(YieldCurve(0.0), origin=today)
>>> grid = [yc.year_fraction(max(cf.domain)) for cf in cashflow_list]
>>> fit(cashflow_list, today, yc.df, price_list=targets, fitting_curve=yc.curve.curve, fitting_grid=grid)
{1.002053388090349: 0.009747946614987986, 2.001368925393566: 0.01249726670743294, 3.0006844626967832: 0.013256157081358156, 4.0: 0.010999999998261295, 5.002053388090349: 0.011000000004102856}

Fundamentals

Build Functions

dcf.plans.same(num, amount=1.0)[source]

all same payment plan

Parameters:
  • num – number of payments \(n\)

  • amount – amount of each payment \(N\)

Returns:

list(float) payment plan \(X_i\) for \(i=1 \dots n\)

Payment plan with

\[X_i = N \text{ for all } i=1 \dots n\]

dcf.plans.bullet(num, amount=1.0)[source]

bullet payment plan

Parameters:
  • num – number of payments \(n\)

  • amount – amount of last bullet payment \(N\)

Returns:

list(float) payment plan \(X_i\) for \(i=1 \dots n\)

Payment plan with

\[X_i = N \text{ for } i=n \text{ else } 0\]

dcf.plans.amortize(num, amount=1.0)[source]

linear amortize payment plan

Parameters:
  • num – number of payments \(n\)

  • amount – amount of total sum of payment \(N\)

Returns:

list(float) payment plan \(X_i\) for \(i=1 \dots n\)

Payment plan with

\[X_i = N/n \text{ for } i=1 \dots n\]

dcf.plans.consumer(num, amount=1.0, fixed_rate=0.01)[source]

consumer loan annuity payment plan

Parameters:
  • num – number of payments \(n\)

  • amount – amount of payment total \(N\)

  • fixed_rate – amortization rate \(r\)

Returns:

list(float) payment plan \(X_i\) for \(i=1 \dots n\)

Actutal payment plan total \(T = N (1 + n \cdot r)\) such that

\[X_i = T / n\]

dcf.plans.iam(num, amount=1.0, fixed_rate=0.01)[source]
dcf.plans.outstanding(plan, amount=1.0, sign=False)[source]

sums up plans to remaining oustanding anmount

Parameters:
  • plan – payment plan \(X_i\)

  • amount – inital amount \(N\)

  • sign\(\sigma\) sign of plan payments (optional, default: -1)

Returns:

list(float) outstanding plan

Adds or substracts payment plan payments \(X_i\) from inital amount \(N\) such that

\[O_i = N + \sigma \cdot \sum_{k=1}^{i-1} X_i\]

dcf.plans.annuity(num=None, amount=None, fixed_rate=None, *, redemption_rate=None, annuity_amount=None)[source]

DayCount

dcf.daycount.origin(curve, default=0.0)[source]
dcf.daycount.year_fraction(curve, default=None)[source]