Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::lending_amortisation_schedule::{amortisation_schedule, period_interest}; ← from lending.amortisation-schedule ^1.0.0 · built alongside by fune
use super::lending_loan_payment::payment_count; ← from lending.loan-payment ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
/// Run a balance down at a fixed payment, with the schedule's rounding, until
/// it is clear or the term ends: (payments, final payment, interest). As in
/// lending.amortisation-schedule, the last payment of the term is whatever
/// clears the balance.
fn pay_off(
balance: i64,
annual_rate_basis_points: i64,
payments_per_year: i64,
payment: i64,
term: i64,
) -> (i64, i64, i64) {
let mut owed = balance;
let mut payments = 0i64;
let mut interest = 0i64;
let mut final_payment = 0i64;
while owed > 0 {
let accrued = period_interest(owed, annual_rate_basis_points, payments_per_year);
if payment <= accrued {
panic!("the payment of {} does not cover the interest of {}", payment, accrued);
}
let due = if payments + 1 == term || owed + accrued <= payment {
owed + accrued
} else {
payment
};
owed -= due - accrued;
interest += accrued;
payments += 1;
final_payment = due;
}
(payments, final_payment, interest)
}
/// The two things a lender offers after a lump-sum overpayment, side by side:
/// keep paying the same amount and finish sooner, or keep the same end date
/// and pay less each period. Both are built period by period with the same
/// rounding as lending.amortisation-schedule, and compared with carrying on
/// as if nothing had been overpaid.
///
/// # Panics
/// Panics on mismatched currencies, an overpayment of zero or of the whole
/// balance, a payment that does not cover the interest, or the arguments
/// lending.loan-payment refuses.
pub fn overpayment_effect(
balance: &Money,
annual_rate_basis_points: i64,
remaining_term_months: i64,
payments_per_year: i64,
payment: &Money,
overpayment: &Money,
mode: &str,
) -> OverpaymentEffect {
let term = payment_count(annual_rate_basis_points, remaining_term_months, payments_per_year);
let currency = balance.currency.as_str();
for other in [payment, overpayment] {
if other.currency != currency {
panic!("currency mismatch: {} and {}", currency, other.currency);
}
}
if payment.minor <= 0 {
panic!("payment must be greater than zero, received {}", payment.minor);
}
if overpayment.minor <= 0 {
panic!("overpayment must be greater than zero, received {}", overpayment.minor);
}
if overpayment.minor >= balance.minor {
panic!("overpayment must be less than the balance; paying it all off is an early settlement");
}
let new_balance = balance.minor - overpayment.minor;
let (base_n, _, base_interest) = pay_off(balance.minor, annual_rate_basis_points, payments_per_year, payment.minor, term);
let (short_n, short_final, short_interest) =
pay_off(new_balance, annual_rate_basis_points, payments_per_year, payment.minor, term);
let lower = amortisation_schedule(
&money(new_balance, currency),
annual_rate_basis_points,
remaining_term_months,
payments_per_year,
mode,
);
OverpaymentEffect {
new_balance: money(new_balance, currency),
baseline_payments: base_n,
baseline_interest: money(base_interest, currency),
reduced_term_payments: short_n,
reduced_term_final_payment: money(short_final, currency),
reduced_term_interest: money(short_interest, currency),
payments_saved: base_n - short_n,
reduced_payment: lower.payment,
reduced_payment_interest: lower.total_interest,
}
}
pub fn overpayment_effect_to_value(effect: &OverpaymentEffect) -> Value {
Value::obj(vec![
("newBalance", money_to_value(&effect.new_balance)),
("baselinePayments", Value::Int(effect.baseline_payments)),
("baselineInterest", money_to_value(&effect.baseline_interest)),
("reducedTermPayments", Value::Int(effect.reduced_term_payments)),
("reducedTermFinalPayment", money_to_value(&effect.reduced_term_final_payment)),
("reducedTermInterest", money_to_value(&effect.reduced_term_interest)),
("paymentsSaved", Value::Int(effect.payments_saved)),
("reducedPayment", money_to_value(&effect.reduced_payment)),
("reducedPaymentInterest", money_to_value(&effect.reduced_payment_interest)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
overpayment_effect_to_value(&overpayment_effect(
&money_from_value(&args[0]),
args[1].as_i64(),
args[2].as_i64(),
args[3].as_i64(),
&money_from_value(&args[4]),
&money_from_value(&args[5]),
args[6].as_str(),
))
}