Skip to content

Commit 639344e

Browse files
authored
Add amortization calculator to financial folder
1 parent 788d95b commit 639344e

File tree

1 file changed

+60
-0
lines changed

1 file changed

+60
-0
lines changed

financial/amortization.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
def level_payment(principal, annual_rate_pct, years, payments_per_year=12):
2+
if principal <= 0:
3+
raise ValueError("principal must be > 0")
4+
if years <= 0 or payments_per_year <= 0:
5+
raise ValueError("years and payments_per_year must be > 0")
6+
r = (annual_rate_pct / 100.0) / payments_per_year
7+
n = years * payments_per_year
8+
if r == 0:
9+
return principal / n
10+
factor = (1 + r) ** n
11+
return principal * (r * factor) / (factor - 1)
12+
13+
14+
def amortization_schedule(principal, annual_rate_pct, years, payments_per_year=12, print_annual_summary=False, eps=1e-9):
15+
pmt = level_payment(principal, annual_rate_pct, years, payments_per_year)
16+
r = (annual_rate_pct / 100.0) / payments_per_year
17+
n = years * payments_per_year
18+
19+
balance = float(principal)
20+
schedule = []
21+
22+
if print_annual_summary:
23+
print(f"{'Year':<6}{'Months Pd':<12}{'Tenure Left':<13}{'Payment/Period':<16}{'Outstanding':<14}")
24+
25+
for period in range(1, n + 1):
26+
interest = balance * r
27+
principal_component = pmt - interest
28+
29+
# shortpay on the last period if the scheduled principal would overshoot
30+
if principal_component > balance - eps:
31+
principal_component = balance
32+
payment_made = interest + principal_component
33+
else:
34+
payment_made = pmt
35+
36+
if principal_component < 0 and principal_component > -eps: # clamp tiny negatives
37+
principal_component = 0.0
38+
39+
balance = max(0.0, balance - principal_component)
40+
schedule.append([period, payment_made, interest, principal_component, balance])
41+
42+
# streamline for all time periods (monthly/quarterly/biweekly/weekly)
43+
months_elapsed = int(round((period * 12) / payments_per_year))
44+
45+
if print_annual_summary and (months_elapsed % 12 == 0 or balance <= eps):
46+
tenure_left_periods = n - period
47+
print(f"{months_elapsed // 12:<6}{months_elapsed:<12}{tenure_left_periods:<13}{pmt:<16.2f}{balance:<14.2f}")
48+
49+
if balance <= eps:
50+
break
51+
52+
# normalize any tiny residual
53+
if schedule and schedule[-1][4] <= eps:
54+
schedule[-1][4] = 0.0
55+
56+
return round(pmt, 4), schedule
57+
58+
59+
pmt, sched = amortization_schedule(10000, 5.5, 15, payments_per_year=12, print_annual_summary=True)
60+
print(pmt)

0 commit comments

Comments
 (0)