diff options
-rw-r--r-- | mortgage.html | 68 | ||||
-rw-r--r-- | mortgage.js | 74 | ||||
-rw-r--r-- | mortgage.py | 55 | ||||
-rw-r--r-- | requirements.txt | 1 |
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. | ||
2 | function round(x) { | ||
3 | return Math.ceil(x*100)/100; | ||
4 | } | ||
5 | |||
6 | /// Format the float as a string using two decimal places. | ||
7 | function fmt(x) { | ||
8 | return round(x).toFixed(2); | ||
9 | } | ||
10 | |||
11 | /// Clear all rows in the table except for the first row (header). | ||
12 | function 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. | ||
19 | function 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. | ||
27 | function 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 | |||
65 | function 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 | |||
74 | update(); | ||
diff --git a/mortgage.py b/mortgage.py new file mode 100644 index 0000000..3b9b958 --- /dev/null +++ b/mortgage.py | |||
@@ -0,0 +1,55 @@ | |||
1 | import argparse | ||
2 | import math | ||
3 | import sys | ||
4 | from tabulate import tabulate, SEPARATING_LINE | ||
5 | |||
6 | |||
7 | def 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 | |||
42 | def 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 | |||
54 | if __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 | |||