Mortgage Calculator

How to create a mortgage calculator using Panel.

You have unsaved changes
Python
import panel as pn
import param
import pandas as pd

from bokeh.palettes import Spectral10
from bokeh.plotting import figure
from bokeh.transform import cumsum


class MortgageCalculator(param.Parameterized):
    total_available_for_closing = param.Number(default=20000)
    home_purchase_price = param.Number(default=200000)
    home_appraised_value = param.Number(default=180000)
    mortgage_duration_years = param.Number(default=30)
    interest_rate_percent = param.Number(default=6.5)
    hoa = param.Number(default=100)
    monthly_insurance = param.Number(default=65)
    property_tax_rate_percent = param.Number(default=1.4)
    fixed_closing_costs = param.Number(default=15000)

    @param.depends("interest_rate_percent")
    def monthly_interest_rate(self):
        return self.interest_rate_percent / 12 / 100.0

    @param.depends("home_purchase_price", "monthly_property_tax")
    def prepaid_closing_costs(self):
        prepaid_interest_for_12_days = 12 * self.interest_rate_percent / 365 / 100.0
        return (
            15 * self.monthly_insurance
            + 4 * self.monthly_property_tax()
            + prepaid_interest_for_12_days
        )

    @param.depends("prepaid_closing_costs", "fixed_closing_costs")
    def estimated_closing_costs(self):
        return round(self.prepaid_closing_costs() + self.fixed_closing_costs)

    @param.depends(
        "home_purchase_price",
        "home_appraised_value",
        "total_available_for_closing",
        "estimated_closing_costs",
    )
    def down_payment(self):
        diff_between_purchase_price_and_appraisal = (
            self.home_purchase_price - self.home_appraised_value
        )

        return (
            self.total_available_for_closing
            - diff_between_purchase_price_and_appraisal
            - self.estimated_closing_costs()
        )

    @param.depends(
        "home_appraised_value",
        "down_payment",
    )
    def loan_amount(self):
        down_payment = self.down_payment()
        return round(self.home_appraised_value - down_payment)

    @param.depends("loan_amount", "mortgage_duration_years", "monthly_interest_rate")
    def mortgage(self):
        """
        M = P [ i(1 + i)^n ] / [ (1 + i)^n – 1]
        P = principal loan amount
        i = monthly interest rate
        n = number of months required to repay the loan
        """
        # Assume monthly payments
        number_of_payments = self.mortgage_duration_years * 12
        monthly_interest_rate = self.monthly_interest_rate()

        monthly_mortgage_payment = self.loan_amount() * (
            monthly_interest_rate
            * (1 + monthly_interest_rate) ** number_of_payments
            / (((1 + monthly_interest_rate) ** number_of_payments) - 1)
        )
        return round(monthly_mortgage_payment)

    @param.depends("property_tax_rate_percent", "home_appraised_value")
    def monthly_property_tax(self):
        return round(
            self.property_tax_rate_percent * self.home_appraised_value / 12 / 100.0
        )

    @param.depends("mortgage", "hoa", "monthly_insurance", "monthly_property_tax")
    def monthly_estimated_payments(self):
        return round(
            self.mortgage()
            + self.hoa
            + self.monthly_insurance
            + self.monthly_property_tax()
        )

    @param.depends("mortgage")
    def amortization_schedule(self):
        number_of_payments = self.mortgage_duration_years * 12
        monthly_interest_rate = self.monthly_interest_rate()
        months = range(1, number_of_payments + 1)

        mortgage = self.mortgage()
        principal_left = self.loan_amount()

        amortization = []

        for month in months:
            interest_paid_in_month = principal_left * monthly_interest_rate
            principal_paid_in_month = (
                min(mortgage, principal_left + interest_paid_in_month)
                - interest_paid_in_month
            )
            principal_left -= principal_paid_in_month

            amortization.append(
                {
                    "Month": month,
                    "Principal Left": round(principal_left),
                    "Interest Paid": round(interest_paid_in_month),
                    "Principal Paid": round(principal_paid_in_month),
                }
            )

        return pn.pane.DataFrame(
            pd.DataFrame.from_records(amortization), width=600, index=False
        )

    @param.depends("monthly_estimated_payments")
    def monthly_payments_pie_chart(self):
        payments = {
            "Mortgage": self.mortgage(),
            "Property Tax": self.monthly_property_tax(),
            "Home Insurance": self.monthly_insurance,
            "HOA": self.hoa,
        }
        return make_pie_chart(
            payments, f"Monthly Costs: ${self.monthly_estimated_payments()}"
        )

    @param.depends("estimated_closing_costs", "down_payment")
    def cost_at_closing_pie_chart(self):
        payments = {
            "Fixed Closing Costs": self.fixed_closing_costs,
            "Prepaid Closing Costs": self.prepaid_closing_costs(),
            "Down Payment": self.down_payment(),
            "Purchase Cost not Covered by Loan": self.home_purchase_price
            - self.home_appraised_value,
        }
        return make_pie_chart(
            payments, f"Costs at Closing: ${self.total_available_for_closing}"
        )


def make_pie_chart(key_values, title):
    data = (
        pd.Series(key_values)
        .reset_index(name="value")
        .rename(columns={"index": "Costs"})
    )
    data["angle"] = data["value"] / data["value"].sum() * 2 * 3.14
    data["color"] = Spectral10[: len(key_values)]

    p = figure(
        height=300,
        title=title,
        toolbar_location=None,
        tools="hover",
        tooltips="@Costs: @value{1}",
        x_range=(-0.5, 1.0),
    )

    p.wedge(
        x=0,
        y=1,
        radius=0.3,
        start_angle=cumsum("angle", include_zero=True),
        end_angle=cumsum("angle"),
        line_color="white",
        fill_color="color",
        legend_field="Costs",
        source=data,
    )

    p.axis.axis_label = None
    p.axis.visible = False
    p.grid.grid_line_color = None

    return p


explorer = MortgageCalculator(name="Mortgage Calculator")

pn.Row(
    explorer.param,
    pn.Column(
        pn.Row(
            pn.Column(
                explorer.cost_at_closing_pie_chart,
                explorer.monthly_payments_pie_chart,
            ),
        ),
        pn.pane.HTML("<h2>Amortization Schedule</h2>"),
        explorer.amortization_schedule,
    )
).servable()
Click Run or press shift + ENTER to run code.