use super::funejson::Value; use super::money_amount::{money, money_from_value, money_to_value, Money}; /// amount x 10000 / price, half-up; both 0 or more, price > 0. fn basis_points(amount: i64, price: i64) -> i64 { let n = amount as i128 * 10000; let d = price as i128; let mut q = n / d; if 2 * (n - q * d) >= d { q += 1; } q as i64 } /// Trailing yield from the dividends paid in the last twelve months, and /// forward yield from the latest regular dividend annualised, both over the /// current price. /// /// # Panics /// Panics on a price of 0 or less, a negative dividend, paymentsPerYear /// outside 1 to 52, or mixed currencies. pub fn dividend_yield(price: &Money, trailing_dividends: &[Money], forward_dividend: &Money, payments_per_year: i64) -> DividendYield { let currency = price.currency.as_str(); if price.minor <= 0 { panic!("price must be whole minor units greater than zero; received {}", price.minor); } if !(1..=52).contains(&payments_per_year) { panic!("paymentsPerYear must be a whole number from 1 to 52; received {}", payments_per_year); } for dividend in trailing_dividends.iter().chain(std::iter::once(forward_dividend)) { if dividend.currency != currency { panic!("currency mismatch: {} and {}", currency, dividend.currency); } if dividend.minor < 0 { panic!("dividends must be whole minor units, 0 or more; received {}", dividend.minor); } } let trailing: i64 = trailing_dividends.iter().map(|d| d.minor).sum(); let forward = forward_dividend.minor * payments_per_year; DividendYield { trailing_annual: money(trailing, currency), forward_annual: money(forward, currency), trailing_basis_points: basis_points(trailing, price.minor), forward_basis_points: basis_points(forward, price.minor), } } pub fn dividend_yield_to_value(result: &DividendYield) -> Value { Value::obj(vec![ ("trailingAnnual", money_to_value(&result.trailing_annual)), ("forwardAnnual", money_to_value(&result.forward_annual)), ("trailingBasisPoints", Value::Int(result.trailing_basis_points)), ("forwardBasisPoints", Value::Int(result.forward_basis_points)), ]) } fn fraction(v: &Value) -> Option { 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!("price must be whole minor units greater than zero; received {}", f); } for v in args[1].as_arr().iter().chain(std::iter::once(&args[2])) { if let Some(f) = fraction(v.get("minor")) { panic!("dividends must be whole minor units, 0 or more; received {}", f); } } if let Some(f) = fraction(&args[3]) { panic!("paymentsPerYear must be a whole number from 1 to 52; received {}", f); } let trailing: Vec = args[1].as_arr().iter().map(money_from_value).collect(); dividend_yield_to_value(÷nd_yield( &money_from_value(&args[0]), &trailing, &money_from_value(&args[2]), args[3].as_i64(), )) }