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