#[allow(unused_imports)]
use crate::present_value_annuity::present_value_annuity;
use crate::*;
use std::ops::Deref;
pub fn net_present_value<C, I>(
rate: f64,
periods: u32,
initial_investment: I,
cashflow: C,
) -> crate::FinanceResult<f64>
where
I: Into<f64> + Copy,
C: Into<f64> + Copy,
{
let annuity = cashflow.into();
let ii = initial_investment.into();
crate::util::error::require_rate_gt_minus_one(rate)?;
crate::util::error::require_money("initial_investment", ii)?;
crate::util::error::require_money("cashflow", annuity)?;
if periods == 0 {
return Ok(ii);
}
let pv_cashflow = if rate == 0.0 {
annuity * periods as f64
} else {
annuity * ((1.0 - (1.0 / (1.0 + rate)).powf(periods as f64)) / rate)
};
let npv = ii + pv_cashflow;
if npv.is_finite() {
Ok(npv)
} else {
Err(crate::FinanceError::NonFinite {
field: "net_present_value",
value: npv,
})
}
}
pub fn net_present_value_solution<C, I>(
rate: f64,
periods: u32,
initial_investment: I,
cashflow: C,
) -> crate::FinanceResult<NpvSolution>
where
I: Into<f64> + Copy,
C: Into<f64> + Copy,
{
let annuity = cashflow.into();
let ii = initial_investment.into();
crate::util::error::require_rate_gt_minus_one(rate)?;
crate::util::error::require_money("initial_investment", ii)?;
crate::util::error::require_money("cashflow", annuity)?;
let rates = repeating_vec![rate, periods];
let mut cashflows = repeating_vec![annuity, periods];
cashflows.insert(0, ii);
net_present_value_schedule_solution(&rates, &cashflows)
}
pub fn net_present_value_schedule<C>(rates: &[f64], cashflows: &[C]) -> crate::FinanceResult<f64>
where
C: Into<f64> + Copy,
{
let (periods, r, c, initial_investment) = check_schedule(rates, cashflows)?;
let mut pv_accumulator = 0_f64;
for i in 0..periods {
let present_value =
-present_value(r[i as usize], (i + 1) as u32, c[i as usize + 1], false)?;
pv_accumulator += present_value;
}
let npv = initial_investment + pv_accumulator;
if npv.is_finite() {
Ok(npv)
} else {
Err(crate::FinanceError::NonFinite {
field: "net_present_value",
value: npv,
})
}
}
fn check_schedule<C>(
rates: &[f64],
cashflows: &[C],
) -> crate::FinanceResult<(u32, Vec<f64>, Vec<f64>, f64)>
where
C: Into<f64> + Copy,
{
let mut cflows = vec![];
for i in 0..cashflows.len() {
cflows.push(cashflows[i].into());
}
let cashflows = &cflows;
if cashflows.is_empty() {
return Err(crate::FinanceError::EmptyInput { what: "cashflows" });
}
if cashflows[0] > 0.0 {
return Err(crate::FinanceError::InvalidCashflow {
message: "initial investment (cashflows[0]) should be negative or zero",
});
}
if cashflows.len() < 2 {
return Err(crate::FinanceError::InvalidCashflow {
message: "must provide at least 2 cashflows: initial investment and one cashflow",
});
}
if rates.is_empty() {
return Err(crate::FinanceError::EmptyInput { what: "rates" });
}
crate::util::error::require_rates(rates)?;
for (i, &cf) in cashflows.iter().enumerate() {
crate::util::error::require_money("cashflow", cf).map_err(|_| {
crate::FinanceError::NonFinite {
field: "cashflow",
value: cf,
}
})?;
let _ = i;
}
let rate_length = rates.len();
let cashflow_length = cashflows.len();
let initial_investment = cashflows[0];
let mut cashflow_vec = vec![initial_investment];
let mut rate_vec = vec![];
let periods: u32;
let r: &[f64];
let c: &[f64];
if rate_length == 1 && cashflow_length == 2 {
r = rates;
c = cashflows;
periods = 1_u32;
} else if rate_length > 1 && cashflow_length > 2 {
if rate_length != cashflow_length - 1 {
return Err(crate::FinanceError::LengthMismatch {
left: rate_length,
right: cashflow_length - 1,
context: "npv rates vs cashflow periods",
});
}
r = rates;
c = cashflows;
periods = rate_length as u32;
} else if rate_length > 1 && cashflow_length == 2 {
r = rates;
periods = rate_length as u32;
for _i in 0..periods {
cashflow_vec.push(cashflows[1]);
}
c = &cashflow_vec;
} else if rate_length == 1 && cashflow_length > 2 {
c = cashflows;
periods = cashflow_length as u32 - 1;
for _i in 0..periods {
rate_vec.push(rates[0]);
}
r = &rate_vec;
} else {
return Err(crate::FinanceError::InvalidCashflow {
message: "rates or cashflows must provide a full series; only one may be a repeating shorthand",
});
}
Ok((periods, r.to_vec(), c.to_vec(), initial_investment))
}
pub fn net_present_value_schedule_solution<C>(
rates: &[f64],
cashflows: &[C],
) -> crate::FinanceResult<NpvSolution>
where
C: Into<f64> + Copy,
{
let (periods, rates, cashflows, initial_investment) = check_schedule(rates, cashflows)?;
let mut sum_accumulator = 0_f64;
let mut pv_accumulator = 0_f64;
for i in 0..periods {
let present_value = -present_value(
rates[i as usize],
(i + 1) as u32,
cashflows[i as usize + 1],
false,
)?;
pv_accumulator += present_value;
sum_accumulator += cashflows[i as usize + 1];
}
let sum_of_cashflows = sum_accumulator;
let sum_of_discounted_cashflows = pv_accumulator;
let net_present_value = initial_investment + pv_accumulator;
Ok(NpvSolution::new(
rates,
periods,
initial_investment,
cashflows,
sum_of_cashflows,
sum_of_discounted_cashflows,
net_present_value,
))
}
#[derive(Debug)]
pub struct NpvSolution {
rates: Vec<f64>,
periods: u32,
cashflows: Vec<f64>,
initial_investment: f64,
sum_of_cashflows: f64,
sum_of_discounted_cashflows: f64,
net_present_value: f64,
}
impl NpvSolution {
pub fn new(
rates: Vec<f64>,
periods: u32,
initial_investment: f64,
cashflows: Vec<f64>,
sum_of_cashflows: f64,
sum_of_discounted_cashflows: f64,
net_present_value: f64,
) -> Self {
Self {
rates,
periods,
initial_investment,
cashflows,
sum_of_cashflows,
sum_of_discounted_cashflows,
net_present_value,
}
}
pub fn series(&self) -> NpvSeries {
net_present_value_schedule_series(self)
}
pub fn rate_avg(&self) -> f64 {
let mut rate_accumulator = 0_f64;
for r in &self.rates {
rate_accumulator = rate_accumulator + r;
}
rate_accumulator / self.periods as f64
}
pub fn rates(&self) -> &[f64] {
&self.rates
}
pub fn periods(&self) -> u32 {
self.periods
}
pub fn initial_investment(&self) -> f64 {
self.initial_investment
}
pub fn cashflows(&self) -> &[f64] {
&self.cashflows
}
pub fn sum_of_cashflows(&self) -> f64 {
self.sum_of_cashflows
}
pub fn sum_of_discounted_cashflows(&self) -> f64 {
self.sum_of_discounted_cashflows
}
pub fn net_present_value(&self) -> f64 {
self.net_present_value
}
pub fn npv(&self) -> f64 {
self.net_present_value
}
pub fn print_table(&self) {
self.series().print_table();
}
pub fn print_table_locale(&self, locale: &num_format::Locale, precision: usize) {
self.series().print_table_locale(locale, precision);
}
pub fn max_discounted_cashflow(&self) -> Option<f64> {
self.series().max_discounted_cashflow()
}
pub fn min_discounted_cashflow(&self) -> Option<f64> {
self.series().min_discounted_cashflow()
}
}
#[derive(Debug)]
pub struct NpvSeries(Vec<NpvPeriod>);
impl NpvSeries {
pub(crate) fn new(series: Vec<NpvPeriod>) -> Self {
Self { 0: series }
}
pub fn filter<P>(&self, predicate: P) -> Self
where
P: Fn(&&NpvPeriod) -> bool,
{
Self {
0: self
.iter()
.filter(|x| predicate(x))
.map(|x| x.clone())
.collect(),
}
}
pub fn print_table(&self) {
self.print_table_locale_opt(None, None);
}
pub fn print_table_locale(&self, locale: &num_format::Locale, precision: usize) {
self.print_table_locale_opt(Some(locale), Some(precision));
}
fn print_table_locale_opt(
&self,
locale: Option<&num_format::Locale>,
precision: Option<usize>,
) {
let columns = columns_with_strings(&[
("period", "i", true),
("rate", "f", true),
("present_value", "f", true),
("future_value", "f", true),
("investment_value", "f", true),
]);
let data = self
.iter()
.map(|entry| {
vec![
entry.period.to_string(),
entry.rate.to_string(),
entry.present_value.to_string(),
entry.future_value.to_string(),
entry.investment_value.to_string(),
]
})
.collect::<Vec<_>>();
print_table_locale_opt(&columns, data, locale, precision);
}
pub fn print_ab_comparison(&self, other: &NpvSeries) {
self.print_ab_comparison_locale_opt(other, None, None);
}
pub fn print_ab_comparison_locale(
&self,
other: &NpvSeries,
locale: &num_format::Locale,
precision: usize,
) {
self.print_ab_comparison_locale_opt(other, Some(locale), Some(precision));
}
fn print_ab_comparison_locale_opt(
&self,
other: &NpvSeries,
locale: Option<&num_format::Locale>,
precision: Option<usize>,
) {
let columns = columns_with_strings(&[
("period", "i", true),
("rate_a", "f", true),
("rate_b", "f", true),
("present_value_a", "f", true),
("present_value_b", "f", true),
("future_value_a", "f", true),
("future_value_b", "f", true),
("investment_value_a", "f", true),
("investment_value_b", "f", true),
]);
let mut data = vec![];
let rows = max(self.len(), other.len());
for row_index in 0..rows {
data.push(vec![
row_index.to_string(),
self.get(row_index)
.map_or("".to_string(), |x| x.rate.to_string()),
other
.get(row_index)
.map_or("".to_string(), |x| x.rate.to_string()),
self.get(row_index)
.map_or("".to_string(), |x| x.present_value.to_string()),
other
.get(row_index)
.map_or("".to_string(), |x| x.present_value.to_string()),
self.get(row_index)
.map_or("".to_string(), |x| x.future_value.to_string()),
other
.get(row_index)
.map_or("".to_string(), |x| x.future_value.to_string()),
self.get(row_index)
.map_or("".to_string(), |x| x.investment_value.to_string()),
other
.get(row_index)
.map_or("".to_string(), |x| x.investment_value.to_string()),
]);
}
print_table_locale_opt(&columns, data, locale, precision);
}
pub fn max_discounted_cashflow(&self) -> Option<f64> {
self.iter()
.skip(1)
.map(|x| x.present_value())
.reduce(f64::max)
}
pub fn min_discounted_cashflow(&self) -> Option<f64> {
self.iter()
.skip(1)
.map(|x| x.present_value())
.reduce(f64::min)
}
}
impl Deref for NpvSeries {
type Target = Vec<NpvPeriod>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug)]
pub struct NpvPeriod {
period: u32,
rate: f64,
present_value: f64,
future_value: f64,
investment_value: f64,
formula: String,
formula_symbolic: String,
}
impl NpvPeriod {
pub fn new(
period: u32,
rate: f64,
present_value: f64,
future_value: f64,
investment_value: f64,
formula: String,
formula_symbolic: String,
) -> Self {
Self {
period,
rate,
present_value,
future_value,
investment_value,
formula,
formula_symbolic,
}
}
pub fn period(&self) -> u32 {
self.period
}
pub fn rate(&self) -> f64 {
self.rate
}
pub fn present_value(&self) -> f64 {
self.present_value
}
pub fn future_value(&self) -> f64 {
self.future_value
}
pub fn investment_value(&self) -> f64 {
self.investment_value
}
pub fn formula(&self) -> &str {
&self.formula
}
pub fn formula_symbolic(&self) -> &str {
&self.formula_symbolic
}
}
pub(crate) fn net_present_value_schedule_series(schedule: &NpvSolution) -> NpvSeries {
let mut series = vec![];
let periods = schedule.periods();
let mut investment_value = 0_f64;
for period in 0..=periods {
let rate = if period == 0 {
0.0
} else {
schedule.rates()[(period - 1) as usize]
};
let future_value = schedule.cashflows[period as usize];
let present_value = schedule.cashflows[period as usize] / (1. + rate).powf(period as f64);
debug_assert!(present_value.is_finite());
investment_value += present_value;
let formula = format!(
"{:.4} = {:.4} / (1 + {:.6})^{}",
present_value, future_value, rate, period
);
let formula_symbolic = "present_value = fv / (1 + rate)^periods".to_string();
series.push(NpvPeriod::new(
period,
rate,
present_value,
future_value,
investment_value,
formula,
formula_symbolic,
))
}
NpvSeries::new(series)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_net_present_value_1() {
let rate = 0.034;
let periods = 10;
let ii = -1000;
let cf = 500;
let npv = net_present_value(rate, periods, ii, cf).unwrap();
assert_approx_equal!(3179.3410288, npv);
}
#[test]
fn test_net_present_value_2() {
let rate = 0.034;
let periods = 400;
let ii = -1000;
let cf = 500;
let npv = net_present_value(rate, periods, ii, cf).unwrap();
assert_eq!(13_705.85948, (100_000. * npv).round() / 100_000.);
}
#[test]
fn test_net_present_value_3() {
let rates = vec![0.034, 0.089, 0.055];
let cashflows = vec![-1000, 200, 300, 500];
let npv = net_present_value_schedule(&rates, &cashflows).unwrap();
assert_eq!(-127.80162, (100_000. * npv).round() / 100_000.);
}
#[test]
fn test_net_present_value_4() {
let rates = vec![0.034, 0.089, 0.055];
let cashflows = vec![-1000, 200, 300, 500];
let npv = net_present_value_schedule_solution(&rates, &cashflows).unwrap();
assert_eq!(-127.80162, (100_000. * npv.npv()).round() / 100_000.);
}
#[test]
fn test_net_present_value_5() {
let rates = vec![0.034, -0.0989, 0.055, -0.02];
let cashflows = vec![-1000, 1000, 500, -250, -250];
let npv = net_present_value_schedule_solution(&rates, &cashflows).unwrap();
assert_eq!(
98.950922304,
(10_000_000_000. * npv.npv()).round() / 10_000_000_000.
);
}
}