Calculating Amortization with Python

5 min read
Calculating Amortization with Python
Update: May 5, 2019 - install amortization library from PyPI. See related topic here.
pip install amortization

Are you a prospective borrower? Have you applied for a loan? Do you know how amortizations are calculated?

In this blog, I will discuss how to calculate amortizations and how to generate amortization schedules.


The Formula

Before availing a loan, we always ask for the amortization amount. It can be calculated using the following formula.

Image from https://www.vertex42.com/ExcelArticles/amortization-calculation.html

In this formula, A is the amortization amount that will be paid per period, P is the principal or loan amount, r is the interest rate per period, and n is the total number of periods.


Solving using Python

We can calculate the amortization amount using the Python code below

def calculate_amortization_amount(principal, interest_rate, period):
    x = (1 + interest_rate) ** period
    return principal * (interest_rate * x) / (x - 1)

How about the amortization schedule?

Generating the amortization schedule is pretty easy. Starting with the balance (initially, principal), multiply it by the interest rate, let's call it the interest. To get the principal from the amortization amount, subtract the interest from the amortization amount. You will get the new balance by subtracting the principal. The following Python generator demonstrates this method.

def amortization_schedule(principal, interest_rate, period):
    amortization_amount = calculate_amortization_amount(principal, interest_rate, period)
    number = 1
    balance = principal
    while number <= period:
        interest = balance * interest_rate
        principal = amortization_amount - interest
        balance = balance - principal
        yield number, amortization_amount, interest, principal, balance if balance > 0 else 0
        number += 1

Let's make an app!

Putting the code into use, we will create a web application using Flask.

See How I built Atom: A Simple URL Shortener with Flask and Hashids and Knowing When to Renew your Vehicle's Registration in PH using Python to get an idea on how to create a web app using Flask

We will start by creating a form using Flask-WTF. Notice how I made use of validators to set the minimum and maximum values of our inputs. For the frequency, I set the number of payments as the key for the choices - this is to reduce our code size. I added a reCAPTCHA to protect our app from automated attacks.

from flask_wtf import FlaskForm, RecaptchaField
from wtforms import SubmitField, FloatField, IntegerField, SelectField
from wtforms.validators import DataRequired, NumberRange


class AmortizationForm(FlaskForm):
    principal = FloatField('Principal', validators=[DataRequired(), NumberRange(min=0)])
    interest_rate = FloatField('Interest Rate', validators=[DataRequired(), NumberRange(min=0, max=100)])
    period = IntegerField('Period', validators=[DataRequired(), NumberRange(min=0)])
    frequency = SelectField('Frequency of Payment', coerce=int, default=12,
                            choices=[(24, 'Bi-Weekly'), (12, 'Monthly'), (6, 'Bi-Monthly'), (4, 'Quarterly'),
                                     (2, 'Semi-Annually'), (1, 'Annually')],
                            validators=[DataRequired()])
    recaptcha = RecaptchaField()
    calculate = SubmitField('Calculate')

Next is to integrate it with Flask.

from flask import Flask, request, render_template

application = Flask(__name__)


@application.route('/amortization-calculator', methods=['GET', 'POST'])
def amortization_calculator():
    form = AmortizationForm()
    amount = schedule = None
    if request.method == 'POST' and form.validate_on_submit():
        amount = calculate_amortization_amount(form.principal.data, form.interest_rate.data / 100 / form.frequency.data,
                                               form.period.data * form.frequency.data)
        schedule = list(amortization_schedule(form.principal.data, form.interest_rate.data / 100 / form.frequency.data,
                                              form.period.data * form.frequency.data))
    return render_template('amortization-calculator.html', form=form, amortization_amount=amount, amortization_schedule=schedule)

Lastly, I wrote a template. I used Bootstrap and bootstrap-table to add pagination to the amortization schedule.

{% if amortization_amount %}
    <div class="text-center">
        <h5>Your amortization amount is </h5>
        <h5 class="text-danger">{{ '{:,.2f}'.format(amortization_amount) }}</h5>
    </div>
    <hr>
    <div class="text-center">
        <h5>Amortization Schedule</h5>
    </div>
    <table id="table"
           data-toggle="table"
           data-pagination="true"
           data-page-size="6"
           data-page-list="[6, 12, 24, 60, 120]"
    >
        <thead>
        <tr>
            <th class="text-center">Number</th>
            <th class="text-center">Amortization</th>
            <th class="text-center">Interest</th>
            <th class="text-center">Principal</th>
            <th class="text-center">Balance</th>
        </tr>
        </thead>
        <tbody>
        {% for number, amount, interest, principal, balance in amortization_schedule %}
            <tr>
                <td class="text-center">{{ number }}</td>
                <td class="text-right">{{ '{:,.2f}'.format(amount) }}</td>
                <td class="text-right">{{ '{:,.2f}'.format(interest) }}</td>
                <td class="text-right">{{ '{:,.2f}'.format(principal) }}</td>
                <td class="text-right">{{ '{:,.2f}'.format(balance) }}</td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
    <hr>
    <div class="text-center">
        <h5>Perform another calculation</h5>
    </div>
{% endif %}
<form method="POST" action="{{ url_for('amortization_calculator') }}" autocomplete="off">
    {{ form.csrf_token }}
    <div class="row">
        <div class="col">
            <div class="form-group has-feedback {{ 'has-error' if form.principal.errors else '' }}">
                <label for="{{ form.principal.id }}">Principal Amount</label>
                {{ form.principal(class_='form-control' + (' is-invalid' if form.principal.errors else ''), placeholder='Enter Principal Amount') }}
                <span class="glyphicon glyphicon-globe form-control-feedback"></span>
                {% if form.principal.errors %}
                    <div>
                        <small class="text-danger">{{ form.principal.errors[0] }}</small>
                    </div>
                {% endif %}
            </div>
        </div>

        <div class="col">
            <div class="form-group has-feedback {{ 'has-error' if form.interest_rate.errors else '' }}">
                <label for="{{ form.interest_rate.id }}">Annual Interest Rate (%)</label>
                {{ form.interest_rate(class_='form-control' + (' is-invalid' if form.interest_rate.errors else ''), placeholder='Enter Annual Interest Rate') }}
                <span class="glyphicon glyphicon-globe form-control-feedback"></span>
                {% if form.interest_rate.errors %}
                    <div>
                        <small class="text-danger">{{ form.interest_rate.errors[0] }}</small>
                    </div>
                {% endif %}
            </div>
        </div>
    </div>

    <div class="row">
        <div class="col">
            <div class="form-group has-feedback {{ 'has-error' if form.period.errors else '' }}">
                <label for="{{ form.period.id }}">Payment Period in Years</label>
                {{ form.period(class_='form-control' + (' is-invalid' if form.period.errors else ''), placeholder='Enter Payment Period') }}
                <span class="glyphicon glyphicon-globe form-control-feedback"></span>
                {% if form.period.errors %}
                    <div>
                        <small class="text-danger">{{ form.period.errors[0] }}</small>
                    </div>
                {% endif %}
            </div>
        </div>

        <div class="col">
            <div class="form-group has-feedback {{ 'has-error' if form.frequency.errors else '' }}">
                <label for="{{ form.frequency.id }}">Payment Frequency</label>
                {{ form.frequency(class_='form-control' + (' is-invalid' if form.frequency.errors else ''), placeholder='Enter Frequency of Payment') }}
                <span class="glyphicon glyphicon-globe form-control-feedback"></span>
                {% if form.frequency.errors %}
                    <div>
                        <small class="text-danger">{{ form.frequency.errors[0] }}</small>
                    </div>
                {% endif %}
            </div>
        </div>
    </div>

    <div class="form-group has-feedback {{ 'has-error' if form.recaptcha.errors else '' }}">
        <div class="text-center">
            {{ form.recaptcha() }}
        </div>
        {% if form.recaptcha.errors %}
            <div class="text-center">
                <small class="text-danger">{{ form.recaptcha.errors[0] }}</small>
            </div>
        {% endif %}
    </div>
    <div class="form-group has-feedback">
        {{ form.calculate(class_='btn btn-primary btn-block btn-flat') }}
    </div>
</form>

The final product

The following screenshots shows how our finished app looks like when used by the end-user.

Upon first visit, you will see the following form. Enter values and press the CALCULATE button.

After submitting the form, you will be able to view your amortization amount and your amortization schedule which indicates the breakdown of your payments per period.

References

Read more articles like this in the future by buying me a coffee!

Buy me a coffeeBuy me a coffee