-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmortgage.go
137 lines (122 loc) · 3.31 KB
/
mortgage.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Package mortgage calculates Amortization schedules
package mortgage
import (
"fmt"
"math"
"strconv"
"time"
"github.com/keep94/toolbox/date_util"
)
// Term represents a single term within an amortization schedule.
type Term struct {
Date time.Time
Payment int64
Interest int64
Balance int64
}
// Principal returns the principal paid during this term
func (t *Term) Principal() int64 {
return t.Payment - t.Interest
}
// Loan represents a loan. Loan instances are immutable.
type Loan struct {
amount int64
rate float64
length int
payment int64
}
// NewLoan returns a new loan. Payments on returned loan are monthly.
// amount is the amount borrowed;
// rate is the annual interest rate, 0.03 = 3%;
// durationInMonths is the number of months of the loan.
// amount and durationInMonths must be positive.
func NewLoan(amount int64, rate float64, durationInMonths int) *Loan {
if amount <= 0 || durationInMonths <= 0 {
panic("Amount and durationInMonths must be positive.")
}
payment := solveForPayment(amount, rate/12.0, durationInMonths)
return &Loan{amount, rate, durationInMonths, payment}
}
// Amount returns the amount borrowed
func (l *Loan) Amount() int64 {
return l.amount
}
// Rate returns the annual interest rate, 0.03 = 3%.
func (l *Loan) Rate() float64 {
return l.rate
}
// DurationInMonths returns the number of months of the loan. Depending on the
// rounding of payment, this may be different than the actual number of months
// needed to pay off the loan.
func (l *Loan) DurationInMonths() int {
return l.length
}
// Payment returns the payment due each term
func (l *Loan) Payment() int64 {
return l.payment
}
// Terms returns all the terms needed to pay off this loan. year and
// month are the origination month of the loan.
// maxTerms is the maximum number of terms this method will return.
// The number of terms returned may differ from the duration of the loan
// depending on the rounding of the payment.
func (l *Loan) Terms(year, month, maxTerms int) []*Term {
var result []*Term
date := date_util.YMD(year, month, 1)
balance := l.amount
monthlyRate := l.rate / 12.0
for balance > 0 {
date = date.AddDate(0, 1, 0)
interest := toInt64(float64(balance) * monthlyRate)
balance += interest
payment := l.payment
if payment > balance {
payment = balance
}
balance -= payment
result = append(result, &Term{
Date: date,
Payment: payment,
Interest: interest,
Balance: balance})
if len(result) == maxTerms {
break
}
}
return result
}
// FormatUSD returns amount as dollars and cents.
// 347 -> "3.47"
func FormatUSD(x int64) string {
return fmt.Sprintf("%.2f", float64(x)/100.0)
}
// ParseUSD is the inverse of FormatUSD.
// "3.47" -> 347
func ParseUSD(s string) (v int64, e error) {
f, e := strconv.ParseFloat(s, 64)
if e != nil {
return
}
v = int64(math.Floor(f*100.0 + 0.5))
return
}
func solveForPayment(
amount int64, rate float64, length int) int64 {
amountF := float64(amount)
lengthF := float64(length)
if rate == 0.0 {
return toInt64(amountF / lengthF)
}
result := toInt64(amountF * rate * (1.0 + 1.0/(math.Pow((1.0+rate), lengthF)-1.0)))
if result <= 0 {
result = 1
}
interestOnly := toInt64(amountF * rate)
if result <= interestOnly {
result = interestOnly + 1
}
return result
}
func toInt64(x float64) int64 {
return int64(x + 0.5)
}