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
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 errorThe 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 errorThe 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 errorSavings 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 errorThe 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 errorThe 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.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/finance.qwer$ curl -O https://quillolang.org/plugins/files/finance.qweThen, in your program, all of it or only what you call:
use finance.qweruse finance_payment, finance_schedule from finance // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
Example
// 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
//! 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(×_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)}Related
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.8 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.7 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.7 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.7 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.7 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.8 views · 7 downloadsThread 0 posts
On Finance: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.