Create A Mortgage Calculator using Python Pynecone and Plotly

Sharing is caring!

Last Updated on March 30, 2023 by Jay

I’ll show you how to create a sleek looking mortgage calculator using the Python Pynecone and Plotly libraries.

First, let me introduce you to the new pynecone library, which allows us to create a full stack web application using just Python. No HTML, CSS, or JavaScript, Python only. If this sounds too good to be true, keep reading to find out!

Mortgage Calculator built with Python
Mortgage Calculator built with Python

What is Pynecone?

Python is a popular language for web development, and you might have heard about Flask, Django, or FastAPI. Those are great web development frameworks; however, they focus on the backend while offer limited features to create the frontend user interface. On the other hand, Pynecone is a relatively new library for web development that allows us to create both the backend and frontend in Python. Pynecone was released around the end of 2022, so it’s still very new.

Under the hood, Pynecone will parse the Python code and compile it into a Nextjs / React application for the front end, and uses FastAPI for the backend.

The Pros

The biggest advantage of Pynecone is that you can create a full stack, modern looking webapp using just Python. You don’t need to know HTML, CSS, or JavaScript and you can still make a fully interactive webapp. This cuts down development time significantly and it helps prototype quickly.

The Cons

Pynecone is still in its infancy and many features are not mature. Some users reported performance issues compared to a native React application. It’s probably not an issue unless your webapp attracts a significant amount of traffic, which means for most websites it’s not an issue. The pynecone creators and community is aware of this issue and are currently working to improve it.

The other disadvantage is error message and debugging. This is actually tied to the advantage, which basically abstracts away all the frontend stuff. It means that if something goes wrong, it might become difficult to debug without any frontend (HTML/CSS/JS) knowledge. I think pynecone can improve this by adding better error messages.

What is Plotly?

Although the protagonist today is Pynecone, I still want to introduce Plotly, which also serves an important role in our application.

Simply put, plotly is one of the best data visualization tools in Python. You can make fully interactive graphs with it. Under the hood, plotly uses JavaScript to create beautiful charts. We also don’t have to know JavaScript to use plotly, only Python 🙂

Installing Libraries

pip install plotly
pip install pynecone

Starting a Pynecone Project

Similar to most web development framework, we need to create a project using command line input that comes with the library installation. It’s very straightforward. We are going to create a mortgage calculator, so I’m going to create folder named “mortgage_calculator” by using mkdir, then cd into the folder and create/initialize the project like the following:

mkdir my_app_name
cd my_app_name
pc init

It will take a little time for pynecone to set up everything. Once done, type pc run to start running the webapp.

pc run

Inside our pynecone project folder “mortgage_calculator”, we should see the following files and folders structure. For starters, we don’ have to touch anything inside the .web and assets folders. The only file we need to edit is the mortgage_calculator.py inside the mortgage_calculator sub-folder.

pynecone folder structure
pynecone project folder structure

In other words, the main script we need to edit is:

/my_app/my_app/my_app.py

It feels repeating but a lot of web dev frameworks do this so it’s quite common practice. Pynecone will generate some default code inside the main script, we can delete everything and write our own code. Let’s start with the frontend stuff since we can see the results immediately!

Setting up the Frontend

While playing with pynecone, I came across a great YouTube channel called Line Indent. Part of the frontend user interface was inspired (read: copied) by one of his tutorials. I recommend checking out his YouTube if you are into webapp or mobile app development using Python.

I’m going to show you the bare minimum code to get the frontend running, then we’ll add logic for the backend.

Copy and paste the following code into mortgage_calculator.py file, then type pc run in the terminal to start the app.

from pcconfig import config
import pynecone as pc

class State(pc.State):
    """The app state."""
    pass


def get_input_field(icon: str, placeholder: str, _type:str):
    return pc.container(
        pc.hstack(
            pc.icon(
                tag=icon,
                color='white',
                fontSize='11px',
            ),
            pc.input(
                placeholder=placeholder,
                border = '0px',
                focus_border_color='None',
                color='white',
                fontWeight='semibold',
                fontSize='11px',
                type=_type,
                #on_change = on_change
            ),
        ),
        borderBottom="0.1px solid grey",
        width="200px",
        height='45px',
    )
    
    


def index() -> pc.Component:
    calculator_container = pc.hstack(
        pc.box(width = "10%"),
        pc.box(pc.container(
            pc.vstack(
                pc.container(height="65px"),
                pc.container(
                    pc.text(
                        "Mortgage Calculator",
                        fontSize="28px",
                        color="white",
                        fontWeight="bold",
                        letterSpacing="3px"
                    ),
                    width="400px",
                    center_content=True,
                ),
                
                pc.container(
                    pc.text(
                        "A pynecone app example",
                        fontSize="12px",
                        color="white",
                        fontWeight="#eeeeee",
                        letterSpacing="0.25px"
                    ),
                    width="400px",
                    center_content=True,
                ),
                pc.container(height="50px"),
                get_input_field('star', 'Total Amount', ''),
                get_input_field('star', 'Down Payment', ''),
                get_input_field('star', 'Interest Rate', ''),
                get_input_field('star', 'Amortization Period', ''),        
               
                pc.container(height='55px'),
                pc.container(
                    pc.button(
                        pc.text(
                            'Calculate',
                            color='white',
                            fontSize='11px',
                            weight='bold',
                        ),
                        width='200px',
                        height='45px',
                        color_scheme='blue',
                    ),
                    center_content=True
                ),
                ),
            width = "400px",
            height = "75vh",
            center_content=True,
            bg = "#1D2330",
            borderRadius = '15px',
            boxShadow="41px -41px 82px #0d0f15, -41px 41px 82px #2d374b"), width="30%"),
        
        pc.box(
            pc.container(
                # pc.plotly(data=State.mortgage_schedule_fig, layout={'barmode':'stack', 'paper_bgcolor':'#1D2330', 'plot_bgcolor':'#1D2330', 
                #                                                     'font':{'color':"white",
                #                                                     'size':'20',},
                                                                    
                #                                                     'width':'800'}, width='100%'),
                center_content = True,
                justifyContent= "center",
            ),
            width="50%"),
        pc.box(width="10%"),
        width = "100%"
    )
        
    
    _main = pc.container(
        calculator_container,
        center_content = True,
        justifyContent= "center",
        maxWidth="auto",
        height = "100vh",
        bg="#1D2330",
    )
    
    return _main


# Add state and page to the app.
app = pc.App(state=State)
app.add_page(index)
app.compile()

If a browser didn’t open up automatically, you can type http://127.0.0.1:3000/ into a browser to view the project. By default, pynecone runs on the localhost and 3000 port. You should see something like this:

Mortgage Calculator User Interface
Mortgage Calculator User Interface

Isn’t that cool? A sleek looking website created using only Python. To be fair, if you look close enough, a lot of the Python code actually resembles HTML or CSS (container, width, height, etc.), but the syntax is still Python 🙂

Right now, the button doesn’t work because we haven’t done anything for the backend logic. Let’s add it next.

Setting up the Backend

A crucial piece of information for our mortgage calculator Python pynecone webapp is the actual mortgage amortization calculation. The relevant terminologies here are “annuity factor” and “mortgage amortization schedule”. We need to first determine the annuity factor, then calculate the monthly payment, and then derive the mortgage amortization schedule.

Mortgage Calculation 101

I appreciate that not everyone has a finance background, so I will try to explain what’s happening on a high level. Note our example app works only for fixed mortgages, not variable.

As with most mortgages, we pay a fixed amount every month until the end of the mortgage term, e.g. 30 years. We need to calculate the fixed payment, such as the total value of all future payments in today’s dollar equals to the total mortgage amount today. The theory is called “time value of money” if you want to look it up, but you can trust me on this, because I’m an actuary 😀

Present Value Annuity Factor Formula
Annuity factor formula

Above is the annuity factor formula, which represents the sum of all payments per $1. So it means:

Mortgage amount = Monthly payment * annuity factor

Once we solve for the monthly payment, the rest is a breeze.

  • Interest per period = Mortgage balance at the beginning of period * interest rate
  • Principal paid = Monthly payment – Interest paid
  • New Mortgage balance at the end of period = Mortgage balance (beginning) – Principal paid

Enough of theory, we can code the mortgage amortization schedule in Python with the following code.

I put the following code inside a separate script “mortgage_scheduled.py“. A word of caution here – the generate_mortgage_schedule function is not yet vectorized so it’s not the most efficient. I’m okay with it for now since a mortgage is generally 30 years or 360 months, which is not a lot to compute.

import pandas as pd


def calculate_monthly_payment(total_amount, down_payment, interest_rate, amortization_period):
    total_amount = float(total_amount)
    amortization_period = int(amortization_period)
    down_payment = float(down_payment)
    interest_rate = float(interest_rate)
    total_amount -= down_payment
    if (interest_rate > 0) and (amortization_period > 0):
        monthly_interest_rate = interest_rate / 12
        payment = total_amount * (monthly_interest_rate * (1 + monthly_interest_rate) ** amortization_period) / ((1 + monthly_interest_rate) ** amortization_period - 1)
        return payment
    return 0


def generate_mortgage_schedule(total_amount, down_payment, interest_rate, amortization_period):
    total_amount = float(total_amount)
    amortization_period = int(amortization_period)*12
    down_payment = float(down_payment)
    interest_rate = float(interest_rate)
    
    monthly_payment = calculate_monthly_payment(total_amount,down_payment, interest_rate, amortization_period)
    remaining_balance = total_amount - down_payment
    mortgage_schedule = []

    for month in range(1, amortization_period + 1):
        interest_payment = remaining_balance * (interest_rate / 12)
        principal_payment = monthly_payment - interest_payment
        remaining_balance -= principal_payment

        mortgage_schedule.append({
            'Month': month,
            'Principal Payment': principal_payment,
            'Interest Payment': interest_payment,
            'Total Payment': monthly_payment,
            'Remaining Balance': remaining_balance
        })

    return mortgage_schedule

We can now bring the mortgage schedule calculation into the app by importing it into the mortgage_calculator.py file.

Pynecone State For Mortgage Calculator

We can think of State as a collection of all the variables and functions that can change in the web application. In our mortgage calculator, the State should include the following variables:

  • Total amount, down payment, and the mortgage amount
  • Interest rate
  • Amortization period
  • The plotly chart for visualization

So we need to declare all those variables inside the State class like the following. Note each variable and function declaration requires a type hint.

Although pure Python does not require type hinting, it’s a good practice to have, also helps reduces errors and debugging.

class State(pc.State):
    """The app state."""
    total_amount: float = 0
    down_payment: float = 0
    interest_rate: float = 0
    amort_period: int = 0
    monthly_amount: float = 0
    mortgage_amount: float = 0
    #mortgage_schedule: pd.DataFrame
    mortgage_schedule_fig: go.Figure = None
    
    def get_data(self)-> go.Figure:
        mortgage_schedule = generate_mortgage_schedule(self.total_amount, self.down_payment, self.interest_rate, self.amort_period)
        mortgage_schedule_df = pd.DataFrame(mortgage_schedule)
        #print(mortgage_schedule_df.shape)
        if mortgage_schedule_df.shape != (0,0):
            
            print(mortgage_schedule_df)
            fig = go.Figure(data = [
                go.Bar(name='Principal', x=mortgage_schedule_df['Month'], y=mortgage_schedule_df['Principal Payment']),
                go.Bar(name='Interest', x=mortgage_schedule_df['Month'], y=mortgage_schedule_df['Interest Payment']),
                ],)
            
            self.mortgage_schedule_fig = fig
            self.mortgage_amount = float(self.total_amount) - float(self.down_payment)
            self.monthly_amount = round(mortgage_schedule_df['Total Payment'][0],2)
        

Connecting Backend and Frontend

Now we have a place (State) to store all the variables, we can connect the backend and frontend. When users enter a value on the web interface, it should pass it to the backend for calculation.

Pynecone makes passing variables from user interface to the backend really easy. Note all of our input fields are text. We can include an additional parameter “on_change” in the the pc.input() method. To re-use code, we have created a new function to wrap around pc.input(), here’s what it looks like:

def get_input_field(icon: str, placeholder: str, _type:str, on_change = None):
    return pc.container(
        pc.hstack(
            pc.icon(
                tag=icon,
                color='white',
                fontSize='11px',
            ),
            pc.input(
                placeholder=placeholder,
                border = '0px',
                focus_border_color='None',
                color='white',
                fontWeight='semibold',
                fontSize='11px',
                type=_type,
                on_change = on_change
            ),
        ),
        borderBottom="0.1px solid grey",
        width="200px",
        height='45px',
    )

And inside the index() function. We just need to include the on_change arguments.

To way to set variables in State is done like the following. Note the syntax – State.set_var. With <var> is any variable we want to include in the app State.

get_input_field('star', 'Total Amount', '', on_change=State.set_total_amount),
get_input_field('star', 'Down Payment', '', on_change=State.set_down_payment),
get_input_field('star', 'Interest Rate', '', on_change=State.set_interest_rate),
get_input_field('star', 'Amortization Period', '', on_change=State.set_amort_period),  

We also make a small tweak to the button UI code by adding an “on_click” argument to tell the app to refresh the plotly chart by calling the “get_data()” function when we click on the button.

pc.button(
                        pc.text(
                            'Calculate',
                            color='white',
                            fontSize='11px',
                            weight='bold',
                        ),
                        on_click=State.get_data,
                        width='200px',
                        height='45px',
                        color_scheme='blue',
                    )

Then we add the plotly chart into the interface, along with some text informing users about the mortgage details:

pc.box(
            pc.text('Total mortgage amount: $' + State.mortgage_amount),
            pc.text('Amortization Period: ' + State.amort_period * 12 + ' months.'),
            pc.text('Your monthly payment is: $' + State.monthly_amount),
            pc.container(
                pc.plotly(data=State.mortgage_schedule_fig, layout={'barmode':'stack', 'paper_bgcolor':'#1D2330', 'plot_bgcolor':'#1D2330', 
                                                                    'font':{'color':"white",
                                                                    'size':'20',},
                                                                    
                                                                    'width':'800'}, width='100%'),
                center_content = True,
                justifyContent= "center",
            ),
            color='white',
            width="50%"),

A few notes on formatting Plotly with Pynecone

If you are familiar with plotly, you might want to use the fig.update_layout() function to customize your chart. However, that won’t work with Pynecone!

Note the get_data() function where I created the plotly go.Figure object. I intentionally left out any styling/customization because it doesn’t work (as of writing). This might change as the pynecone frames becomes more mature.

Currently, the only way to style a plotly chart is by using the layout argument inside the pc.plotly() method. As illustrated in the code snippet above. Additionally, you have to use the nested data structure. For example, instead of {“font_color”:”white”, “font_size”:”20″}, you have to use:

{"font": {"color":"white", "size":"20"}}

I’ve only tried font related styles in this case, but I image it will be similar for other parameters if you want to style a plotly chart.

There you go, a fully functional, full stack React web app created using only Python!

A Mortgage Calculator Written in Python Pynecone – Full Code

You can find the full code here: https://github.com/pythoninoffice/tutorials/tree/main/pynecone_examples/mortgage_calculator

Additional Resources

Chatgpt in VScode

Leave a Reply

Your email address will not be published. Required fields are marked *