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.

In [1]:

```
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.

In [2]:

```
# 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")
```

Out[2]:

Now let's compute payments.

In [3]:

```
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.