A package for filtering real estate opportunities based on your financial situation
Project description
The Mortgage-Filter-Package
A Python package for analyzing real estate affordability based on your financial situation. Filter properties, calculate mortgage costs, and visualize how different financial parameters affect your home buying power.
A small note: this was one of the very first things I've ever uploaded to github. It's not complicated. It's simply a first naive attempt at developing a basic Python package for processing real estate data which instantly yields information on property affordability.
What it does:
- Filters property datasets by affordability based on your downpayment and monthly budget
- Calculates mortgage insurance, optimal payments, and total interest costs
- Provides detailed affordability metrics for each property
- Visualizes the relationship between interest rates, payments, and costs
Quick Links
Installation
pip install mortgage-pkg
Requirements:
- Python >= 3.7
- pandas
- numpy
Features
✅ Property Filtering - Find affordable properties from your dataset ✅ Mortgage Calculations - Compute interest, payments, and amortization periods ✅ Insurance Analysis - Calculate Canadian mortgage insurance requirements ✅ Cost Optimization - Determine optimal monthly payments to minimize interest ✅ Visual Analytics - Generate publication-quality visualizations with seaborn
Quick Start
Basic Usage
from mortgage_filter import property_filter
import pandas as pd
# Load your property data (2 columns: name/area, price)
properties = pd.read_csv('your_properties.csv')
# Filter by affordability
affordable = property_filter(
property_data=properties,
downpayment=190000,
mortgage_rate=2.8,
max_monthly_payment=4800
)
Output
The function returns a dataframe with detailed affordability metrics:
- Price - Listed property price
- Minimum_Downpayment - Legal minimum (5% of value)
- Mortgage_Insurance - Insurance cost when downpayment < 20%
- Principal - Loan amount (price - downpayment + insurance)
- Monthly_Payment - Optimal payment for shortest amortization
- Shortest_Amortization - Years to pay off with your budget
- Total_Interest - Cumulative interest over loan lifetime
- Net_Cost - Total cost (price + insurance + interest)
Visualizations
The package includes powerful visualization capabilities. See the interactive demo notebook for detailed examples.
Interest Rate Impact
How mortgage term length affects estimated interest rates
Monthly Payments vs Total Interest
How your monthly payment amount affects total interest paid over the life of the loan
Amortization Period Analysis
Relationship between monthly payments and loan duration
Mortgage Insurance by Downpayment
How downpayment size affects mortgage insurance (20% threshold for Canadian mortgages)
Complete Demo
For a comprehensive walkthrough with all features and visualizations, see the Interactive Demo Notebook.
The demo covers:
- 3 real-world affordability scenarios
- 5 detailed analysis questions with visualizations
- Vancouver, BC housing data examples
- Canadian mortgage insurance rules
Example: Vancouver Housing Data
properties = pd.read_csv('data/vancouver_testset.csv')
properties.head()
| Area | House Price | |
|---|---|---|
| 0 | Downtown & Westside | 3118200 |
| 1 | West Vancouver | 2743600 |
| 2 | North Vancouver | 1665100 |
| 3 | Richmond | 1581600 |
| 4 | South Burnaby | 1564000 |
Filtering Affordable Properties
from mortgage_filter import property_filter
# Scenario: Unknown interest rate, specify term length
results = property_filter(
property_data=properties,
downpayment=190000,
mortgage_term=15,
max_monthly_payment=4800
)
# Output: "You can afford 5 properties from the 25 you've provided."
| Property/Area | Price | Minimum_Downpayment | Mortgage_Insurance | Principal | Monthly_Payment | Shortest_Amortization | Total_Interest | Net_Cost | |
|---|---|---|---|---|---|---|---|---|---|
| 20 | Pitt Meadows | 974800 | 72480.0 | 18793.83 | 803593.83 | 4767.48 | 24 | 569440.84 | 1563034.67 |
| 21 | North Delta | 972500 | 72250.0 | 18702.26 | 801202.26 | 4753.29 | 24 | 567746.53 | 1558948.79 |
| 22 | Maple Ridge | 884200 | 63420.0 | 0.00 | 694200.00 | 4698.63 | 19 | 377088.77 | 1261288.77 |
| 23 | Abbotsford | 873600 | 62360.0 | 0.00 | 683600.00 | 4782.72 | 18 | 349446.77 | 1223046.77 |
| 24 | Mission | 726000 | 47600.0 | 0.00 | 536000.00 | 4662.46 | 13 | 191344.02 | 917344.02 |
We're left with what?
-
We're left with all the affordable home indexes as well as:
- the listed prices
- the minimum downpayment (5% of value)
- the mortgage insurance
- the principal (price - downpayment)
- the optimal monthly payment for the shortest amortization period (years)
- the cumulative interst
- the net cost
-
We note there's a printed warning saying that terms must be less than or equal to 10 years (banks don't typically offer terms beyond 10 years).
The function can handles the exception and extrapolates from the term to interest rate function; however, this could yield misleading interest rates.
Lets see what the function looks like (interest as a function of term length):
terms = np.arange(1, 15)
rate = [mort_rate(t) for t in terms]
plt.plot(terms, rate)
plt.xlabel('Terms'); plt.ylabel('Interest Rate as %')
plt.axvline(x = 10, linestyle='--', color = 'black')
plt.grid()
plt.savefig('output_7_1.svg', transparent = False, format = 'svg')
plt.show()
Lengths greater than 10 years are not typically available.
Terms must range from 1 to 10 years, but calculation will be performed anyway.
Lengths greater than 10 years are not typically available.
Terms must range from 1 to 10 years, but calculation will be performed anyway.
Lengths greater than 10 years are not typically available.
Terms must range from 1 to 10 years, but calculation will be performed anyway.
Lengths greater than 10 years are not typically available.
Terms must range from 1 to 10 years, but calculation will be performed anyway.
Okay, now lets try using a mortgage rate of our own, say 2.8%.
property_filter(property_data = properties,
downpayment = 190000,
mortgage_rate = 2.8,
max_monthly_payment = 4800)
You can afford 5 properties from the 25 you've provided.
| Property/Area | Price | Minimum_Downpayment | Mortgage_Insurance | Principal | Monthly_Payment | Shortest_Amortization | Total_Interest | Net_Cost | |
|---|---|---|---|---|---|---|---|---|---|
| 20 | Pitt Meadows | 974800 | 72480.0 | 18793.83 | 803593.83 | 4740.54 | 18 | 220350.90 | 1213944.73 |
| 21 | North Delta | 972500 | 72250.0 | 18702.26 | 801202.26 | 4726.43 | 18 | 219695.22 | 1210897.48 |
| 22 | Maple Ridge | 884200 | 63420.0 | 0.00 | 694200.00 | 4727.53 | 15 | 156743.49 | 1040943.49 |
| 23 | Abbotsford | 873600 | 62360.0 | 0.00 | 683600.00 | 4655.34 | 15 | 154350.26 | 1027950.26 |
| 24 | Mission | 726000 | 47600.0 | 0.00 | 536000.00 | 4722.69 | 11 | 87383.70 | 813383.70 |
Notice how the last 4 columns have lower costs now.
- Lets try one more time with a high downpayment/low monthly payment scenario.
- Lets also assume a loan limit of $600,000 and $700,000 to see how this might affect a buying decision.
property_filter(property_data = properties,
downpayment = 500000,
mortgage_rate = 2.8,
max_monthly_payment = 3200,
max_loan = 600000).head()
You can afford 12 properties from the 25 you've provided.
| Property/Area | Price | Minimum_Downpayment | Mortgage_Insurance | Principal | Monthly_Payment | Shortest_Amortization | Total_Interest | Net_Cost | |
|---|---|---|---|---|---|---|---|---|---|
| 13 | Langley | 1090800 | 218160.0 | 0 | 590800 | 3103.53 | 21 | 191282.32 | 1282082.32 |
| 14 | Cloverdale | 1087400 | 217480.0 | 0 | 587400 | 3199.21 | 20 | 180410.55 | 1267810.55 |
| 15 | Central Surrey | 1086300 | 217260.0 | 0 | 586300 | 3193.22 | 20 | 180065.18 | 1266365.18 |
| 16 | Ladner | 1042000 | 208400.0 | 0 | 542000 | 3197.35 | 18 | 148627.69 | 1190627.69 |
| 17 | Port Coquitlam | 1034400 | 206880.0 | 0 | 534400 | 3152.52 | 18 | 146536.03 | 1180936.03 |
property_filter(property_data = properties,
downpayment = 500000,
mortgage_rate = 2.8,
max_monthly_payment = 3200,
max_loan = 700000).head()
You can afford 14 properties from the 25 you've provided.
| Property/Area | Price | Minimum_Downpayment | Mortgage_Insurance | Principal | Monthly_Payment | Shortest_Amortization | Total_Interest | Net_Cost | |
|---|---|---|---|---|---|---|---|---|---|
| 11 | Tsawwassen | 1153300 | 230660.0 | 0 | 653300 | 3117.86 | 24 | 244644.99 | 1397944.99 |
| 12 | New Westminster | 1127200 | 225440.0 | 0 | 627200 | 3184.83 | 22 | 213587.50 | 1340787.50 |
| 13 | Langley | 1090800 | 218160.0 | 0 | 590800 | 3103.53 | 21 | 191282.32 | 1282082.32 |
| 14 | Cloverdale | 1087400 | 217480.0 | 0 | 587400 | 3199.21 | 20 | 180410.55 | 1267810.55 |
| 15 | Central Surrey | 1086300 | 217260.0 | 0 | 586300 | 3193.22 | 20 | 180065.18 | 1266365.18 |
Note that no mortgage insurance is applied.
In Canada, downpayments >20% of the home price do not require mortgage insurance.
If you'd like to dive deeper at one scenario in particular, base functions could be used as follows:
- How quickly does cumulative interest increase as the mortgage rate increases?
- How does cumulative interest change as monthly contributions increase??
- How does the amortization period change as as monthly contributions increase?
- What's my monthly payment on a $500,000 home for a 20 year amortization on 2% interest?
- How does mortgage insurance change with downpayment?
# Parameters
princ = 500000 # principal of 500k
mth_pay = 2000 # monthly payment of 2k
mort_rate = 2.0 # mortgage rate of 2%
# Question 1
rate = np.linspace(1, 5, 20)
interest = [total_interest(princ, mortgage_rate = i, monthly_payment = mth_pay) for i in rate]
plt.plot(rate, interest)
plt.ticklabel_format(axis='y', style='plain')
plt.xlabel('Interest Rate as %'); plt.ylabel('Cumulative Interest'); plt.grid()
plt.savefig('output_15_1.svg', transparent = False, format = 'svg')
plt.show()
Monthly contribution is insufficient to pay off the original Principal.
# Question 2
payments = np.linspace(800, 6000, 20)
interest = [total_interest(princ, mort_rate, monthly_payment = p) for p in payments]
plt.plot(payments, interest)
plt.ticklabel_format(axis='y', style='plain')
plt.xlabel('Monthly Payment Amount'); plt.ylabel('Cumulative Interest'); plt.grid()
plt.savefig('output_16_1.svg', transparent = False, format = 'svg')
plt.show()
Monthly contribution is insufficient to pay off the original Principal.
- Note that as the monthly payment increases, not only does cumulative interest decrease, but the amortization period decreases substantially but this isn't see in the plot above.
- Lets just peek at how the amortization period decreases.
# Question 3
years = [optimal_monthly_payment(princ, mort_rate, max_monthly_payment = p)[1] for p in payments]
plt.plot(payments, years)
plt.xlabel('Max Monthly Payment'); plt.ylabel('Years to Pay Off'); plt.grid()
plt.savefig('output_18_1.svg', transparent = False, format = 'svg')
plt.show()
# Question 4
monthly_payment(princ, mort_rate, amortization = 20)
2529.42
# Question 5
downpayment_size = np.linspace(22000, 125000, 28)
mort_ins = [mortgage_insurance(princ, d) for d in downpayment_size]
plt.plot(downpayment_size, mort_ins)
plt.xlabel('Downpayment on $500,000'); plt.ylabel('Mortgage Insurance'); plt.grid()
plt.savefig('output_20_1.svg', transparent = False, format = 'svg')
plt.show()
Downpayment must be at least 5% the asset value
Input value is too low to be legally considered.
- Notice how a message prints for downpayments less than 5% the property value (minimal downpayment).
- Also notice how at a downpayment of 20% mortgage insurance no longer applies.
Documentation
All functions include detailed docstrings. Use Python's help() function for inline documentation:
help(mortgage_filter)
Help on module mortgage_filter.mortgage_filter in mortgage_filter:
NAME
mortgage_filter.mortgage_filter - # coding: utf-8
FUNCTIONS
property_filter(property_data, downpayment, mortgage_rate=None, mortgage_term=None, max_monthly_payment=None, max_loan=None)
Given a dataframe of properties, their prices, and some basic financial information, it returns a dataframe with only the affordable properties and other affordability metrics (ie. how long it would take to pay off, monthly payments, total interest, etc.).
| Arguments |
|---|
| data : dataframe |
| Areas/properties in column index 0 (str) |
| Respective prices in column index 1 (numeric) |
downpayment : numeric
Your maximal possible downpayment
mortgage_rate : numeric
Interest rate on the mortgage loan (leave empty if mortgage_term is provided)
mortgage_term : int
Contract length in years (1 to 10) for the mortgage interest rate.
Only specify if you do not know what mortgage_rate to enter (leave empty if mortgage_rate provided)
max_monthly_payment : numeric
Your max affordable or bank limited monthly payment towards your home
max_loan : numeric
Max eligible loan based on your downpayment
| Return |
|---|
| dataframe |
| Properties/Areas |
| Prices/Average area price |
| Minimum_Downpayment |
| Mortgage_Insurance |
| Principal |
| Monthly_Payment |
| Shortest_Amortization |
| Total_Interest |
| Net_Cost (assuming no other fees) |
help(total_interest)
Help on function total_interest in module mortgage_filter.mortgage_base:
total_interest(principal, mortgage_rate, monthly_payment)
Returns the cumulative interest paid on a given principal, mortgage rate, and monthly payment.
| Arguments |
|---|
| principal : numeric |
mortgage_rate : float
Annual mortgage rate (loan interest)
amortization: int
Amortization period in years (or in months if months == True)
monthly_payment : bool
Monthly contribution towards the principal
| Return |
|---|
| float |
| Cumulative interest paid |
help(optimal_monthly_payment)
Help on function optimal_monthly_payment in module mortgage_filter.mortgage_base:
optimal_monthly_payment(principal, mortgage_rate, max_monthly_payment)
Returns the first amortization period which has a monthly payment
less than your max_monthly_payment (ie. within budget). The shortest
possible amortization period has the lowest long term interest cost.
| Arguments |
|---|
| principal : numeric |
mortgage_rate : float
Annual mortgage rate (loan interest)
max_monthly_payment: numeric
Your max affordable monthly contribution
| Return |
|---|
| list |
| mp: monthly payment for a given amortization |
| i: amortization period in years |
help(mortgage_insurance)
Key Features & Canadian Mortgage Rules
Mortgage Insurance (CMHC)
- Required when downpayment < 20% of home value
- Not required when downpayment ≥ 20% of home value
- Insurance rates vary by loan-to-value ratio (calculated automatically)
Interest Rate Estimation
- Estimates rates based on mortgage term length (1-10 years typical)
- Uses regression model fitted to Canadian mortgage rate data
- Terms > 10 years use extrapolation (less accurate)
Optimization
- Calculates optimal monthly payment for shortest amortization
- Minimizes total interest paid over loan lifetime
- Respects your maximum monthly budget constraint
Testing & CI/CD
- ✅ Comprehensive test suite with >95% coverage
- ✅ Automated CI testing via GitHub Actions
- ✅ Compatible with Python 3.7-3.11
- ✅ Tests run on every commit to
main
Run tests locally:
python TEST_SUITE.py
Contributing & Support
Questions or feature requests? Contact: luka.vuko@outlook.com
Found a bug? Open an issue
Want to contribute? Pull requests are welcome!
License
MIT License - see LICENSE.md for details
Acknowledgments
This project was created to better understand the home buying process and associated costs. It's designed for educational purposes and personal financial planning.
Disclaimer: This tool provides estimates based on typical Canadian mortgage scenarios. Always consult with a qualified mortgage professional for official financial advice.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mortgage_pkg-1.0.1.tar.gz.
File metadata
- Download URL: mortgage_pkg-1.0.1.tar.gz
- Upload date:
- Size: 19.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
36a0139b863b502981554d479399a39aab5d7af374ea26e42f28dc5db4b40d01
|
|
| MD5 |
29782f663bbc29603524bda477ff80df
|
|
| BLAKE2b-256 |
5389a9220e5741b0be296cbd1e6ffe7bbd925d4ee5c61307e9a4bb95f19cd43a
|
File details
Details for the file mortgage_pkg-1.0.1-py3-none-any.whl.
File metadata
- Download URL: mortgage_pkg-1.0.1-py3-none-any.whl
- Upload date:
- Size: 13.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
25e0b611412eb5a347bbd7184c52aeb006dd905048b01c216541fc8d8bb75d40
|
|
| MD5 |
5cec6e468e790c66deb22d9af3c3aec8
|
|
| BLAKE2b-256 |
97acc523a4619351944ad3dcbd75e106f28bd8c2602b28a1640eea2d4019353c
|