Installation

pip install bond_pricing

For installation without pulling in scipy as a dependency, see below

The source code is at https://github.com/jrvarma/bond_pricing if you want to go that route.

Overview

This package provides bond pricing functions as well as basic NPV/IRR functions. Bond valuation can be done using an yield to maturity or using a zero yield curve. There is a convenience function to construct a zero yield curve from a few points on the par bond or zero yield curve or from Nelson Siegel parameters.

The documentation is available at https://bond-pricing.readthedocs.io/

The bond valuation functions can be used in two modes:

  • The first mode is similar to spreadsheet bond pricing functions. The settlement date and maturity date are given as dates and the software calculates the time to maturity and to each coupon payment date from these dates. For any daycount other than simple counting of days (ACT/365 in ISDA terminology), this packages relies on the isda_daycounters module that can be downloaded from https://github.com/miradulo/isda_daycounters

  • Maturity can be given in years (the settle parameter is set to None and is assumed to be time 0) and there are no dates at all. This mode is particularly convenient to price par bonds or price other bonds on issue date or coupon dates. For example, finding the price of a 7 year 3.5% coupon bond if the prevailing yield is 3.65% is easier in this mode as the maturity is simply given as 7.0 instead of providing a maturity date and specifying today’s date. Using this mode between coupon dates is not so easy as the user has to basically compute the day count and year fraction and provide the maturity as say 6.7 years.

  • Bond Valuation

    • Bond price using YTM (bond_price) or using zero yield curve (zero_curve_bond_price)

    • Accrued interest and dirty bond prices using YTM (bond_price_breakup) or using zero yield curve (zero_curve_bond_price_breakup)

    • Duration using YTM (bond_duration)

    • Yield to maturity (bond_yield).

  • Zero curve construction

    • bootstrap zero yields from par yields (par_yld_to_zero) or vice versa (zero_to_par)

    • compute zero rates from Nelson Siegel parameters (nelson_siegel_zero_rate)

    • construct zero prices from par or zero yields at selected knot points using a cubic spline or assuming a flat yield curve (make_zero_price_fun)

  • Present Value functions

    • Net Present Value (npv)

    • Internal Rate of Return (irr)

    • Duration (duration). These functions allow different compounding frequencies: for example, the cash flows may be monthly while the interest rate is semi-annually compounded. The function equiv_rate converts between different compounding frequencies.

  • Annuity functions

    • Annuity present value (annuity_pv)

    • Future value (annuity_fv)

    • Implied interest rate (annuity_rate)

    • Number of periods to achieve given present value or terminal value (annuity_periods).

    • Periodic instalment to achieve given present value or terminal value (annuity_instalment).

    • Breakup of instalment into principal and interest (annuity_instalment_breakup)

    In these functions also, the cash flow frequency may be different from the compounding frequency.

Reducing Dependencies

This module requires numpy, pandas and scipy. In some environments, installing scipy may be difficult, and only a couple of functions (the newton root finder and CubicSpline interpolation) are actually needed from the huge scipy package. So a provision has been made to avoid scipy with some loss of functionality (the newton root finder is replaced by a less sophisticated root bracketing and bisection algorithm and CubicSpline interpolation is replaced by the much cruder linear interpolation). At run time, the module checks for the availability of scipy and uses the cruder methods (with a suitable warning) if scipy is not available.

To install this package without pulling in scipy as a dependency, do the following:

git clone https://github.com/jrvarma/bond_pricing.git
export no_scipy=1
pip install bond_pricing

Bond Valuation using YTM

Bond Valuation functions

Important functions are bond_price, bond_yield and bond_duration for computing the price, yield to maturity and duration of one or more bonds.

The settlement date and maturity date can be given as dates and the software calculates the time to maturity (and coupon dates) using a daycount convention to calculate year_fractions. For any daycount other than simple counting of days (ACT/365 in ISDA terminology), this packages relies on the isda_daycounters module that can be downloaded from <https://github.com/miradulo/isda_daycounters>

Maturity can alternatively be given in years by setting settle to None. The trade/settlement date is then year 0.

bond_pricing.simple_bonds.bond_coupon_periods(settle=None, mat=1, freq=2, daycount=None, return_dataframe=False)

Compute no of coupon, coupon dates and fractions

Parameters:
  • settle (date or None) – The settlement date. None means maturity is in years.

  • mat (int or date) – Maturity date or if settle is None, maturity in years

  • freq (int) – Coupon frequency

  • daycount (daycounter) – This class has day_count and year_fraction methods

  • return_dataframe (bool) – whether to return pandas DataFrame instead of dict

Returns:

n : No of coupons left

discounting_fraction: Fraction of coupon period to next coupon

accrual_fraction: Fraction of coupon period to previous coupon

next_coupon: Next coupon date (None if settle is None)

prev_coupon Previous coupon date (None if settle is None)

Return type:

dict

Examples

>>> bond_coupon_periods(
... settle='2020-03-13', mat='2030-01-01', freq=2, daycount=None
... )  
{'n': 20,
'discounting_fraction': 0.6,
'accrual_fraction': 0.4,
'next_coupon': Timestamp('2020-07-01 00:00:00'),
'prev_coupon': Timestamp('2020-01-01 00:00:00')}
>>> bond_coupon_periods(
... mat=10.125, freq=2, daycount=None
... )  
{'n': 21.0,
'discounting_fraction': 0.25,
'accrual_fraction': 0.75,
'next_coupon': None,
'prev_coupon': None}
bond_pricing.simple_bonds.bond_duration(settle=None, cpn=0, mat=1, yld=0, freq=2, comp_freq=None, face=100, redeem=None, daycount=None, modified=False)
Parameters:
  • settle (date or None) – The settlement date. None means maturity is in years.

  • cpn (float) – The coupon rate in decimal

  • mat (float or date) – Maturity date or if settle is None, maturity in years

  • yld (float) – The yield to maturity in decimal

  • freq (int) – Coupon frequency

  • comp_freq (int) – Compounding frequency

  • face (float) – Face value of the bond

  • redeem (float) – Redemption value

  • daycount (daycounter) – This class has day_count and year_fraction methods

  • modified (bool) – Whether to return modified duration

Returns:

The duration (modified duration if modified is True)

Return type:

float

Examples

>>> bond_duration(settle="2012-04-15", mat="2022-01-01", cpn=8e-2,
...               yld=8.8843e-2)
6.678708669753968
>>> bond_duration(settle="2012-04-15", mat="2022-01-01", cpn=8e-2,
...               yld=8.8843e-2, modified=True)
6.394648779016871
>>> bond_duration(settle="2012-04-15", mat="2022-01-01", cpn=8e-2,
...               yld=[7e-2, 8.8843e-2])
array([6.88872548, 6.67870867])
bond_pricing.simple_bonds.bond_price(settle=None, cpn=0, mat=1, yld=0, freq=2, comp_freq=None, face=100, redeem=None, daycount=None)

Compute clean price of coupon bond using YTM

This is a wrapper for bond_price_breakup that extracts and returns only the clean price.

Parameters:
  • settle (date or None) – The settlement date. None means maturity is in years.

  • cpn (float) – The coupon rate in decimal

  • mat (float or date) – Maturity date or if settle is None, maturity in years

  • yld (float) – The yield to maturity in decimal

  • freq (int) – Coupon frequency

  • comp_freq (int) – Compounding frequency

  • face (float) – Face value of the bond

  • redeem (float) – Redemption value

  • daycount (daycounter) – This class has day_count and year_fraction methods

Returns:

clean price of the bond

Return type:

float

Examples

>>> bond_price(settle="2012-04-15", mat="2022-01-01", cpn=8e-2,
... yld=8.8843e-2, freq=1)
94.33211715988098
>>> bond_price(mat=10.25, cpn=8e-2, yld=9e-2, freq=2)
93.37373582338677
>>> bond_price(settle="2012-04-15", mat="2022-01-01", cpn=8e-2,
...            yld=8.8843e-2,freq=[1, 2, 4])
array([94.33211715988098, 94.30449490379667, 94.28377717535678],
      dtype=object)
>>> bond_price(settle = "2021-01-01", mat = "2031-01-01",
...            yld = 1e-2, freq = 2, cpn = 5e-2)
137.9748382933389
bond_pricing.simple_bonds.bond_price_breakup(settle=None, cpn=0, mat=1, yld=0, freq=2, comp_freq=None, face=100, redeem=None, daycount=None, return_dataframe=False)

Compute clean/dirty price & accrued_interest of coupon bond using YTM

Parameters:
  • settle (date or None) – The settlement date. None means maturity is in years.

  • cpn (float) – The coupon rate in decimal

  • mat (float or date) – Maturity date or if settle is None, maturity in years

  • yld (float) – The yield to maturity in decimal

  • freq (int) – Coupon frequency

  • comp_freq (int) – Compounding frequency

  • face (float) – Face value of the bond

  • redeem (float) – Redemption value

  • daycount (daycounter) – This class has day_count and year_fraction methods

  • return_dataframe (bool) – whether to return pandas DataFrame instead of dict

Returns:

dirty: dirty price of the bond

accrued: accrued interest

clean: clean price of the bond

next_coupon: Next coupon date (only if settle is None)

prev_coupon: Previous coupon date (only if settle is None)

Return type:

dict

Examples

>>> bond_price_breakup(
... settle="2012-04-15", mat="2022-01-01", cpn=8e-2, yld=8.8843e-2,
... freq=1)  
{'DirtyPrice': 96.64322827099208,
'AccruedInterest': 2.311111111111111,
'CleanPrice': 94.33211715988098,
'NextCoupon': Timestamp('2013-01-01 00:00:00'),
'PreviousCoupon': Timestamp('2012-01-01 00:00:00')}
>>> bond_price_breakup(
... mat=10.25, cpn=8e-2, yld=9e-2,
... freq=2)  
{'DirtyPrice': 95.37373582338677,
'AccruedInterest': 2.0,
'CleanPrice': 93.37373582338677,
'NextCoupon': None,
'PreviousCoupon': None}
>>> bond_price_breakup(
... settle="2012-04-15", mat="2022-01-01", cpn=8e-2, yld=8.8843e-2,
... freq=[1,2,4])  
{'DirtyPrice': array([96.64322827099208, 96.61560601490777,
 94.59488828646788], dtype=object),
'AccruedInterest': array([2.311111111111111, 2.311111111111111,
0.3111111111111111], dtype=object),
'CleanPrice': array([94.33211715988098, 94.30449490379667,
94.28377717535678], dtype=object),
'NextCoupon': array([Timestamp('2013-01-01 00:00:00'),
                    Timestamp('2012-07-01 00:00:00'),
                    Timestamp('2012-07-01 00:00:00')],
dtype=object),
'PreviousCoupon': array([Timestamp('2012-01-01 00:00:00'),
                        Timestamp('2012-01-01 00:00:00'),
                        Timestamp('2012-04-01 00:00:00')],
                        dtype=object)}
>>> bond_price_breakup(
... settle="2012-04-15", mat="2022-01-01", cpn=8e-2, yld=8.8843e-2,
... freq=[1,2,4],
... return_dataframe=True)
  DirtyPrice AccruedInterest CleanPrice NextCoupon PreviousCoupon
0  96.643228        2.311111  94.332117 2013-01-01     2012-01-01
1  96.615606        2.311111  94.304495 2012-07-01     2012-01-01
2  94.594888        0.311111  94.283777 2012-07-01     2012-04-01
bond_pricing.simple_bonds.bond_yield(settle=None, cpn=0, mat=1, price=100, freq=2, comp_freq=None, face=100, redeem=None, daycount=None, guess=0.1)

Find the yield to maturity of one or more bonds

Parameters:
  • settle (date or None) – The settlement date. None means maturity is in years.

  • cpn (float) – The coupon rate in decimal

  • mat (float or date) – Maturity date or if settle is None, maturity in years

  • price – The price of the bond

  • freq (int) – Coupon frequency

  • comp_freq (int) – Compounding frequency

  • face (float) – Face value of the bond

  • redeem (float) – Redemption value

  • daycount (daycounter) – This class has day_count and year_fraction methods

  • guess (float) – Initial guess of the yield for the root finder

Returns:

The yield to maturity of the bond. np.nan if the root finder failed.

Return type:

float

Examples

>>> bond_yield(settle="2012-04-15", mat="2022-01-01", cpn=8e-2,
... price=94.33, freq=1)
0.08884647275135965
>>> bond_yield(mat=10.25, cpn=8e-2, price=93.37, freq=2)
0.09000591604105035
>>> bond_yield(settle="2012-04-15", mat="2022-01-01", cpn=8e-2,
... price=[93, 94, 95], freq=1)
array([0.09104904, 0.08938905, 0.08775269])

Bond Valuation using zero curve

bond_pricing.zero_curve_bond_price.make_zero_price_fun(flat_curve=None, nelson_siegel=None, par_at_knots=None, par_at_coupon_dates=None, zero_at_knots=None, zero_at_coupon_dates=None, freq=1, max_maturity=None)

Create function that returns zero price for any maturity

The zero curve can be specified in many alternative ways: flat_curve, nelson_siegel, par_at_knots, par_at_coupon_dates, zero_at_knots, zero_at_coupon_dates. One of these must not be None.

Parameters:
  • flat_curve (float, optional) – Yield (flat yield curve)

  • nelson_siegel (tuple of floats, optional) – tuple consists of beta0, beta1, beta2, tau

  • par_at_knots (tuple of two sequences of floats, optional) – First element of tuple is a sequence of maturities Second element is a sequence of par yields for these maturities

  • par_at_coupon_dates (sequence of floats, optional) – Par yields for maturities spaced 1/freq years apart

  • zero_at_knots (tuple of two sequences of floats, optional) – First element of tuple is a sequence of maturities Second element is a sequence of zero rates for these maturities

  • zero_at_coupon_dates (sequence, optional) – Zero yields for maturities spaced 1/freq years apart

  • freq (int, optional) – The coupon frequency (equals compounding frequency)

  • max_maturity (float, optional) – The maximum maturity upto which the yields are to be extrapolated. If None, no extrapolation is done.

Returns:

Function that takes float (maturity) as argument and returns float (zero price)

Return type:

function

Examples

>>> make_zero_price_fun(par_at_knots = (
... [0, 1, 3, 5, 10],
... [3e-2, 3.5e-2,  4e-2, 4.5e-2, 4.75e-2]))([1, 5, 10])
array([0.96618357, 0.80065643, 0.62785397])
bond_pricing.zero_curve_bond_price.nelson_siegel_zero_rate(beta0, beta1, beta2, tau, m)

Computes zero rate from Nelson Siegel parameters

Parameters:
  • beta0 (float) – the long term rate

  • beta1 (float) – the slope

  • beta2 (float) – curvature

  • tau (float) – location of the hump

  • m (float) – maturity at which zero rate is to be found

Returns:

the continuously compounded zero yield for maturity m

Return type:

float

Examples

>>> nelson_siegel_zero_rate(0.128397, -0.024715, -0.050231, 2.0202,
...                         [0.25, 5, 15, 30])
array([0.10228692, 0.10489195, 0.11833924, 0.12335016])
>>> nelson_siegel_zero_rate(0.0893088, -0.0314768, -0.0130352,
...                         3.51166, [0.25, 5, 15, 30])
array([0.05848376, 0.06871299, 0.07921554, 0.08410199])
bond_pricing.zero_curve_bond_price.par_yld_to_zero(par, freq=1, return_dataframe=False)

Bootstrap a complete par bond yield curve to zero

Parameters:
  • par (sequence of floats) – The par bond yields for various maturities in decimal Maturities are spaced 1/freq years apart

  • freq (int, optional) – The coupon frequency (equals compounding frequency)

  • return_dataframe (bool, optional) – whether to return pandas DataFrame instead of dict

Returns:

zero_yields: array of zero yields in decimal

zero_prices: array of zero prices

forward_rates: array of forward rates in decimal

(Maturities are spaced 1/freq years apart)

Return type:

dict or DataFrame

Examples

>>> par_yld_to_zero(
... par=[1.0200e-2, 1.2000e-2, 1.4200e-2, 1.6400e-2, 1.9150e-2,
...      2.1900e-2, 2.4375e-2, 2.6850e-2, 2.9325e-2, 3.1800e-2],
... freq=2, return_dataframe=True)
   zero_yields  zero_prices  forward_rates
0     0.010200     0.994926       0.010200
1     0.012005     0.988102       0.013812
2     0.014220     0.978970       0.018656
3     0.016445     0.967776       0.023133
4     0.019245     0.953245       0.030487
5     0.022068     0.936279       0.036242
6     0.024630     0.917891       0.040066
7     0.027218     0.897504       0.045429
8     0.029837     0.875223       0.050915
9     0.032492     0.851159       0.056545
bond_pricing.zero_curve_bond_price.static_zero_spread(settle=None, cpn=0, mat=1, price=None, zero_price_fn=<function <lambda>>, freq=2, face=100, redeem=None, daycount=None, guess=0.0)

Static spread (Z-spread) over zero curve to match bond price

Parameters:
  • settle (date or None) – The settlement date. None means maturity is in years.

  • cpn (float) – The coupon rate in decimal

  • mat (float or date) – Maturity date or if settle is None, maturity in years

  • price (float) – Market price of the bond

  • zero_price_fn (function) – takes float (maturity) as argument and returns float (zero price)

  • freq (int) – Coupon frequency

  • face (float) – Face value of the bond

  • redeem (float) – Redemption value

  • daycount (daycounter) – This class has day_count and year_fraction methods

  • guess (float) – Initial guess of the yield for the root finder

Returns:

Static spread (Z-spread)

Return type:

float

Examples

>>> round(static_zero_spread(
...     cpn=5e-2, mat=2, freq=1, price=91.63122843617106,
...     zero_price_fn=make_zero_price_fun(
...         zero_at_coupon_dates=[2.5e-2, 9.5e-2])),
... 6)
0.005
bond_pricing.zero_curve_bond_price.zero_curve_bond_duration(settle=None, cpn=0, mat=1, zero_price_fn=<function <lambda>>, freq=2, face=100, redeem=None, daycount=None, modified=False)

Duration of coupon bond using zero yields

Parameters:
  • settle (date or None) – The settlement date. None means maturity is in years.

  • cpn (float) – The coupon rate in decimal

  • mat (float or date) – Maturity date or if settle is None, maturity in years

  • zero_price_fn (function) – takes float (maturity) as argument and returns float (zero price)

  • freq (int) – Coupon frequency

  • face (float) – Face value of the bond

  • redeem (float) – Redemption value

  • daycount (daycounter) – This class has day_count and year_fraction methods

Returns:

duration or modified duration of the bond

Return type:

float

Examples

>>> zero_curve_bond_duration(
...     cpn=10e-2, mat=10, freq=1,
...     zero_price_fn=make_zero_price_fun(
...         flat_curve=8e-2))  
6.965803939497351
>>> zero_curve_bond_duration(
...     cpn=5e-2, mat=2, freq=1,
...     zero_price_fn=make_zero_price_fun(
...         zero_at_coupon_dates=[3e-2, 10e-2])
... )  
1.9470227670753064
bond_pricing.zero_curve_bond_price.zero_curve_bond_price(settle=None, cpn=0, mat=1, zero_price_fn=<function <lambda>>, freq=2, face=100, redeem=None, daycount=None)

Compute clean price of coupon bond using zero yields

Parameters:
  • settle (date or None) – The settlement date. None means maturity is in years.

  • cpn (float) – The coupon rate in decimal

  • mat (float or date) – Maturity date or if settle is None, maturity in years

  • zero_price_fn (function) – takes float (maturity) as argument and returns float (zero price)

  • freq (int) – Coupon frequency

  • face (float) – Face value of the bond

  • redeem (float) – Redemption value

  • daycount (daycounter) – This class has day_count and year_fraction methods

Returns:

clean price of the bond

Return type:

float

Examples

>>> zero_curve_bond_price(
...     cpn=10e-2, mat=10, freq=1,
...     zero_price_fn=make_zero_price_fun(
...         flat_curve=8e-2))  
113.42016279788285
>>> zero_curve_bond_price(
...     cpn=5e-2, mat=2, freq=1,
...     zero_price_fn=make_zero_price_fun(
...         zero_at_coupon_dates=[3e-2, 10e-2])
... )  
91.63122843617106
>>> zero_curve_bond_price(
...     cpn=5.792982e-2, mat=6, freq=2,
...     zero_price_fn=make_zero_price_fun(
...         nelson_siegel=(6.784e-2, -3.8264e-2, -3.6631e-2, 0.7774))
... )
99.99999939355965
bond_pricing.zero_curve_bond_price.zero_curve_bond_price_breakup(settle=None, cpn=0, mat=1, zero_price_fn=<function <lambda>>, freq=2, face=100, redeem=None, daycount=None, return_dataframe=False)

Clean/dirty price & accrued interest of coupon bond using zero yields

Parameters:
  • settle (date or None) – The settlement date. None means maturity is in years.

  • cpn (float) – The coupon rate in decimal

  • mat (float or date) – Maturity date or if settle is None, maturity in years

  • zero_price_fn (function) – takes float (maturity) as argument and returns float (zero price)

  • freq (int) – Coupon frequency

  • face (float) – Face value of the bond

  • redeem (float) – Redemption value

  • daycount (daycounter) – This class has day_count and year_fraction methods

  • return_dataframe (bool) – whether to return pandas DataFrame instead of dict

Returns:

dirty: dirty price of the bond

accrued: accrued interest

clean: clean price of the bond

next_coupon: Next coupon date (only if settle is None)

prev_coupon: Previous coupon date (only if settle is None)

Return type:

dict or DataFrame

Examples

>>> zero_curve_bond_price_breakup(
...     cpn=10e-2, mat=10, freq=1,
...     zero_price_fn=make_zero_price_fun(
...         flat_curve=8e-2))  
{'DirtyPrice': 113.42016279788285,
 'AccruedInterest': 0.0,
 'CleanPrice': 113.42016279788285,
 'NextCoupon': None,
 'PreviousCoupon': None}
>>> zero_curve_bond_price_breakup(
...     cpn=5e-2, mat=2, freq=1,
...     zero_price_fn=make_zero_price_fun(
...         zero_at_coupon_dates=[3e-2, 10e-2])
... )  
{'DirtyPrice': 91.63122843617106,
 'AccruedInterest': 0.0,
 'CleanPrice': 91.63122843617106,
 'NextCoupon': None,
 'PreviousCoupon': None}
bond_pricing.zero_curve_bond_price.zero_to_par(zero_prices=None, zero_yields=None, freq=1)

Convert zero yield curve into par curve

The zero curve can be specified either as prices or as yields. One of zero_prices and zero_yields must not be None.

Parameters:
  • zero_prices (sequence of floats, optional) – The zero prices for various maturities in decimal Either zero_prices or zero_yields must be provided If both are provided, zero_yields is ignored Maturities are spaced 1/freq years apart

  • zero_yields (sequence of floats, optional) – The zero yields for various maturities in decimal Maturities are spaced 1/freq years apart

  • freq (int, optional) – The coupon frequency (equals compounding frequency)

Returns:

par – The par yields for various maturities in decimal Maturities are spaced 1/freq years apart

Return type:

array of floats

Examples

>>> zero_to_par(
... zero_yields=[0.0102    , 0.0120054 , 0.01421996, 0.01644462,
...              0.01924529, 0.02206823, 0.02462961, 0.02721789,
...              0.0298373 , 0.0324924 ],
... freq=2)  
array([0.0102  , 0.012   , 0.0142  , 0.0164  , 0.01915 , 0.0219  ,
       0.024375, 0.02685 , 0.029325, 0.0318  ])
>>> zero_to_par(
... zero_prices=[0.99492588, 0.98810183, 0.97896982, 0.96777586,
...              0.9532451 , 0.9362787 , 0.91789052, 0.89750426,
...              0.87522337, 0.85115892],
... freq=2)  
array([0.0102  , 0.012   , 0.0142  , 0.0164  , 0.01915 , 0.0219  ,
       0.024375, 0.02685 , 0.029325, 0.0318  ])

Bond Valuation Key Rate Shifts

bond_pricing.key_rates.key_rate_shift(krs, mat=None, T=None, freq=2, krs_points=[2, 5, 10, 30])

Shift in various par bond yields for a key rate shift

Mainly for internal use or pedagogical use.

Parameters:
  • krs (int or string, optional) – The key rate segment of the curve to be shifted For example, 5 for shifting the 5 year segment. “all” and “none” are also accepted.

  • mat (array) – The maturities for which the shift is to be computed

  • T (Maximum maturity upto which shifted yields are to be returned.) –

  • freq (Coupon or Compounding Frequency) –

  • krs_points (sequence, optional) – The key rates to be used.

Returns:

The shifts in each par bond yields for a one basis point shift in a key rate

Return type:

array

Examples

bond_pricing.key_rates.key_rate_shifted_zero_curve(initial_zero_fn=None, initial_zero_price=None, initial_zero_yld=None, initial_par_yld=None, krs='none', freq=2, T=None, what='zero_yields', krs_points=[2, 5, 10, 30])

Applies a key rate shift to an yield curve

The initial yield curve can be given in many alternative ways. One of these must be non None: initial_zero_fn, initial_zero_price, initial_zero_yld, initial_par_yld

Parameters:
  • initial_zero_fn (function, optional) – Function that returns zero prices for any maturity

  • initial_zero_price (array, optional) – Zero prices for coupon dates up to some maturity

  • initial_zero_yld (array, optional) – Zero yields for coupon dates up to some maturity

  • initial_par_yld (array, optional) – Par yields for coupon dates up to some maturity

  • krs (int or string, optional) – The key rate segment of the curve to be shifted For example, 5 for shifting the 5 year segment. “all” and “none” are also accepted.

  • freq (Coupon or Compounding Frequency) –

  • T (Maximum maturity upto which shifted yields are to be returned.) –

  • what (str, optional) – What to return. Can be: zero_yields, zero_prices, forward_rates or zero_fn

  • krs_points (sequence, optional) – The key rates to be used.

Returns:

If what is zero_yields, zero_prices or forward_rates, the corresponding array is return. If what is zero_fn, a function is returned.

Return type:

array or function

Examples

Compute KR01 of a 10-year 8% bond when initial yield curve is flat at 5%. Being a 10 year bond, the 10 year KR01 is the largest. But since it is not a par bond, the 5 year KR01 is also large.

>>> fn0 = make_zero_price_fun(flat_curve=5e-2, freq=2)
>>> P0 = zero_curve_bond_price(cpn=8e-2, mat=10, zero_price_fn=fn0)
>>> fns = [key_rate_shifted_zero_curve(
...        initial_zero_fn=fn0, krs=krs, what='zero_fn')
...        for krs in standard_krs_points]
>>> zero_curve_bond_price(cpn=8e-2, mat=10, zero_price_fn=fns) - P0
array([-0.00121608, -0.00467591, -0.08307928,  0.        ])
bond_pricing.key_rates.make_KRS(freq=2, krs_points=[2, 5, 10, 30])

Data Frame of par yield shifts for all key rate shifts

Mainly for pedagogical use.

Parameters:
  • freq (Coupon or Compounding Frequency) –

  • krs_points (sequence, optional) – The key rates to be used.

Return type:

pandas DataFrame

Examples

Present Value and Annuities

Present value module in bond_pricing

This module includes npv, irr and duration for arbitrary cash flows. The functions for annuities include annuity_pv, annuity_pv, annuity_rate, annuity_instalment, annuity_instalment_breakup, annuity_periods. The helper functions pvaf and fvaf are intended for use by the annuity functions, but can be used directly if desired.

bond_pricing.present_value.annuity_fv(rate, n_periods=inf, instalment=1, terminal_payment=0, immediate_start=False, cf_freq=1, comp_freq=1)

Future value of annuity

Parameters:
  • rate (float or sequence of floats) – per period discount rate in decimal

  • n_periods (float or sequence of floats) – number of periods of annuity

  • instalment (float or sequence of floats) – instalment per period

  • terminal_payment (float or sequence of floats) – baloon payment at the end of the annuity

  • immediate_start (bool or sequence of bool) – If True, cash flows start immediately Else, the first cash flow is at the end of the first period.

  • cf_freq (float or sequence of floats) – cash flow frequency (for example, 2 for semi-annual)

  • comp_freq (float or sequence of floats) – compounding frequency (for example, 2 for semi-annual)

Returns:

The future value of the annuity

Return type:

float or array of floats

Examples

>>> annuity_fv(rate=10e-2, n_periods=15, instalment=500)
15886.240847078281
>>> annuity_fv(rate=10e-2, n_periods=[10, 15], instalment=500)
array([ 7968.7123005 , 15886.24084708])
bond_pricing.present_value.annuity_instalment(rate, n_periods=inf, pv=None, fv=None, terminal_payment=0, immediate_start=False, cf_freq=1, comp_freq=1)

Periodic instalment to get desired PV or FV

Parameters:
  • rate (float or sequence of floats) – per period discount rate in decimal

  • n_periods (float or sequence of floats) – number of periods of annuity

  • pv (float or sequence of floats) – desired present value of annuity

  • fv (float or sequence of floats) – desired future value of annuity If pv and fv are given, discounted value of fv is added to pv

  • terminal_payment (float or sequence of floats) – baloon payment at the end of the annuity

  • immediate_start (bool or sequence of bool) – If True, cash flows start immediately Else, the first cash flow is at the end of the first period.

  • cf_freq (float or sequence of floats) – cash flow frequency (for example, 2 for semi-annual)

  • comp_freq (float or sequence of floats) – compounding frequency (for example, 2 for semi-annual)

Returns:

The instalment

Return type:

float or array of floats

Examples

>>> annuity_instalment(rate=10e-2, n_periods=15, pv=3803.04)
500.0000324537518
>>> annuity_instalment(rate=10e-2, n_periods=[10, 15], pv=3803.04)
array([618.92724655, 500.00003245])
bond_pricing.present_value.annuity_instalment_breakup(rate, n_periods=inf, pv=None, fv=None, terminal_payment=0, immediate_start=False, cf_freq=1, comp_freq=1, period_no=1, return_dataframe=False)

Break up instalment into principal and interest parts

Parameters:
  • rate (float or sequence of floats) – per period discount rate in decimal

  • n_periods (float or sequence of floats) – number of periods of annuity

  • pv (float or sequence of floats) – desired present value of annuity

  • fv (float or sequence of floats) – desired future value of annuity If pv and fv are given, discounted value of fv is added to pv

  • terminal_payment (float or sequence of floats) – baloon payment at the end of the annuity

  • immediate_start (bool or sequence of bool) – If True, cash flows start immediately Else, the first cash flow is at the end of the first period.

  • cf_freq (float or sequence of floats) – cash flow frequency (for example, 2 for semi-annual)

  • comp_freq (float or sequence of floats) – compounding frequency (for example, 2 for semi-annual)

  • return_dataframe (bool) – whether to return pandas DataFrame instead of dict

Returns:

Opening Principal

Instalment

Interest Part

Principal Part

Closing Principal

Return type:

dict

Examples

>>> annuity_instalment_breakup(rate=10e-2, n_periods=15, pv=3803.04,
...       period_no=6)  
{'Period No': 6,
 'Opening Principal': 3072.283752266599,
 'Instalment': 500.0000324537518,
 'Interest Part': 307.2283752266599,
 'Principal Part': 192.7716572270919,
 'Closing Principal': 2879.512095039507}
>>> d = annuity_instalment_breakup(rate=10e-2, n_periods=15, pv=3803.04,
...       period_no=range(1, 4), return_dataframe=True
...       ); print(d.iloc[:, :4]); print(d.iloc[:, 4:])
   Period No  Opening Principal  Instalment  Interest Part
0          1        3803.040000  500.000032     380.304000
1          2        3683.343968  500.000032     368.334397
2          3        3551.678332  500.000032     355.167833
   Principal Part  Closing Principal
0      119.696032        3683.343968
1      131.665636        3551.678332
2      144.832199        3406.846133
bond_pricing.present_value.annuity_periods(rate, instalment=1, pv=None, fv=None, terminal_payment=0, immediate_start=False, cf_freq=1, comp_freq=1, round2int_digits=6)

Number of periods of annuity to get desired PV or FV

Parameters:
  • rate (float or sequence of floats) – per period discount rate in decimal

  • instalment (float or sequence of floats) – instalment per period

  • pv (float or sequence of floats) – desired present value of annuity

  • fv (float or sequence of floats) – desired future value of annuity If pv and fv are given, discounted value of fv is added to pv

  • terminal_payment (float or sequence of floats) – baloon payment at the end of the annuity

  • immediate_start (bool or sequence of bool) – If True, cash flows start immediately Else, the first cash flow is at the end of the first period.

  • cf_freq (float or sequence of floats) – cash flow frequency (for example, 2 for semi-annual)

  • comp_freq (float or sequence of floats) – compounding frequency (for example, 2 for semi-annual)

  • round2int_digits (float or sequence of floats) – answer is rounded to integer if round2int_digits after the decimal point are zero

Returns:

The number of periods

Return type:

float or array of floats

Examples

>>> annuity_periods(rate=10e-2, instalment=500, pv=3803.04)
15.000002163748604
>>> annuity_periods(rate=10e-2, instalment=500, pv=3803.04,
...      round2int_digits=4)
15.0
>>> annuity_periods(rate=[0, 10e-2], instalment=500, pv=3803.04)
array([ 7.60608   , 15.00000216])
bond_pricing.present_value.annuity_pv(rate, n_periods=inf, instalment=1, terminal_payment=0, immediate_start=False, cf_freq=1, comp_freq=1)

Present value of annuity

Parameters:
  • rate (float or sequence of floats) – per period discount rate in decimal

  • n_periods (float or sequence of floats) – number of periods of annuity

  • instalment (float or sequence of floats) – instalment per period

  • terminal_payment (float or sequence of floats) – baloon payment at the end of the annuity

  • immediate_start (bool or sequence of bool) – If True, cash flows start immediately Else, the first cash flow is at the end of the first period.

  • cf_freq (float or sequence of floats) – cash flow frequency (for example, 2 for semi-annual)

  • comp_freq (float or sequence of floats) – compounding frequency (for example, 2 for semi-annual)

Returns:

The present value of the annuity

Return type:

float or array of floats

Examples

>>> annuity_pv(rate=10e-2, n_periods=15, instalment=500)
3803.039753154183
>>> annuity_pv(rate=10e-2, n_periods=[10, 15], instalment=500)
array([3072.28355285, 3803.03975315])
bond_pricing.present_value.annuity_rate(n_periods=inf, instalment=1, pv=None, fv=None, terminal_payment=0, immediate_start=False, cf_freq=1, comp_freq=1, r_guess=0)

Discount rate to get desired PV or FV of annuity

Parameters:
  • n_periods (float or sequence of floats) – number of periods of annuity

  • instalment (float or sequence of floats) – instalment per period

  • pv (float or sequence of floats) – desired present value of annuity

  • fv (float or sequence of floats) – desired future value of annuity If pv and fv are given, discounted value of fv is added to pv

  • terminal_payment (float or sequence of floats) – baloon payment at the end of the annuity

  • immediate_start (bool or sequence of bool) – If True, cash flows start immediately Else, the first cash flow is at the end of the first period.

  • cf_freq (float or sequence of floats) – cash flow frequency (for example, 2 for semi-annual)

  • comp_freq (float or sequence of floats) – compounding frequency (for example, 2 for semi-annual)

  • r_guess (float, optional) – Starting value (guess) for root finder

Returns:

The discount rate

Return type:

float or array of floats

Examples

>>> annuity_rate(n_periods=15, instalment=500, pv=3803.04)
0.09999998862890495
>>> annuity_rate(n_periods=[9, 10, 15], instalment=100, pv=1000)
array([-0.0205697 ,  0.        ,  0.05556497])
bond_pricing.present_value.duration(cf, rate, cf_freq=1, comp_freq=1, cf_t=None, immediate_start=False, modified=False)

Duration of arbitrary sequence of cash flows

Parameters:
  • cf (sequence of floats) – array of cash flows

  • rate (float or sequence of floats) – discount rate

  • cf_freq (float or sequence of floats, optional) – cash flow frequency (for example, 2 for semi-annual)

  • comp_freq (float or sequence of floats, optional) – compounding frequency (for example, 2 for semi-annual)

  • cf_t (float or sequence of floats or None, optional) – The timing of cash flows. If None, equally spaced cash flows are assumed

  • immediate_start (bool or sequence of bool, optional) – If True, cash flows start immediately Else, the first cash flow is at the end of the first period.

  • modified (bool or sequence of bool, optional) – If True, modified duration is returned

Returns:

The duration of the cash flows

Return type:

float or array of floats

Examples

>>> duration(cf=[100, 50, 75, 25], rate=10e-2)
1.9980073065426769
>>> duration(cf=[100, 50, 75, 25], rate=10e-2,
...          immediate_start=[True, False])
array([0.99800731, 1.99800731])
bond_pricing.present_value.equiv_rate(rate, from_freq=1, to_freq=1)

Convert interest rate from one compounding frequency to another

Parameters:
  • rate (float or sequence of floats) – discount rate in decimal

  • from_freq (float or sequence of floats) – compounding frequency of input rate

  • to_freq (float or sequence of floats) – compounding frequency of output rate

Returns:

The discount rate for the desired compounding frequency

Return type:

float or array of floats

Examples

>>> equiv_rate(
...    rate=10e-2, from_freq=1, to_freq=[1, 2, 12, 365, np.inf])
array([0.1       , 0.0976177 , 0.09568969, 0.09532262, 0.09531018])
>>> equiv_rate(
...    rate=10e-2, from_freq=[1, 2, 12, 365, np.inf], to_freq=1)
array([0.1       , 0.1025    , 0.10471307, 0.10515578, 0.10517092])
bond_pricing.present_value.fvaf(r, n)

Compute Future Value of Annuity Factor

Parameters:
  • r (float or sequence of floats) – per period interest rate in decimal

  • n (float or sequence of floats) – number of periods

Returns:

The future value of annuity factor

Return type:

float or array of floats

Examples

>>> fvaf(r=0.1, n=10)
15.937424601000023
>>> fvaf(r=[0, 0.1], n=10)
array([10.       , 15.9374246])
bond_pricing.present_value.irr(cf, cf_freq=1, comp_freq=1, cf_t=None, r_guess=0.1)

IRR of a sequence of cash flows

Multiple IRRs can be found by giving multiple values of r_guess as shown in one of the examples below.

Parameters:
  • cf (float or sequence of floats) – array of cash flows

  • cf_freq (float or sequence of floats, optional) – cash flow frequency (for example, 2 for semi-annual)

  • comp_freq (float or sequence of floats, optional) – compounding frequency (for example, 2 for semi-annual)

  • cf_t (float or sequence of floats or None, optional) – The timing of cash flows. If None, equally spaced cash flows are assumed

  • immediate_start (bool or sequence of bool, optional) – If True, cash flows start immediately Else, the first cash flow is at the end of the first period.

  • r_guess (float or sequence of floats, optional) – Starting value (guess) for root finder

Returns:

The internal rate of return (IRR) of the cash flows

Return type:

float or array of floats

Examples

>>> irr(cf=[-100, 150, -50, 75])
0.4999999999999994
>>> irr(cf=[-100, 150, -50, 75], cf_freq=1, comp_freq=2)
0.4494897427831782
>>> irr(cf=[-100, 150, -50, 75], cf_t=[0, 2, 5, 7])
0.2247448713915599
>>> irr(cf=(-100, 230, -132), r_guess=[0.13, 0.18])
array([0.1, 0.2])
bond_pricing.present_value.npv(cf, rate, cf_freq=1, comp_freq=1, cf_t=None, immediate_start=False)

NPV of a sequence of cash flows

Parameters:
  • cf (float or sequence of floats) – array of cash flows

  • rate (float or sequence of floats) – discount rate

  • cf_freq (float or sequence of floats, optional) – cash flow frequency (for example, 2 for semi-annual)

  • comp_freq (float or sequence of floats, optional) – compounding frequency (for example, 2 for semi-annual)

  • cf_t (float or sequence of floats or None, optional) – The timing of cash flows. If None, equally spaced cash flows are assumed

  • immediate_start (bool or sequence of bool, optional) – If True, cash flows start immediately Else, the first cash flow is at the end of the first period.

Returns:

The net present value of the cash flows

Return type:

float or array of floats

Examples

>>> npv(cf=[-100, 150, -50, 75], rate=5e-2)
59.327132213429586
>>> npv(cf=[-100, 150, -50, 75], rate=5e-2, comp_freq=[1, 2])
array([59.32713221, 59.15230661])
>>> npv(cf=[-100, 150, -50, 75], rate=5e-2,
...     immediate_start=[False, True])
array([59.32713221, 62.29348882])
>>> npv(cf=[-100, 150, -50, 75], cf_t=[0, 2, 5, 7], rate=[5e-2, 8e-2])
array([50.17921321, 38.33344284])
bond_pricing.present_value.pvaf(r, n)

Compute Present Value of Annuity Factor

Parameters:
  • r (float or sequence of floats) – per period interest rate in decimal

  • n (float or sequence of floats) – number of periods

Returns:

The present value of annuity factor

Return type:

float or array of floats

Examples

>>> pvaf(r=0.1, n=10)
6.144567105704685
>>> pvaf(10e-2, [5, 10])
array([3.79078677, 6.14456711])

Utility Functions

Utility functions

newton_wrapper is a wrapper for scipy.optimize.newton to return root or nan

edate moves date(s) by specified no of months (similar to excel edate)

dataframe_like_dict creates pandas DataFrame from dict like arguments

bond_pricing.utils.edate(dt, m)

Move date(s) by specified no of months (similar to excel edate)

Parameters:
  • dt (date, object convertible to date or sequence) – The date(s) to be shifted

  • m (int or sequence) – Number of months to shift by

Returns:

dt shifted by m months

Return type:

date

Examples

>>> edate('2020-01-31', 1)
Timestamp('2020-02-29 00:00:00')
>>> edate(['2020-01-31', '2020-03-31'], [1, 6])
array([Timestamp('2020-02-29 00:00:00'), Timestamp('2020-09-30 00:00:00')],
      dtype=object)
bond_pricing.utils.newton_wrapper(f, guess, warn=True)

Wrapper for scipy.optimize.newton to return root or nan

If scipy cannot be imported, falls back on the my_irr function that uses root bracketing and bisection.

Parameters:
  • f (callable) – The function whose zero is to be found

  • guess (float) – An initial estimate of the zero somewhere near the actual zero.

  • warn (bool, Optional) – If true, a warning is issued when returning nan. This happens when scipy.optimize.newton does not converge.

Returns:

The root if found, numpy.nan otherwise.

Return type:

float

Examples

>>> newton_wrapper(lambda x: x**2 - x, 0.8)
1.0
>>> newton_wrapper(lambda x: x**2 + 1, 1, warn=False)
nan

Indices and tables