// Fixed-rate loan amortization: monthly payment, remaining balance after // k payments, and total interest paid. Standard annuity formula with // periodic compounding. cell LoanAmortization { face { signal payment(principal: Float, annual_rate: Float, months: Int) -> Float signal balance_after(principal: Float, annual_rate: Float, months: Int, k: Int) -> Float signal total_interest(principal: Float, annual_rate: Float, months: Int) -> Float } // M = P * r(1+r)^n / ((1+r)^n - 1), r = monthly rate on payment(principal: Float, annual_rate: Float, months: Int) { let r = annual_rate / 12.0 if r == 0.0 { return principal / months } let g = pow(1.0 + r, months) return principal * r * g / (g - 1.0) } // remaining balance B_k = P(1+r)^k - M*((1+r)^k - 1)/r on balance_after(principal: Float, annual_rate: Float, months: Int, k: Int) { let r = annual_rate / 12.0 let m = payment(principal, annual_rate, months) if r == 0.0 { return principal - m * k } let g = pow(1.0 + r, k) return principal * g - m * (g - 1.0) / r } on total_interest(principal: Float, annual_rate: Float, months: Int) { return payment(principal, annual_rate, months) * months - principal } } cell test LoanTests { rules { // 200k @ 6% / 360 months ≈ 1199.10/mo assert abs(payment(200000.0, 0.06, 360) - 1199.101) < 0.01 // balance after all payments is ~0 assert abs(balance_after(200000.0, 0.06, 360, 360)) < 0.01 // balance after 0 payments is the principal assert abs(balance_after(200000.0, 0.06, 360, 0) - 200000.0) < 0.01 // total interest is positive and large on a 30y loan assert total_interest(200000.0, 0.06, 360) > 200000.0 // zero-rate loan: payment is just principal/n assert abs(payment(1200.0, 0.0, 12) - 100.0) < 0.0001 } }