raymon1 / financial

Financial is a Rust crate that contains collection of finance calculations memicking some of Excel Financial Functions interface.
https://docs.rs/crate/financial
14 stars 5 forks source link

year fraction calculation in `xnpv()` #9

Closed shrektan closed 2 years ago

shrektan commented 2 years ago

Hi,

When converting date period into year fractions, it uses the simple algo (days between start_date and end_date) / 365.0. This actually results in slightly unexpected output when the year is a leap year (like 2020, see the example below).

I don't know if it's intended or not.

Is it open to accept other options as the year fraction calculation algo, something similar to 30/365? (See below)

I'm happy to provide a PR if it's ok.

Thanks.

Related Source Code

https://github.com/raymon1/financial/blob/2ea69baf1fb40906f38e456d8c8b51f08f746083/src/scheduled_cashflow/xnpv.rs#L46

https://github.com/raymon1/financial/blob/2ea69baf1fb40906f38e456d8c8b51f08f746083/src/common/utils.rs#L31-L33

Example

Code

use chrono::{DateTime, NaiveDate, Utc};
use financial::*;

fn main() {
    let cf = [-100., 105.];
    let dates = [
        DateTime::<Utc>::from_utc(NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0), Utc),
        DateTime::<Utc>::from_utc(NaiveDate::from_ymd(2021, 1, 1).and_hms(0, 0, 0), Utc),
    ];
    let out = xirr::<Utc>(&cf, &dates, None).unwrap();
    println!("xirr of leap year (2020) is {}", out);

    let cf = [-100., 105.];
    let dates = [
        DateTime::<Utc>::from_utc(NaiveDate::from_ymd(2021, 1, 1).and_hms(0, 0, 0), Utc),
        DateTime::<Utc>::from_utc(NaiveDate::from_ymd(2022, 1, 1).and_hms(0, 0, 0), Utc),
    ];
    let out = xirr::<Utc>(&cf, &dates, None).unwrap();
    println!("xirr of normal year(2021) is {}", out);
}

Output

I would expect both results are 0.05.

shrektan@shrektan-MBP test-rust % cargo run
   Compiling learn-rust v0.1.0 (/Users/shrektan/Documents/RWD/learn-rust)
    Finished dev [unoptimized + debuginfo] target(s) in 0.15s
     Running `target/debug/learn-rust`
xirr of leap year (2020) is 0.04986003754670357
xirr of normal year(2021) is 0.050000000000000114

Suggested algo of calculating the year fraction

pub fn year_frac(d1: &NaiveDate, d0: &NaiveDate) -> f64 {
    (d1.year() - d0.year()) as f64
    // must be as f64 first, otherwise u32 - u32 may overflow (when negative)
        + (d1.month() as f64 - d0.month() as f64) / 12.0
        + (d1.day() as f64 - d0.day() as f64) / 365.0
}
raymon1 commented 2 years ago

Since this is a scheduled cashflow and not periodic, it makes sense to have different IRR %, I am not sure why are you expecting the same results?

I tried it in google sheets to confirm the logic

image

shrektan commented 2 years ago

Ok, I verify that Excel share the same logic that uses 365 to calculate the IRR.

The reason that I proposed this is I'm using this to calculate bond's YTM. Now I think this change probably should not be applied to this function...

I'm going to close this for now. Thanks.