rust_finprim/rate/mirr.rs
1use crate::rate::cagr;
2use crate::tvm::{fv, pv};
3use crate::FloatLike;
4
5/// MIRR - Modified Internal Rate of Return
6///
7/// The modified internal rate of return (MIRR) is a financial metric that adjusts the
8/// internal rate of return (IRR) to account for a different cost of capital and reinvestment rate.
9/// Similar behavior and usage to the `MIRR` function in Excel.
10///
11/// The MIRR assumes that positive cash flows are reinvested at a reinvestment rate, and
12/// any negative cash flows are financed at the cost of capital.
13///
14/// # Arguments
15/// * `cash_flows` - A slice of values representing the cash flows of the investment
16/// * `finance_rate` - The cost of capital (interest rate) for financing
17/// * `reinvest_rate` - The reinvestment rate for positive cash flows
18///
19/// # Returns
20/// * The modified internal rate of return (MIRR)
21///
22/// # Example
23/// * Cash flows of $-100, $50, $40, $30, $20, finance rate of 0.1, reinvestment rate of 0.05
24/// ```
25/// use rust_finprim::rate::mirr;
26///
27/// let cash_flows = vec![-100.0, 50.0, 40.0, 30.0, 20.0];
28/// let finance_rate = 0.1;
29/// let reinvest_rate = 0.05;
30/// mirr(&cash_flows, finance_rate, reinvest_rate);
31/// ```
32pub fn mirr<T: FloatLike>(cash_flows: &[T], finance_rate: T, reinvest_rate: T) -> T {
33 // Num of compounding perids does not include the final period
34 let n = cash_flows.len() - 1;
35
36 let mut npv_neg = T::zero();
37 let mut fv_pos = T::zero();
38 for (i, &cf) in cash_flows.iter().enumerate() {
39 if cf < T::zero() {
40 // Calculate the present value of negative cash flows
41 npv_neg += pv(finance_rate, T::from_usize(i), T::zero(), Some(cf), None);
42 } else {
43 // Calculate the future value of positive cash flows
44 fv_pos -= fv(reinvest_rate, T::from_usize(n - i), T::zero(), Some(cf), None);
45 }
46 }
47 npv_neg = npv_neg.abs(); // Ensure npv_neg is positive for the calculation
48 cagr(
49 // Calculate the CAGR using the future value of positive cash flows and the present value of negative cash flows
50 npv_neg,
51 fv_pos,
52 T::from_usize(n),
53 )
54}
55
56/// XMIRR - Modified Internal Rate of Return for Irregular Cash Flows
57///
58/// The XMIRR function calculates the modified internal rate of return for a schedule of cash flows that is not necessarily periodic.
59///
60/// # Arguments
61/// * `flow_table` - A slice of tuples representing the cash flows and dates for each period `(cash_flow, date)`
62/// where `date` represents the number of days from an arbitrary epoch. The first cash flow is assumed to be the initial investment date
63/// at time 0, the order of subsequent cash flows does not matter.
64/// * `finance_rate` - The cost of capital (interest rate) for financing
65/// * `reinvest_rate` - The reinvestment rate for positive cash flows
66///
67/// Most time libraries will provide a method for the number of days from an epoch. For example, in the `chrono` library
68/// you can use the `num_days_from_ce` method to get the number of days from the Common Era (CE) epoch, simply convert
69/// your date types to an integer representing the number of days from any epoch. Alternatively, you can calculate the
70/// time delta in days from an arbitrary epoch, such as the initial investment date.
71///
72/// Cash flows are discounted assuming a 365-day year.
73///
74/// # Returns
75/// * The modified internal rate of return (MIRR)
76///
77/// # Example
78/// * Cash flows of $-100, $-20, $20, $20, $20, finance rate of 0.1, reinvestment rate of 0.05
79/// ```
80/// use rust_finprim::rate::xmirr;
81///
82/// let flow_table = vec![
83/// (-100.0, 0),
84/// (-20.0, 359),
85/// (20.0, 400),
86/// (20.0, 1000),
87/// (20.0, 2000),
88/// ];
89/// let finance_rate = 0.1;
90/// let reinvest_rate = 0.05;
91/// xmirr(&flow_table, finance_rate, reinvest_rate);
92pub fn xmirr<T: FloatLike>(flow_table: &[(T, i32)], finance_rate: T, reinvest_rate: T) -> T {
93 let init_date = flow_table.first().unwrap().1;
94
95 let n = T::from_i32(flow_table.last().unwrap().1);
96 let mut npv_neg = T::zero();
97 let mut fv_pos = T::zero();
98 // Calculate the NPV of negative cash flows and the FV of positive cash Flows
99 for &(cf, date) in flow_table {
100 // For negative cash flows, calculate the present value
101 // For positive cash flows, calculate the future value
102 if cf < T::zero() {
103 npv_neg += pv(
104 finance_rate,
105 T::from_i32(date - init_date) / T::from_u16(365),
106 T::zero(),
107 Some(cf),
108 None,
109 );
110 } else {
111 fv_pos -= fv(
112 reinvest_rate,
113 (n - T::from_i32(date)) / T::from_u16(365),
114 T::zero(),
115 Some(cf),
116 None,
117 );
118 }
119 }
120 npv_neg.abs(); // Ensure npv_neg is positive for the calculation
121 cagr(
122 // Calculate the CAGR using the future value of positive cash flows and the present value of negative cash flows
123 npv_neg,
124 fv_pos,
125 n / T::from_u16(365), // Convert to years by dividing by 365
126 )
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[cfg(not(feature = "std"))]
134 extern crate std;
135 #[cfg(not(feature = "std"))]
136 use std::{assert, vec};
137
138 #[test]
139 fn test_mirr() {
140 let cash_flows = vec![-100.0, -20.0, 20.0, 20.0, 20.0];
141 let finance_rate = 0.1;
142 let reinvest_rate = 0.05;
143 let result = mirr(&cash_flows, finance_rate, reinvest_rate);
144 let expected: f64 = -0.14536;
145 assert!(
146 (result - expected).abs() < 1e-5,
147 "Failed on case: {}. Expected: {}, Result: {}",
148 "Cash flows of -100, -20, 20, 20, 20, finance rate of 0.1, reinvestment rate of 0.05",
149 expected,
150 result
151 );
152 }
153
154 #[test]
155 fn test_xmirr() {
156 let finance_rate = 0.1;
157 let reinvest_rate = 0.05;
158
159 // Simtle 1 year case
160 let flow_table = vec![(-100.0, 0), (-20.0, 365), (20.0, 730), (20.0, 1095), (20.0, 1460)];
161 let result = xmirr(&flow_table, finance_rate, reinvest_rate);
162 let expected: f64 = -0.14536;
163 assert!(
164 (result - expected).abs() < 1e-5,
165 "Failed on case: {}. Expected: {}, Result: {}",
166 "Cash flows of -100, -20, 20, 20, 20, finance rate of 0.1, reinvestment rate of 0.05",
167 expected,
168 result
169 );
170
171 // More complex case
172 let flow_table = vec![(-100.0, 0), (-20.0, 359), (20.0, 400), (20.0, 1000), (20.0, 2000)];
173 let result = xmirr(&flow_table, finance_rate, reinvest_rate);
174 let expected: f64 = -0.09689;
175 assert!(
176 (result - expected).abs() < 1e-5,
177 "Failed on case: {}. Expected: {}, Result: {}",
178 "Cash flows of -100, -20, 20, 20, 20, at 0, 359, 400, 1000, 2000 days,
179 finance rate of 0.1, reinvestment rate of 0.05",
180 expected,
181 result
182 );
183 }
184}