summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mortgage.html68
-rw-r--r--mortgage.js74
-rw-r--r--mortgage.py55
-rw-r--r--requirements.txt1
4 files changed, 198 insertions, 0 deletions
diff --git a/mortgage.html b/mortgage.html
new file mode 100644
index 0000000..07de66b
--- /dev/null
+++ b/mortgage.html
@@ -0,0 +1,68 @@
1<!DOCTYPE html>
2<html>
3
4<head>
5 <title>Mortgage Calculator</title>
6 <style>
7 #parameters {
8 margin-top: 2em;
9 display: grid;
10 grid-template-columns: 10em min-content;
11 column-gap: 1em;
12 row-gap: 0.5em;
13 }
14 #parameters div {
15 text-align: right;
16 }
17 #parameters input {
18 width: 12em;
19 text-align: right;
20 }
21 #results {
22 margin-top: 2em;
23 }
24 #results-table {
25 border: 1px solid;
26 border-collapse: collapse;
27 }
28 #results-table th {
29 padding-top: 0.5em;
30 padding-bottom: 0.5em;
31 }
32 #results-table th, td {
33 text-align: center;
34 padding-left: 1em;
35 padding-right: 1em;
36 }
37 #results-table tr:nth-child(even){
38 background-color: #f2f2f2;
39 }
40 #results-table tr:hover {background-color: #ddd;}
41 </style>
42</head>
43
44<body>
45 <h1>Mortgage Calculator</h1>
46 <div id="parameters">
47 <div>Principal</div>
48 <div><input id="principal" type="number" value="100000" onchange="update()"></div>
49 <div>Annual Interest (%)</div>
50 <div><input id="interest" type="number" value="5.0" onchange="update()"></div>
51 <div>Installments</div>
52 <div><input id="installments" type="number" value="300" onchange="update()"></div>
53 </div>
54 <div id="results">
55 <table id="results-table">
56 <tr>
57 <th>Time</th>
58 <th>Outstanding Debt</th>
59 <th>Monthly Installment</th>
60 <th>Interest Paid</th>
61 <th>Amount Amortised</th>
62 </tr>
63 </table>
64 </div>
65 <script type="text/javascript" src="mortgage.js"></script>
66</body>
67
68</html>
diff --git a/mortgage.js b/mortgage.js
new file mode 100644
index 0000000..ffc1b1d
--- /dev/null
+++ b/mortgage.js
@@ -0,0 +1,74 @@
1/// Round up the given number two the nearest two decimal places.
2function round(x) {
3 return Math.ceil(x*100)/100;
4}
5
6/// Format the float as a string using two decimal places.
7function fmt(x) {
8 return round(x).toFixed(2);
9}
10
11/// Clear all rows in the table except for the first row (header).
12function clear_table(table) {
13 while (table.rows.length > 1) {
14 table.deleteRow(1);
15 }
16}
17
18/// Insert the values into a new row in the table.
19function insert_row(table, values) {
20 var row = table.insertRow(table.rows.length);
21 values.forEach((value) => {
22 row.insertCell(row.cells.length).innerHTML = value;
23 });
24}
25
26/// Compute the mortgage.
27function compute_mortgage(principal, annual_interest, installments, table) {
28 const p = principal;
29 const i = annual_interest / 100 / 12; // Monthly equivalent.
30 const n = installments;
31
32 const monthly_installment = p * (i * (1 + i)**n) / ((1 + i)**n - 1);
33
34 var debt = principal;
35 var total_interest = 0;
36 var total_amortised = 0;
37
38 clear_table(table);
39
40 for (k = 0; k < n; ++k) {
41 const interest = i * debt;
42 const amortised = monthly_installment - interest;
43
44 const values = [k.toString(), fmt(debt), fmt(monthly_installment), fmt(interest), fmt(amortised)];
45 insert_row(table, values);
46
47 total_interest += interest;
48 total_amortised += amortised;
49 debt -= amortised;
50 }
51
52 const total_interest_pct = total_interest / principal * 100;
53
54 insert_row(table, ["", "", "", "", ""]); // Separator.
55
56 insert_row(table, ["Total", "", "Amount Paid", "Interest Paid", "Amount Amortised"]);
57 insert_row(table, [
58 "",
59 "",
60 fmt(monthly_installment * n),
61 fmt(total_interest) + " (" + fmt(total_interest_pct) + "%)",
62 fmt(total_amortised)]);
63}
64
65function update() {
66 const principal = parseFloat(document.getElementById("principal").value);
67 const interest = parseFloat(document.getElementById("interest").value);
68 const installments = parseInt(document.getElementById("installments").value);
69 const results = document.getElementById("results-table");
70
71 compute_mortgage(principal, interest, installments, results);
72}
73
74update();
diff --git a/mortgage.py b/mortgage.py
new file mode 100644
index 0000000..3b9b958
--- /dev/null
+++ b/mortgage.py
@@ -0,0 +1,55 @@
1import argparse
2import math
3import sys
4from tabulate import tabulate, SEPARATING_LINE
5
6
7def calc_payments(principal, annual_interest, installments):
8 p = principal
9 i = annual_interest / 100 / 12 # Monthly equivalent.
10 n = installments
11
12 def fmt(x):
13 return f"{math.ceil(x*100)/100:.2f}"
14
15 table = [["Time", "Outstanding Debt", "Monthly Installment", "Interest Paid", "Amount Amortised"]]
16
17 monthly_installment = p * (i * (1 + i)**n) / ((1 + i)**n - 1)
18 debt = principal
19 total_interest = 0
20 total_amortised = 0
21
22 for k in range(n):
23 interest = i * debt
24 amortised = monthly_installment - interest
25 table.append([k] + [fmt(x) for x in [debt, monthly_installment, interest, amortised]])
26
27 total_interest += interest
28 total_amortised += amortised
29 debt -= amortised
30
31 total_interest_pct = total_interest / principal * 100
32
33 table.append(SEPARATING_LINE)
34 table.append(["Total", "", "Amount Paid", "Interest Paid", "Amount Amortised"])
35 table.append(["", ""] + [
36 fmt(monthly_installment * n),
37 fmt(total_interest) + f" ({fmt(total_interest_pct)}%)",
38 fmt(total_amortised)])
39 return table
40
41
42def main():
43 parser = argparse.ArgumentParser()
44 parser.add_argument("-p", "--principal", type=float, default=100000, help="Principal")
45 parser.add_argument("-i", "--interest", type=float, default=5, help="Annual interest %")
46 parser.add_argument("-n", "--installments", type=int, default=300, help="Number of installments")
47 args = parser.parse_args()
48
49 table = calc_payments(args.principal, args.interest, args.installments)
50 print(tabulate(table))
51 return 0
52
53
54if __name__ == "__main__":
55 sys.exit(main())
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..a5d5159
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
tabulate