Skip to content

Commit

Permalink
continue work on adding sinking fund features
Browse files Browse the repository at this point in the history
  • Loading branch information
genedan committed Aug 31, 2020
1 parent 0fec08a commit 6c3c59b
Showing 1 changed file with 146 additions and 28 deletions.
174 changes: 146 additions & 28 deletions tmval/loan.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from math import ceil, floor
from math import ceil
from typing import Union

from tmval.annuity import Annuity, olb_r, olb_p, get_loan_pmt
Expand All @@ -24,32 +24,77 @@ def __init__(
self.term = term
self.gr = standardize_acc(gr)
self.cents = cents
self.sfr = sfr
if sfr:
self.sfr = standardize_acc(sfr)
else:
self.sfr = None
self.sfd = sfd

if amt is None:

ann = Annuity(
period=self.period,
term=self.term,
gr=self.gr,
amount=self.pmt
)

self.amt = ann.pv()
if sfr is None:
ann = Annuity(
period=self.period,
term=self.term,
gr=self.gr,
amount=self.pmt
).pv()
else:
a_n = Annuity(
period=self.period,
term=self.term,
gr=self.sfr,
amount=1
).pv()

sf_i = self.gr.effective_interval(t2=self.period)
sf_j = self.sfr.effective_interval(t2=self.period)
print(a_n)
print(sf_i)
print(sf_j)
ann = self.pmt * (a_n / (((sf_i - sf_j) * a_n) + 1))

self.amt = ann
else:
self.amt = amt

if pmt is None:
self.pmt_sched = self.get_payments()
self.pmt = self.pmt_sched.amounts[0]
if period and term:
self.pmt_sched = self.get_payments()
self.pmt = self.pmt_sched.amounts[0]

if pmt and period and term:
n_payments = ceil(term / period)
self.pmt_sched = Payments(
times=[(x + 1) * period for x in range(n_payments)],
amounts=[pmt for x in range(n_payments)]
)

def get_payments(self):
interest_due = self.gr.effective_rate(self.period) * self.amt
n_payments = ceil(self.term / self.period)

if self.sfr:
n_payments = ceil(self.term / self.period)
amt = self.gr.effective_rate(self.period) * self.amt
pmts = Payments(amounts=[amt] * n_payments, times=[(x + 1) * self.period for x in range(n_payments)])
if self.sfd is not None:

final_pmt = self.sf_final()
pmts = Payments(
amounts=[self.sfd + interest_due for x in range(n_payments - 1)] + [final_pmt],
times=[(x + 1) * self.period for x in range(n_payments)]
)

else:

sv_ann = Annuity(
gr=self.sfr.effective_rate(self.period),
period=self.period,
term=self.term
).sv()
sfd = self.amt / sv_ann
amt = interest_due + sfd
pmts = Payments(
amounts=[amt] * n_payments,
times=[(x + 1) * self.period for x in range(n_payments)]
)

else:

Expand Down Expand Up @@ -143,6 +188,10 @@ def amortize_payments(self, payments: Payments) -> dict:
res['principal_paid'] += [principal_paid]
res['remaining_balance'] += [principal]

if self.cents:
for k, v in res.items():
res[k] = [round(x, 2) if isinstance(x, float) else x for x in v]

return res

def principal_paid(self, t2: float, t1: float = 0):
Expand Down Expand Up @@ -173,24 +222,93 @@ def amortization(self):

res = self.amortize_payments(payments=self.pmt_sched)

if self.cents:
for k, v in res.items():
res[k] = [round(x, 2) if isinstance(x, float) else x for x in v]

return res

def sf_final(self):
def sf_final(self, payments: Payments = None) -> float:

if self.sfr is None:
raise Exception("sf_final only applicable to sinking fund loans.")

sv = Annuity(
amount=self.sfd,
gr=self.sfr,
period=self.period,
term=self.term - self.period
).eq_val(self.term)
if payments:
bal = self.amt
t0 = 0
sf_amounts = []
sf_times = []
for amount, time in zip(payments.amounts, payments.times):
interest_due = bal * self.gr.effective_interval(t1=t0, t2=time)
if amount >= interest_due:
sf_deposit = amount - interest_due
else:
sf_deposit = 0
bal += interest_due - amount
sf_amounts += [sf_deposit]
sf_times += [time]
t0 = time
sf_payments = Payments(amounts=sf_amounts, times=sf_times, gr=self.sfr)

sv = sf_payments.eq_val(self.term)

final_pmt = bal * (1 + self.gr.effective_interval(t1=t0, t2=self.term)) - sv
else:
sv = Annuity(
amount=self.sfd,
gr=self.sfr,
period=self.period,
term=self.term - self.period
).eq_val(self.term)

final_pmt = self.amt - sv
final_pmt = self.amt - sv

return final_pmt

def sink_payments(self, payments: Payments) -> dict:

res = {
'time': [],
'interest_due': [],
'sf_deposit': [],
'sf_interest': [],
'sf_bal': [],
'loan_balance': []
}

# initial row
res['time'] += [0]
res['interest_due'] += [0]
res['sf_deposit'] += [0]
res['sf_interest'] += [0]
res['sf_bal'] += [0]
res['loan_balance'] += [self.amt]

bal = self.amt
sf_bal = 0
t0 = 0
for amount, time in zip(payments.amounts, payments.times):
interest_due = bal * self.gr.effective_interval(t1=t0, t2=time)
if amount >= interest_due:
sf_deposit = amount - interest_due
else:
sf_deposit = 0
bal += interest_due - amount

sf_interest = sf_bal * self.sfr.effective_interval(t1=t0, t2=time)
sf_bal += (sf_deposit + sf_interest)
net_bal = bal - sf_bal
res['time'] += [time]
res['interest_due'] += [interest_due]
res['sf_deposit'] += [sf_deposit]
res['sf_interest'] += [sf_interest]
res['sf_bal'] += [sf_bal]
res['loan_balance'] += [net_bal]
t0 = time

if self.cents:
for k, v in res.items():
res[k] = [round(x, 2) if isinstance(x, float) else x for x in v]

return res

def sinking(self):
res = self.sink_payments(payments=self.pmt_sched)

return res

0 comments on commit 6c3c59b

Please sign in to comment.