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_loan_payment::{loan_payment, payment_count}; ← from lending.loan-payment ^1.0.0 · built alongside by fune
use super::math_round_div::round_div; ← from math.round-div ^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
/// Interest for one period on a positive balance: balance × b / D, rounded
/// half-up to the minor unit, which is what gets posted to the account.
pub fn period_interest(balance: i64, annual_rate_basis_points: i64, payments_per_year: i64) -> i64 {
if balance <= 0 || annual_rate_basis_points == 0 {
return 0;
}
let product = balance as i128 * annual_rate_basis_points as i128;
if product > i64::MAX as i128 {
panic!("balance too large for exact interest");
}
round_div(product as i64, 10000 * payments_per_year, "half-up")
}
/// The schedule a lender's system produces: each period's interest is
/// computed on the opening balance and rounded to a whole minor unit, the
/// level payment repays interest first and principal with the rest, and the
/// last payment is whatever clears the balance, so it ends at exactly zero.
///
/// # Panics
/// Panics on the same arguments lending.loan-payment refuses.
pub fn amortisation_schedule(
principal: &Money,
annual_rate_basis_points: i64,
term_months: i64,
payments_per_year: i64,
mode: &str,
) -> AmortisationSchedule {
let payment = loan_payment(principal, annual_rate_basis_points, term_months, payments_per_year, mode);
let n = payment_count(annual_rate_basis_points, term_months, payments_per_year);
let currency = principal.currency.as_str();
let mut rows: Vec<AmortisationRow> = Vec::new();
let mut balance = principal.minor;
let mut total_interest = 0i64;
let mut total_paid = 0i64;
let mut period = 1i64;
while period <= n && balance > 0 {
let interest = period_interest(balance, annual_rate_basis_points, payments_per_year);
// The last period, or one where the level payment would overshoot (a
// rounded-up payment can clear the loan a period early), pays exactly
// what is owed.
let due = if period == n || balance + interest <= payment.minor {
balance + interest
} else {
payment.minor
};
let repaid = due - interest;
balance -= repaid;
total_interest += interest;
total_paid += due;
rows.push(AmortisationRow {
period,
payment: money(due, currency),
interest: money(interest, currency),
principal: money(repaid, currency),
balance: money(balance, currency),
});
period += 1;
}
AmortisationSchedule {
payment,
rows,
total_interest: money(total_interest, currency),
total_paid: money(total_paid, currency),
}
}
pub fn amortisation_row_to_value(row: &AmortisationRow) -> Value {
Value::obj(vec![
("period", Value::Int(row.period)),
("payment", money_to_value(&row.payment)),
("interest", money_to_value(&row.interest)),
("principal", money_to_value(&row.principal)),
("balance", money_to_value(&row.balance)),
])
}
pub fn amortisation_schedule_to_value(schedule: &AmortisationSchedule) -> Value {
Value::obj(vec![
("payment", money_to_value(&schedule.payment)),
("rows", Value::Arr(schedule.rows.iter().map(amortisation_row_to_value).collect())),
("totalInterest", money_to_value(&schedule.total_interest)),
("totalPaid", money_to_value(&schedule.total_paid)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
amortisation_schedule_to_value(&amortisation_schedule(
&money_from_value(&args[0]),
args[1].as_i64(),
args[2].as_i64(),
args[3].as_i64(),
args[4].as_str(),
))
}