rust_finprim/derivatives/
pv.rs

1use crate::FloatLike;
2
3/// PV'(r) - Derivative of the present value of a cash flow with respect to the rate.
4///
5/// # Arguments
6/// * `rate` - The discount rate per period
7/// * `n` - The nth period
8/// * `cash_flow` - The cash flow at period n
9///
10/// # Returns
11/// * The derivative of the present value (PV) with respect to the rate
12///
13/// Via the sum and difference property, this can be used for finding the derivative of
14/// an NPV calculation with respect to the rate.
15pub fn pv_prime_r<T: FloatLike>(rate: T, n: T, cash_flow: T) -> T {
16    -cash_flow * n / (rate + T::one()).powf(n + T::one())
17}
18
19/// NPV'(r) - Derivative of the net present value with respect to the rate.
20/// # Arguments
21/// * `rate` - The discount rate per period
22/// * `cash_flows` - A slice of cash flows, where each cash flow is at a specific period
23///
24/// # Returns
25/// * The derivative of the net present value (NPV) with respect to the rate
26///
27/// This function calculates the derivative of NPV.
28///
29/// Optimizations over summing `pv_prime_r` for each cash flow individually.
30pub fn npv_prime_r<T: FloatLike>(rate: T, cash_flows: &[T]) -> T {
31    // Accumulator for the power of (1 + rate)
32    // (1 + rate)^(1 + t) so first iteration is (1 + rate)^1 = (1 + rate)
33    let mut powf_acc = T::one() + rate;
34    let mut npv_prime = T::zero();
35    for (t, &cf) in cash_flows.iter().enumerate() {
36        npv_prime += -cf * T::from_usize(t) / powf_acc;
37        powf_acc *= T::one() + rate;
38    }
39    npv_prime
40}
41
42/// PV''(r) - Second derivative of the present value of a cash flow with respect to the rate.
43///
44/// # Arguments
45/// * `rate` - The discount rate per period
46/// * `n` - The nth period
47/// * `cash_flow` - The cash flow at period n
48///
49/// # Returns
50/// * The second derivative of the present value (PV) with respect to the rate
51///
52/// Via the sum and difference property, this can be used for finding the 2nd derivative of
53/// an NPV calculation with respect to the rate.
54pub fn pv_prime2_r<T: FloatLike>(rate: T, n: T, cash_flow: T) -> T {
55    cash_flow * n * (n + T::one()) / (rate + T::one()).powf(n + T::two())
56}
57
58/// NPV''(r) - Second derivative of the net present value with respect to the rate.
59///
60/// # Arguments
61/// * `rate` - The discount rate per period
62/// * `cash_flows` - A slice of cash flows, where each cash flow is at a specific period
63///
64/// # Returns
65/// * The second derivative of the net present value (NPV) with respect to the rate
66///
67/// This function calculates the second derivative of NPV.
68/// Optimizations over summing `pv_prime2_r` for each cash flow individually.
69pub fn npv_prime2_r<T: FloatLike>(rate: T, cash_flows: &[T]) -> T {
70    // Accumulator for the power of (1 + rate)
71    // (1 + rate)^(2 + t) so first iteration is (1 + rate)^2 = (1 + rate) * (1 + rate)
72    let mut powf_acc = (T::one() + rate) * (T::one() + rate);
73    let mut npv_prime2 = T::zero();
74    for (t, &cf) in cash_flows.iter().enumerate() {
75        let t = T::from_usize(t);
76        npv_prime2 += cf * t * (t + T::one()) / powf_acc;
77        powf_acc *= T::one() + rate;
78    }
79    npv_prime2
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    #[cfg(not(feature = "std"))]
86    extern crate std;
87    #[cfg(not(feature = "std"))]
88    use std::{assert, vec};
89
90    #[test]
91    fn test_pv_prime() {
92        let rate = 0.05;
93        let n = 5.0;
94        let cash_flow = 1000.0;
95
96        let result = pv_prime_r(rate, n, cash_flow);
97        let expected: f64 = -3731.07698;
98        assert!(
99            (result - expected).abs() < 1e-5,
100            "Failed on case: {}. Expected: {}, Result: {}",
101            "Rate of 5%, 5th period, cash flow of $1000",
102            expected,
103            result
104        );
105    }
106
107    #[test]
108    fn test_pv_double_prime() {
109        let rate = 0.05;
110        let n = 5.0;
111        let cash_flow = 1000.0;
112
113        let result = pv_prime2_r(rate, n, cash_flow);
114        let expected: f64 = 21320.43990;
115        assert!(
116            (result - expected).abs() < 1e-5,
117            "Failed on case: {}. Expected: {}, Result: {}",
118            "Rate of 5%, 5th period, cash flow of $1000",
119            expected,
120            result
121        );
122    }
123
124    #[test]
125    fn test_npv_prime() {
126        let rate = 0.05;
127        let cash_flows = vec![1000.0, 2000.0, 3000.0];
128        let expected = cash_flows
129            .iter()
130            .enumerate()
131            .map(|(t, &cf)| pv_prime_r(rate, t as f64, cf))
132            .sum::<f64>();
133        let result = npv_prime_r(rate, &cash_flows);
134        assert!(
135            (result - expected).abs() < 1e-5,
136            "Failed on case: {}. Expected: {}, Result: {}",
137            "Rate of 5%, cash flows of $1000, $2000, $3000",
138            expected,
139            result
140        );
141    }
142
143    #[test]
144    fn test_npv_double_prime() {
145        let rate = 0.05;
146        let cash_flows = vec![1000.0, 2000.0, 3000.0];
147        let expected = cash_flows
148            .iter()
149            .enumerate()
150            .map(|(t, &cf)| pv_prime2_r(rate, t as f64, cf))
151            .sum::<f64>();
152
153        let result = npv_prime2_r(rate, &cash_flows);
154        assert!(
155            (result - expected).abs() < 1e-5,
156            "Failed on case: {}. Expected: {}, Result: {}",
157            "Rate of 5%, cash flows of $1000, $2000, $3000",
158            expected,
159            result
160        );
161    }
162}