Numbers

Finance

Loan payments and amortization tables, compound growth with deposits, net present value and internal rate of return, in cents.

Version 1.0.0 · 5 functions · 0 crates

#money#loans#calculators#maths

Functions

What use finance.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use finance_payment, finance_schedule from finance (Quillo 0.6).

finance_payment(principal: float, annual_rate: float, months: int) → floator an error

The fixed monthly payment that pays off principal in months (1 to 1,200) at annual_rate percent a year, interest compounded monthly (as for mortgages and car loans).

finance_schedule(principal: float, annual_rate: float, months: int, extra: float) → anyor an error

The amortization table: { payment, total_paid, total_interest, rows: [{ month, payment, interest, principal, balance }] }, each month's interest on the balance left, in cents; the last payment is adjusted so the balance ends at exactly 0. extra is paid off the principal each month too (0 for none), which ends the loan sooner.

finance_compound(principal: float, annual_rate: float, years: float, times_per_year: int, deposit: float) → anyor an error

Savings growing at annual_rate percent for years, compounded times_per_year (12 monthly, 1 yearly), with deposit added at the end of each period: { final, deposited, interest }, deposited counting the starting principal.

finance_npv(rate: float, cashflows: list) → floator an error

The net present value of cashflows (the first now, then one per period, usually years; money in positive, out negative) at a discount rate percent per period.

finance_irr(cashflows: list) → floator an error

The internal rate of return of cashflows (as for finance_npv), in percent per period: the rate at which their NPV is 0. An error when the flows do not change sign, so no rate exists.

Get it

$ quillo install finance               # finance.qwer into this folder$ quillo install finance --example     # and its example, finance.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/finance.qwer$ curl -O https://quillolang.org/plugins/files/finance.qwe

Then, in your program, all of it or only what you call:

main.qwe
use finance.qweruse finance_payment, finance_schedule from finance      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]

Example

finance.qwe
// A mortgage calculator, a savings plan and an investment's return: the numbers a bank's// website shows, worked out on the server.////   quillo run finance.qwe      then open http://localhost:8080/mortgage?amount=300000&rate=4.5&years=30use "finance.qwer"route GET /mortgage ? { amount: float, rate: float, years: int, extra: float = 0.0 } {    let s = finance_schedule(amount, rate, years * 12, extra)?    { monthly: finance_payment(amount, rate, years * 12)?, months: s.months, interest: s.total_interest, first: s.rows[0], last: s.rows[len(s.rows) - 1] }}route GET /savings ? { start: float = 1000.0, monthly: float = 200.0, rate: float = 5.0, years: float = 10.0 } = finance_compound(start, rate, years, 12, monthly)?// A machine costing 10,000 that saves 3,000 a year for five years.route GET /machine = { npv_at_8: finance_npv(8.0, [-10000, 3000, 3000, 3000, 3000, 3000])?, irr: finance_irr([-10000, 3000, 3000, 3000, 3000, 3000])?, never: finance_irr([100, 200]).error }
$ quillo run finance.qwe$ curl 'localhost:8080/mortgage?amount=300000&rate=4.5&years=30'$ curl 'localhost:8080/savings'$ curl 'localhost:8080/machine'

Source

finance.qwer
//! The money maths of loans, savings and investments: a loan's monthly payment and amortization table, compound growth with deposits, net present value and internal rate of return. Rates are yearly percentages, amounts rounded to cents.//! version: 1.0.0//!//! [dependencies]use serde_json::{json, Value};fn cents(x: f64) -> f64 {    (x * 100.0).round() / 100.0}fn check_rate(r: f64) -> Result<(), String> {    if !r.is_finite() || r <= -100.0 || r > 1000.0 {        return Err("a rate is a yearly percentage above -100 and up to 1000".into());    }    Ok(())}fn payment(principal: f64, monthly: f64, months: i64) -> f64 {    let n = months as f64;    if monthly == 0.0 { principal / n } else { principal * monthly / (1.0 - (1.0 + monthly).powf(-n)) }}/// The fixed monthly payment that pays off `principal` in `months` (1 to 1,200) at `annual_rate` percent a year, interest compounded monthly (as for mortgages and car loans).pub fn finance_payment(principal: f64, annual_rate: f64, months: i64) -> Result<f64, String> {    check_rate(annual_rate)?;    if !(1..=1200).contains(&months) || !principal.is_finite() {        return Err("months is 1 to 1,200 and the principal a number".into());    }    Ok(cents(payment(principal, annual_rate / 1200.0, months)))}/// The amortization table: `{ payment, total_paid, total_interest, rows: [{ month, payment, interest, principal, balance }] }`,/// each month's interest on the balance left, in cents; the last payment is adjusted so the balance ends at exactly 0. `extra` is paid off the principal each month too (0 for none), which ends the loan sooner.pub fn finance_schedule(principal: f64, annual_rate: f64, months: i64, extra: f64) -> Result<Value, String> {    check_rate(annual_rate)?;    if !(1..=1200).contains(&months) || !(principal > 0.0) || !(extra >= 0.0) {        return Err("months is 1 to 1,200, the principal above 0 and extra 0 or more".into());    }    let r = annual_rate / 1200.0;    let pay = cents(payment(principal, r, months));    let mut bal = cents(principal);    let (mut rows, mut paid, mut interest_sum) = (Vec::new(), 0.0, 0.0);    for m in 1..=months {        let interest = cents(bal * r);        let mut p = cents(pay + extra);        if p > bal + interest || m == months {            p = cents(bal + interest);        }        let princ = cents(p - interest);        bal = cents(bal - princ);        paid += p;        interest_sum += interest;        rows.push(json!({ "month": m, "payment": p, "interest": interest, "principal": princ, "balance": bal }));        if bal <= 0.0 {            break;        }    }    Ok(json!({ "payment": pay, "months": rows.len(), "total_paid": cents(paid), "total_interest": cents(interest_sum), "rows": rows }))}/// Savings growing at `annual_rate` percent for `years`, compounded `times_per_year` (12 monthly, 1 yearly), with `deposit` added at the end of each period:/// `{ final, deposited, interest }`, `deposited` counting the starting `principal`.pub fn finance_compound(principal: f64, annual_rate: f64, years: f64, times_per_year: i64, deposit: f64) -> Result<Value, String> {    check_rate(annual_rate)?;    if !(1..=365).contains(&times_per_year) || !(0.0..=200.0).contains(&years) {        return Err("times_per_year is 1 to 365 and years 0 to 200".into());    }    let k = times_per_year as f64;    let n = (years * k).round();    let r = annual_rate / 100.0 / k;    let growth = (1.0 + r).powf(n);    let fv = principal * growth + if r == 0.0 { deposit * n } else { deposit * (growth - 1.0) / r };    let deposited = principal + deposit * n;    Ok(json!({ "final": cents(fv), "deposited": cents(deposited), "interest": cents(fv - deposited) }))}fn npv(rate: f64, flows: &[f64]) -> f64 {    flows.iter().enumerate().map(|(t, c)| c / (1.0 + rate).powi(t as i32)).sum()}/// The net present value of `cashflows` (the first now, then one per period, usually years; money in positive, out negative) at a discount `rate` percent per period.pub fn finance_npv(rate: f64, cashflows: Vec<f64>) -> Result<f64, String> {    check_rate(rate)?;    if cashflows.is_empty() || cashflows.len() > 10_000 {        return Err("give 1 to 10,000 cash flows".into());    }    Ok(cents(npv(rate / 100.0, &cashflows)))}/// The internal rate of return of `cashflows` (as for `finance_npv`), in percent per period: the rate at which their NPV is 0. An error when the flows do not change sign, so no rate exists.pub fn finance_irr(cashflows: Vec<f64>) -> Result<f64, String> {    if cashflows.len() < 2 || cashflows.len() > 10_000 {        return Err("give 2 to 10,000 cash flows".into());    }    if !(cashflows.iter().any(|c| *c > 0.0) && cashflows.iter().any(|c| *c < 0.0)) {        return Err("the cash flows never change sign: there is no rate of return".into());    }    // Newton's method from 10%, then bisection on [-99.99%, 1000%] when it does not settle.    let mut r = 0.1;    for _ in 0..100 {        let f = npv(r, &cashflows);        let d: f64 = cashflows.iter().enumerate().map(|(t, c)| -(t as f64) * c / (1.0 + r).powi(t as i32 + 1)).sum();        if d == 0.0 || !d.is_finite() {            break;        }        let next = r - f / d;        if !next.is_finite() || next <= -0.9999 {            break;        }        if (next - r).abs() < 1e-12 {            return Ok((next * 1e6).round() / 1e4);        }        r = next;    }    let (mut lo, mut hi) = (-0.9999, 10.0);    let (flo, fhi) = (npv(lo, &cashflows), npv(hi, &cashflows));    if flo.signum() == fhi.signum() {        return Err("no rate of return between -99.99% and 1000%".into());    }    for _ in 0..200 {        let mid = (lo + hi) / 2.0;        if npv(mid, &cashflows).signum() == flo.signum() { lo = mid } else { hi = mid }    }    Ok((((lo + hi) / 2.0) * 1e6).round() / 1e4)}
Big numbers bignum.qwer · 1.0.0Integers and fractions of any size, exact: factorials, powers, gcd, modular powers, and fractions such as 1/3 + 1/6 = 1/2 with no rounding.mathsmoneyexactfractions8 views · 7 downloadsFractions fractions.qwer · 1.0.0Exact fractions for recipes, carpentry and school maths: "1 1/2", "3/8" and "12.5%" read and calculated without rounding, expressions worked out, decimals turned into simple fractions, measures rounded to 1/16 inch, and recipes scaled.fractionsmathscalculatorsexact7 views · 9 downloadsMoney split money_split.qwer · 1.0.0Money split fairly in whole cents, the parts always adding up: by ratios, evenly, into dated instalments, a restaurant bill by who had what with tax and tip, a group's shared expenses settled in few payments, and a plan prorated by days.moneypaymentsaccountingcalculators7 views · 8 downloadsUnit conversion units.qwer · 1.0.0Quantities converted between units: length, mass, temperature, volume and kitchen measures, speed, energy, pressure, data sizes and more, with prefixes and compound units, mpg against L/100km, "5 ft 11 in" read, and values shown in their most readable unit.calculatorsmathslocaleconversion7 views · 8 downloadsClustering clustering.qwer · 1.0.0Data put into groups: k-means with k chosen by the elbow and silhouette, DBSCAN for groups of any shape and their outliers (in metres for latitude and longitude), and hierarchical clustering, on rows of numbers or maps.statisticsanalyticssegmentsgeomaths7 views · 7 downloadsFrequency spectrum fft.qwer · 1.0.0The frequency spectrum of samples from a microphone or sensor: frequencies and their strength, the dominant ones found between bins, and the signal's level.signalsaudiosensorsmaths8 views · 7 downloads
Thread
76Useful? Like it, or add to its thread below.

Thread 0 posts

On Finance: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.