Skip to content

Instantly share code, notes, and snippets.

@rnkoaa
Created January 9, 2022 07:51
Show Gist options
  • Save rnkoaa/47e6424f99753d1f01bde443c516a42c to your computer and use it in GitHub Desktop.
Save rnkoaa/47e6424f99753d1f01bde443c516a42c to your computer and use it in GitHub Desktop.

Revisions

  1. rnkoaa created this gist Jan 9, 2022.
    125 changes: 125 additions & 0 deletions Application.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,125 @@
    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())
    }
    }
    }