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::dates_add_days::{days_from_civil, epoch_day_from_iso, is_leap_year, parse_iso_date}; ← from dates.add-days ^1.0.0 · built alongside by fune
fn gcd(mut a: i64, mut b: i64) -> i64 {
while b != 0 {
let r = a % b;
a = b;
b = r;
}
a
}
fn reduced(numerator: i64, denominator: i64) -> YearFraction {
if numerator == 0 {
return YearFraction { numerator: 0, denominator: 1 };
}
let g = gcd(numerator, denominator);
YearFraction {
numerator: numerator / g,
denominator: denominator / g,
}
}
/// The year fraction from `start_iso` to `end_iso` under a day count convention
/// (2006 ISDA Definitions 4.16), as an exact fraction in lowest terms. Kept
/// exact so the caller multiplies principal and rate by the numerator and
/// rounds once, instead of rounding a float year fraction first.
///
/// # Panics
/// Panics on a malformed date, a start after the end, or an unknown convention.
pub fn day_count_fraction(start_iso: &str, end_iso: &str, convention: &str) -> YearFraction {
let start = parse_iso_date(start_iso);
let end = parse_iso_date(end_iso);
if start_iso > end_iso {
panic!("startIso {} must not be after endIso {}", start_iso, end_iso);
}
let first = epoch_day_from_iso(start_iso);
let last = epoch_day_from_iso(end_iso);
let actual = last - first;
match convention {
"act-365f" => reduced(actual, 365),
"act-360" => reduced(actual, 360),
"30-360" | "30e-360" => {
let d1 = if start.day == 31 { 30 } else { start.day };
let mut d2 = end.day;
// Bond basis only trims D2 when D1 is at the month end too; Eurobond always does.
if d2 == 31 && (convention == "30e-360" || d1 == 30) {
d2 = 30;
}
reduced(
360 * (end.year - start.year) + 30 * (end.month - start.month) + (d2 - d1),
360,
)
}
"act-act-isda" => {
// Split the actual days by the calendar year they fall in: [start, end).
let mut leap_days = 0i64;
let mut other_days = 0i64;
for year in start.year..=end.year {
let from = if year == start.year { first } else { days_from_civil(year, 1, 1) };
let to = if year == end.year { last } else { days_from_civil(year + 1, 1, 1) };
if is_leap_year(year) {
leap_days += to - from;
} else {
other_days += to - from;
}
}
reduced(other_days * 366 + leap_days * 365, 365 * 366)
}
_ => panic!(
"unknown day count convention \"{}\": expected act-365f, act-360, 30-360, 30e-360 or act-act-isda",
convention
),
}
}
pub fn year_fraction_to_value(fraction: &YearFraction) -> Value {
Value::obj(vec![
("numerator", Value::Int(fraction.numerator)),
("denominator", Value::Int(fraction.denominator)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
year_fraction_to_value(&day_count_fraction(
args[0].as_str(),
args[1].as_str(),
args[2].as_str(),
))
}