Skip to content

Instantly share code, notes, and snippets.

@rnkoaa
Created January 9, 2022 07:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rnkoaa/47e6424f99753d1f01bde443c516a42c to your computer and use it in GitHub Desktop.
Save rnkoaa/47e6424f99753d1f01bde443c516a42c to your computer and use it in GitHub Desktop.
Loan Amortization in Kotlin
package org.richard.amortization
import kotlin.math.pow
object Application {
private const val NUMBER_OF_MONTHS_IN_YEAR = 12
private fun interest(principal: Money, rate: Double): Money {
return Money(rate * principal.value())
}
private fun payment(originalPrincipal: Money, rate: Double, numberOfPayments: Int): Money {
val rateCorrection = (1 + rate).pow(numberOfPayments)
val numerator = rate * rateCorrection
val denominator = rateCorrection - 1
val paymentRate = numerator.div(denominator)
return Money(originalPrincipal.value() * paymentRate)
}
fun main() {
val principal = 287_000
val baseInterest = 2.875
val length = 20 // 20 years
val totalPaymentMonths = length * NUMBER_OF_MONTHS_IN_YEAR
val monthlyRate = baseInterest / (NUMBER_OF_MONTHS_IN_YEAR * 100)
val extraPayments = Array(totalPaymentMonths) { Money.zero() }
extraPayments[0] = Money(1000.00)
extraPayments[1] = Money(106.00)
extraPayments[2] = Money(110.00)
extraPayments[3] = Money(156.00)
extraPayments[4] = Money(156.00)
extraPayments[5] = Money(200.00)
extraPayments[6] = Money(300.00)
val rows = generatePayments(Money(principal.toDouble()), monthlyRate, totalPaymentMonths)
println(rows.size)
}
private fun generatePayments(principal: Money, monthlyRate: Double, totalPaymentMonths: Int): List<Row> {
val paymentAmount = payment(principal, monthlyRate, totalPaymentMonths)
val amortizedPayments = Array(totalPaymentMonths) { Row.zero() }
for (i in amortizedPayments.indices) {
val row: Row
if (i == 0) {
val interest = interest(principal, monthlyRate)
val appliedPrincipal = paymentAmount.subtract(interest)
val endingBalance = principal.subtract(appliedPrincipal)
row = Row(
principal,
interest,
appliedPrincipal,
paymentAmount,
Money.zero(),
endingBalance
)
amortizedPayments[i] = row
} else {
val previousRow = amortizedPayments[i - 1]
if (previousRow.startingBalance.value() > 0.0) {
val startingBalance = previousRow.endingBalance
//
val interest = interest(startingBalance, monthlyRate)
val appliedPrincipal = paymentAmount.subtract(interest)
val endingBalance = startingBalance.subtract(appliedPrincipal)
row = Row(
startingBalance,
interest,
appliedPrincipal,
paymentAmount,
Money.zero(),
endingBalance
)
amortizedPayments[i] = row
}
}
}
//
return amortizedPayments.filter { r -> r.startingBalance.value() > 0 }
}
}
@JvmInline
value class Money(private val v: Double) {
companion object {
fun zero(): Money {
return Money(0.0)
}
}
fun value(): Double {
return v
}
fun subtract(value: Money): Money {
return Money(v - value.v)
}
override fun toString(): String {
return String.format("%,.2f", v)
}
}
data class Row(
val startingBalance: Money,
val interest: Money,
val principal: Money,
val totalPayment: Money,
val extraPrincipal: Money,
val endingBalance: Money
) {
companion object {
fun zero(): Row {
return Row(Money.zero(), Money.zero(), Money.zero(), Money.zero(), Money.zero(), Money.zero())
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment