#[cfg(test)]
mod tests {
use chrono::{DateTime, NaiveDate, Utc};
use financial;
pub const PRECISION: f64 = 1e-7;
#[test]
fn npv() {
test_fn("./tests/test_data/npv.csv", |test_case, case_index| {
let mut test_case = test_case;
let (ans, r, values): (f64, f64, Vec<f64>) = (
test_case.next().unwrap().parse::<f64>().unwrap(),
test_case.next().unwrap().parse::<f64>().unwrap(),
test_case.map(|x| x.parse::<f64>().unwrap()).collect(),
);
let res = financial::npv(r, &values);
assert!(
(ans - res).abs() < PRECISION,
"case {}: answer is {}, result is {}",
case_index,
ans,
res
);
});
}
#[test]
fn irr() {
test_fn("./tests/test_data/irr.csv", |test_case, case_index| {
let mut test_case = test_case;
let (ans, values): (f64, Vec<f64>) = (
test_case
.next()
.unwrap()
.parse::<f64>()
.unwrap_or_else(|_| f64::NAN),
test_case.map(|x| x.parse::<f64>().unwrap()).collect(),
);
let res = financial::irr(&values, None).unwrap_or_else(|_| f64::NAN);
if !ans.is_nan() {
assert!(
(ans - res).abs() < PRECISION,
"case {}: answer is {}, result is {}",
case_index,
ans,
res
);
}
});
}
#[test]
fn mirr() {
test_fn("./tests/test_data/mirr.csv", |test_case, case_index| {
let mut test_case = test_case;
let parse_next_f64_or = |x : Option<&str>| x.unwrap().parse::<f64>().unwrap_or_else(|_| f64::NAN);
let (ans, finance_rate, reinvest_rate, values): (f64, f64, f64, Vec<f64>) = (
parse_next_f64_or(test_case.next()),
parse_next_f64_or(test_case.next()),
parse_next_f64_or(test_case.next()),
test_case.map(|x| x.parse::<f64>().unwrap()).collect(),
);
let res = financial::mirr(&values, finance_rate, reinvest_rate);
if ans.is_finite() {
assert!(
(ans - res).abs() < PRECISION,
"case {}: answer is {}, result is {}",
case_index,
ans,
res
);
}
else {
assert_eq!(res.is_infinite(), true);
}
});
}
#[test]
fn xnpv() {
test_fn("./tests/test_data/xnpv.csv", |test_case, case_index| {
let mut test_case = test_case;
let date_from_str = |x| {
DateTime::<Utc>::from_utc(
NaiveDate::parse_from_str(x, "%m/%d/%Y")
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap(),
Utc,
)
};
let (ans, r, values, dates): (f64, f64, Vec<f64>, Vec<DateTime<Utc>>) = (
test_case.next().unwrap().parse::<f64>().unwrap(),
test_case.next().unwrap().parse::<f64>().unwrap(),
test_case
.by_ref()
.take_while(|x| x.parse::<f64>().is_ok())
.map(|x| x.parse::<f64>().unwrap())
.collect(),
test_case.map(|x| date_from_str(x)).collect(),
);
let res = financial::xnpv(r, &values, &dates).unwrap();
assert!(
(ans - res).abs() < PRECISION,
"case {}: answer is {}, result is {}",
case_index,
ans,
res
);
});
}
#[test]
fn xirr() {
test_fn("./tests/test_data/xirr.csv", |test_case, case_index| {
let mut test_case = test_case;
let date_from_str = |x| {
DateTime::<Utc>::from_utc(
NaiveDate::parse_from_str(x, "%m/%d/%Y")
.unwrap()
.and_hms_opt(12, 0, 0)
.unwrap(),
Utc,
)
};
let (_, values, dates): (f64, Vec<f64>, Vec<DateTime<Utc>>) = (
test_case
.next()
.unwrap()
.parse::<f64>()
.unwrap_or_else(|_| f64::NAN),
test_case
.by_ref()
.take_while(|x| x.parse::<f64>().is_ok())
.map(|x| x.parse::<f64>().unwrap())
.collect(),
test_case.map(|x| date_from_str(x)).collect(),
);
let res = financial::xirr(&values, &dates, None).unwrap_or_else(|_| f64::NAN);
if !res.is_nan() {
assert_nearly_eq(
0.,
financial::xnpv(res, &values, &dates).unwrap(),
case_index,
);
}
});
}
fn assert_nearly_eq(ans: f64, res: f64, case_index: i32) {
assert!(
(ans - res).abs() < PRECISION,
"case {}: answer is {}, result is {}, diff is {}",
case_index,
ans,
res,
(ans - res).abs()
);
}
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
fn test_fn<P, F>(filename: P, func: F)
where
P: AsRef<Path>,
F: Fn(std::str::Split<&str>, i32),
{
if let Ok(lines) = read_lines(filename) {
let mut case_index = 0;
for line in lines.skip(1) {
if let Ok(test_case) = line {
let test_case = test_case.split(",");
case_index += 1;
func(test_case, case_index);
}
}
}
}
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
P: AsRef<Path>,
{
match File::open(filename) {
Ok(file) => Ok(io::BufReader::new(file).lines()),
Err(e) => panic!("file read error: {}", e),
}
}
}