#[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) -> f64
where I: Into<f64> + Copy, C: Into<f64> + Copy
{
let annuity = cashflow.into();
let ii = initial_investment.into();
let pv_cashflow = annuity * ((1. - (1. / (1. + rate)).powf(periods as f64)) / rate);
let npv = ii + pv_cashflow;
npv
}
pub fn net_present_value_solution<C, I>(rate: f64, periods: u32, initial_investment: I, cashflow: C) -> NpvSolution
where I: Into<f64> + Copy, C: Into<f64> + Copy
{
let annuity = cashflow.into();
let ii = initial_investment.into();
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]) -> 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 = pv_accumulator + present_value;
}
let npv = initial_investment + pv_accumulator;
npv
}
fn check_schedule<C>(rates:&[f64], cashflows: &[C]) -> (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;
assert!(cashflows[0] <= 0.0, "The initial investment (cashflows[0]) should be negative or zero");
assert!(cashflows.len() >= 2, "Must provide at least 2 values in cashflows, the initial investment at the 0 position and the following cashflows, or a single cashflow representing a repeating constant cashflow.");
assert!(rates.len() >= 1, "Must provide at least 1 rate.");
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 {
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 {
panic!("At least rates or cashflows for net_present_value_schedule must provide the full series of inputs. Only one input can be a shorthand expression of a repeating input. If both are repeating constant inputs, use the net_present_value function.");
}
(periods, r.to_vec(), c.to_vec(), initial_investment)
}
pub fn net_present_value_schedule_solution<C>(rates: &[f64], cashflows: &[C]) -> 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 = pv_accumulator + present_value;
sum_accumulator = 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;
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) -> f64 {
self.series().max_discounted_cashflow()
}
pub fn min_discounted_cashflow(&self) -> 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) -> f64 {
assert!(self.len() > 1);
self.iter().skip(1).fold(std::f64::MIN, |acc, x| acc.max(x.present_value()))
}
pub fn min_discounted_cashflow(&self) -> f64 {
assert!(self.len() > 1);
self.iter().skip(1).fold(std::f64::MAX, |acc, x| acc.min(x.present_value()))
}
}
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);
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);
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);
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);
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);
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);
assert_eq!(98.950922304, (10_000_000_000. * npv.npv()).round() / 10_000_000_000.);
}
}