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 theisda_daycounters
module that can be downloaded from https://github.com/miradulo/isda_daycountersMaturity can be given in years (the
settle
parameter is set toNone
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 functionequiv_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