This notebook produces bond or loan payment and amortization schedules for absolutely any payment shape imaginable. The only restriction is that interest rates have to be fixed.
import numpy as np
# collect inputs
bnow = float(input("Enter the initial size of the loan: "))
balloon= float(input("Enter the size of the final balloon payment (0 if none): "))
periods = int(input("Enter the term (=number of years, say): "))
nper= int(input("Enter the number of payments per period: "))
rper = float(input("Enter the interest rate per period, in percent: "))
nregims = int(input("Enter the number of distinct growth regimes: "))
rnow=rper/nper # interest rate per payment period
term=periods*nper # total number of payments
checksum=False
while not checksum:
shape = np.array([input("Enter the growth rate in percent and length in number of payments (5 10, e.g.) for regime " + str(i+1) + ": ").split()
for i in range(nregims)]).astype(np.float)
checksum= (np.sum(shape,axis=0)[1]==term) # this makes sure that the regimes add up in length to the term
if not checksum:
print(" ")
print("Error: inconsistent inputs")
print("The length of your growth regimes does not add up to the number of payments.")
print("Let's try this again.")
print(" ")
Now we show the generic shape of payment to make sure the input was properly understood.
# now we display the shape of payments to make sure this matches what the user had in mind
paymentshape=np.ones(term)
regime=0
switch=shape[0][1]
for i in range(1,term):
paymentshape[i]=paymentshape[i-1]*(1+shape[regime][0]/100)
if (i==switch-1 and regime<nregims-1):
regime=regime+1
switch=switch+shape[regime][1]
import matplotlib.pyplot as plt #graphing module with matlab-like properties
%matplotlib inline
plt.plot(range(1,term+1),paymentshape)
plt.title("Shape of payments")
Now let's compute payments.
import numpy_financial as npf
import pandas as pd
F=npf.npv(rnow/100,np.concatenate(([0],paymentshape),axis=None)) # this the bond factor
# warning: npv does not discount first payment in numpy-financial unlike excel
firstpayment=(bnow-balloon/(1+rnow/100)**term)/F # this uses the "fundamental equation of bond design"
actualpayments=firstpayment*paymentshape
ib=np.zeros(term) # start of period balance
fb=np.zeros(term) # end of period balance
intpayment=np.zeros(term) # interest path
for t in range(term):
if t==0:
ib[t]=bnow
else:
ib[t]=fb[t-1]
intpayment[t]=ib[t]*rnow/100
fb[t]=ib[t]+intpayment[t]-actualpayments[t]
pd.options.display.float_format = '{:,.2f}'.format # this is the financial format we like
bondtable=pd.DataFrame({'Month':range(1,term+1),'Initial Balance': ib,'Payment':actualpayments,
'Interest':intpayment,'Final Balance':fb}) # bond payment schedule
bondtable=bondtable.set_index('Month')
# let's show the first and last few lines of the bond table
sliced_bond = bondtable.head(20)
sliced_bond = sliced_bond.append(bondtable.tail(20))
print("Debt payment table:")
print(sliced_bond)
We probably did this right because the final balance equals the balloon, as it should.