# Modified or Macauley Duration in python

are there any existing python modules that can calculate Modified and/or Macauley Duration of a bond.

I calculate duration in Python using numpy, it's nice and simple:

def durations(cfs, rates, price, ytm, no_coupons, payments_per_year=2):
import numpy as np
mac_dur = np.sum([cfs[i]*((i+1)/payments_per_year)/np.power(1+rates[i],i+1) for i in range(len(cfs))])/price
mod_dur = mac_dur/(1+ytm/no_coupons)
return mac_dur, mod_dur

• You have an error in your calculation your range function should start in 1 but because you are iterating in an array this will cause index out of range, when you say cfs[i]*i should be * (i+1) because the first i is 0 and you are loosing then the first number the same in your power method – Alejandro Serret Oct 13 '17 at 22:47

Go talk to Fincad. Here is their page on integrating with scripting languages:

Their analytics libraries include bond analytics, and they have a spreadsheet product so you can test methods and results before implementing them.

Disclaimer: I work for a company who is a customer of Fincad's analytics.

You can use my script:def Duration (timetomaturity,nominalvalue,yieldrate,couponrate):    import math as m    yld=yieldrate/100    cpnr=couponrate/100    t=list(range(1,timetomaturity+1))    cfi=nominalvalue*cpnr    cfN=nominalvalue*cpnr+nominalvalue    cfl=[cfi]*(len(t)-1)+[cfN]    B=0 # B is the bond's present value    for k in range(0,timetomaturity):        B=B+cfl[k]*(m.exp(-yld*t[k]))    D=0 # D is the duration    for i in range(0,timetomaturity):        D+=(t[i]*cfl[i]*m.exp(-yld*t[i]))/B    return round(D,2),round(B,2)#Duration(5,100,1,1)#By Tural Valiyev

Example: Consider a 7% bond with 3 years to maturity. Assume bond is selling at 8% yield.

Most concise function for explicit formulas:

def durations_explicit(c, y, m, n):
macaulay_duration = ((1+y) / (m*y)) - ( (1 + y + n*(c-y)) / ((m*c* ((1+y)**n - 1)) + m*y) )
modified_duration = macaulay_duration / (1 + y)
return macaulay_duration, modified_duration

durations_explicit(c=.035, y=.04, m=2, n=6)
(2.75371702450442, 2.6478048312542497)


For a more complete answer with docstrings and accounting for the case when coupon rate per period equals yield per period

def durations_explicit(c, y, m, n):
"""Parameters:
c = coupon rate per period
y = yield per period
m = periods per year
n = periods remaining"""
if c==y: # Shorter explicit formula if coupon rate per period = yield per period
macaulay_duration = ((1+y)/(m*y))*(1 - (1 / (1+y)**n))
modified_duration = macaulay_duration / (1 + y)
print(f"Macaulay Duration: {macaulay_duration}")
print(f"Modified Duration: {modified_duration}")
return macaulay_duration, modified_duration
else:
macaulay_duration = ((1+y) / (m*y)) - ( (1 + y + n*(c-y)) / ((m*c* ((1+y)**n - 1)) + m*y) )
modified_duration = macaulay_duration / (1 + y)
print(f"Macaulay Duration: {macaulay_duration}")
print(f"Modified Duration: {modified_duration}")
return macaulay_duration, modified_duration

durations_explicit(c=.035, y=.04, m=2, n=6)
Macaulay Duration: 2.75371702450442
Modified Duration: 2.6478048312542497
(2.75371702450442, 2.6478048312542497)

• Very nice. But your input definitions seem somewhat non-standard, for example coupon rate is usually expressed "per year", not "per period". – Alex C Dec 3 '19 at 18:36
• I agree, but I wanted to stay consistent with "Investment Science" by Luenberger. He flips the usage of "yield to maturity" using "lambda", and using "yield per period" using "y" as in the formula. You have to adjust mbudda's formula by dividing by semi-annual ((i+1)/2) or dividing his final result by 2 to get the same duration calculations as the explicit formulas. In this manner I thought it would be best to have the individual supply with their payment structure upfront. – the775 Dec 3 '19 at 18:57