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::money_amount::{money, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
use super::money_apply_rate::apply_rate; ← from money.apply-rate ^1.0.0 · built alongside by fune
/// The same investment grown twice, with and without an annual charge taken
/// on the year-end value, rounding to minor units every year as a statement
/// would.
///
/// # Panics
/// Panics on a negative start value, growth below -10000, a fee outside 0 to
/// 10000, or years outside 0 to 100.
pub fn fee_drag(start_value: &Money, annual_growth_basis_points: i64, annual_fee_basis_points: i64, years: i64) -> FeeDrag {
if start_value.minor < 0 {
panic!("startValue must be whole minor units, 0 or more; received {}", start_value.minor);
}
if annual_growth_basis_points < -10000 {
panic!(
"annualGrowthBasisPoints must be a whole number, -10000 or more; received {}",
annual_growth_basis_points
);
}
if !(0..=10000).contains(&annual_fee_basis_points) {
panic!(
"annualFeeBasisPoints must be a whole number from 0 to 10000; received {}",
annual_fee_basis_points
);
}
if !(0..=100).contains(&years) {
panic!("years must be a whole number from 0 to 100; received {}", years);
}
let currency = start_value.currency.as_str();
let mut without = start_value.minor;
let mut with_fee = start_value.minor;
let mut fees: i64 = 0;
let mut schedule: Vec<FeeDragYear> = Vec::new();
for year in 1..=years {
without += apply_rate(&money(without, currency), annual_growth_basis_points, "half-up").minor;
let grown = with_fee + apply_rate(&money(with_fee, currency), annual_growth_basis_points, "half-up").minor;
let fee = apply_rate(&money(grown, currency), annual_fee_basis_points, "half-up").minor;
with_fee = grown - fee;
fees += fee;
schedule.push(FeeDragYear {
year,
value_without_fee: money(without, currency),
value_with_fee: money(with_fee, currency),
fee: money(fee, currency),
});
}
let drag = without - with_fee;
let mut drag_bp: i64 = 0;
if without > 0 {
let n = drag as i128 * 10000;
let d = without as i128;
let a = n.abs();
let mut q = a / d;
if 2 * (a - q * d) >= d {
q += 1;
}
drag_bp = (if n < 0 { -q } else { q }) as i64;
}
FeeDrag {
value_without_fee: money(without, currency),
value_with_fee: money(with_fee, currency),
drag: money(drag, currency),
drag_basis_points: drag_bp,
fees_paid: money(fees, currency),
schedule,
}
}
pub fn fee_drag_to_value(result: &FeeDrag) -> Value {
Value::obj(vec![
("valueWithoutFee", money_to_value(&result.value_without_fee)),
("valueWithFee", money_to_value(&result.value_with_fee)),
("drag", money_to_value(&result.drag)),
("dragBasisPoints", Value::Int(result.drag_basis_points)),
("feesPaid", money_to_value(&result.fees_paid)),
(
"schedule",
Value::Arr(
result
.schedule
.iter()
.map(|y| {
Value::obj(vec![
("year", Value::Int(y.year)),
("valueWithoutFee", money_to_value(&y.value_without_fee)),
("valueWithFee", money_to_value(&y.value_with_fee)),
("fee", money_to_value(&y.fee)),
])
})
.collect(),
),
),
])
}
fn fraction(v: &Value) -> Option<f64> {
match v {
Value::Float(f) if f.fract() != 0.0 => Some(*f),
_ => None,
}
}
pub fn fune_vector(args: &[Value]) -> Value {
if let Some(f) = fraction(args[0].get("minor")) {
panic!("startValue must be whole minor units, 0 or more; received {}", f);
}
if let Some(f) = fraction(&args[1]) {
panic!("annualGrowthBasisPoints must be a whole number, -10000 or more; received {}", f);
}
if let Some(f) = fraction(&args[2]) {
panic!("annualFeeBasisPoints must be a whole number from 0 to 10000; received {}", f);
}
if let Some(f) = fraction(&args[3]) {
panic!("years must be a whole number from 0 to 100; received {}", f);
}
fee_drag_to_value(&fee_drag(&money_from_value(&args[0]), args[1].as_i64(), args[2].as_i64(), args[3].as_i64()))
}