Functional Weave
Code in Rust

invest.dividend-yield@1.0.1

impl/rust.rs

3,242 bytes · the Rust implementation · view raw

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

/// 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<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!("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<Money> = args[1].as_arr().iter().map(money_from_value).collect();
    dividend_yield_to_value(&dividend_yield(
        &money_from_value(&args[0]),
        &trailing,
        &money_from_value(&args[2]),
        args[3].as_i64(),
    ))
}