Finance.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. <?php
  2. namespace MathPHP;
  3. use MathPHP\Exception\OutOfBoundsException;
  4. /**
  5. * General references on financial functions and formulas:
  6. * - Open Document Format for Office Applications (OpenDocument) Version 1.2 Part 2:
  7. * Recalculated Formula (OpenFormula) Format. 29 September 2011. OASIS Standard.
  8. * http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part2.html#__RefHeading__1018228_715980110
  9. * - https://wiki.openoffice.org/wiki/Documentation/How_Tos/Calc:_Derivation_of_Financial_Formulas#Loans_and_Annuities
  10. */
  11. class Finance
  12. {
  13. /**
  14. * Floating-point range near zero to consider insignificant.
  15. */
  16. public const EPSILON = 1e-6;
  17. /**
  18. * Consider any floating-point value less than epsilon from zero as zero,
  19. * ie any value in the range [-epsilon < 0 < epsilon] is considered zero.
  20. * Also used to convert -0.0 to 0.0.
  21. *
  22. * @param float $value
  23. * @param float $epsilon
  24. *
  25. * @return float
  26. */
  27. private static function checkZero(float $value, float $epsilon = self::EPSILON): float
  28. {
  29. return \abs($value) < $epsilon ? 0.0 : $value;
  30. }
  31. /**
  32. * Financial payment for a loan or annuity with compound interest.
  33. * Determines the periodic payment amount for a given interest rate,
  34. * principal, targeted payment goal, life of the annuity as number
  35. * of payments, and whether the payments are made at the start or end
  36. * of each payment period.
  37. *
  38. * Same as the =PMT() function in most spreadsheet software.
  39. *
  40. * The basic monthly payment formula derivation:
  41. * https://en.wikipedia.org/wiki/Mortgage_calculator#Monthly_payment_formula
  42. *
  43. * rP(1+r)ᴺ
  44. * PMT = --------
  45. * (1+r)ᴺ-1
  46. *
  47. * The formula is adjusted to allow targeting any future value rather than 0.
  48. * The 1/(1+r*when) factor adjusts the payment to the beginning or end
  49. * of the period. In the common case of a payment at the end of a period,
  50. * the factor is 1 and reduces to the formula above. Setting when=1 computes
  51. * an "annuity due" with an immediate payment.
  52. *
  53. * Examples:
  54. * The payment on a 30-year fixed mortgage note of $265000 at 3.5% interest
  55. * paid at the end of every month.
  56. * pmt(0.035/12, 30*12, 265000, 0, false)
  57. *
  58. * The payment on a 30-year fixed mortgage note of $265000 at 3.5% interest
  59. * needed to half the principal in half in 5 years:
  60. * pmt(0.035/12, 5*12, 265000, 265000/2, false)
  61. *
  62. * The weekly payment into a savings account with 1% interest rate and current
  63. * balance of $1500 needed to reach $10000 after 3 years:
  64. * pmt(0.01/52, 3*52, -1500, 10000, false)
  65. * The present_value is negative indicating money put into the savings account,
  66. * whereas future_value is positive, indicating money that will be withdrawn from
  67. * the account. Similarly, the payment value is negative
  68. *
  69. * How much money can be withdrawn at the end of every quarter from an account
  70. * with $1000000 earning 4% so the money lasts 20 years:
  71. * pmt(0.04/4, 20*4, 1000000, 0, false)
  72. *
  73. * @param float $rate
  74. * @param int $periods
  75. * @param float $present_value
  76. * @param float $future_value
  77. * @param bool $beginning adjust the payment to the beginning or end of the period
  78. *
  79. * @return float
  80. */
  81. public static function pmt(float $rate, int $periods, float $present_value, float $future_value = 0.0, bool $beginning = false): float
  82. {
  83. $when = $beginning ? 1 : 0;
  84. if ($rate == 0) {
  85. return - ($future_value + $present_value) / $periods;
  86. }
  87. return - ($future_value + ($present_value * \pow(1 + $rate, $periods)))
  88. /
  89. ((1 + $rate * $when) / $rate * (\pow(1 + $rate, $periods) - 1));
  90. }
  91. /**
  92. * Interest on a financial payment for a loan or annuity with compound interest.
  93. * Determines the interest payment at a particular period of the annuity. For
  94. * a typical loan paid down to zero, the amount of interest and principle paid
  95. * throughout the lifetime of the loan will change, with the interest portion
  96. * of the payment decreasing over time as the loan principle decreases.
  97. *
  98. * Same as the =IPMT() function in most spreadsheet software.
  99. *
  100. * See the PMT function for derivation of the formula. For IPMT, we have
  101. * the payment equal to the interest portion and principle portion of the payment:
  102. *
  103. * PMT = IPMT + PPMT
  104. *
  105. * The interest portion IPMT on a regular annuity can be calculated by computing
  106. * the future value of the annuity for the prior period and computing the compound
  107. * interest for one period:
  108. *
  109. * IPMT = FV(p=n-1) * rate
  110. *
  111. * For an "annuity due" where payment is at the start of the period, period=1 has
  112. * no interest portion of the payment because no time has elapsed for compounding.
  113. * To compute the interest portion of the payment, the future value of 2 periods
  114. * back needs to be computed, as the definition of a period is different, giving:
  115. *
  116. * IPMT = (FV(p=n-2) - PMT) * rate
  117. *
  118. * By thinking of the future value at period 0 instead of the present value, the
  119. * given formulas are computed.
  120. *
  121. * Example of regular annuity and annuity due for a loan of $10.00 paid back in 3 periods.
  122. * Although the principle payments are equal, the total payment and interest portion are
  123. * lower with the annuity due because a principle payment is made immediately.
  124. *
  125. * Regular Annuity | Annuity Due
  126. * Period FV PMT IPMT PPMT | PMT IPMT PPMT
  127. * 0 -10.00 |
  128. * 1 -6.83 -3.67 -0.50 -3.17 | -3.50 0.00 -3.50
  129. * 2 -3.50 -3.67 -0.34 -3.33 | -3.50 -0.33 -3.17
  130. * 3 0.00 -3.67 -0.17 -3.50 | -3.50 -0.17 -3.33
  131. * -----------------------|----------------------
  132. * SUM -11.01 -1.01 -10.00 | -10.50 -0.50 -10.00
  133. *
  134. * Examples:
  135. * The interest on a payment on a 30-year fixed mortgage note of $265000 at 3.5% interest
  136. * paid at the end of every month, looking at the first payment:
  137. * ipmt(0.035/12, 1, 30*12, 265000, 0, false)
  138. *
  139. * @param float $rate
  140. * @param int $period
  141. * @param int $periods
  142. * @param float $present_value
  143. * @param float $future_value
  144. * @param bool $beginning adjust the payment to the beginning or end of the period
  145. *
  146. * @return float
  147. */
  148. public static function ipmt(float $rate, int $period, int $periods, float $present_value, float $future_value = 0.0, bool $beginning = false): float
  149. {
  150. if ($period < 1 || $period > $periods) {
  151. return \NAN;
  152. }
  153. if ($rate == 0) {
  154. return 0;
  155. }
  156. if ($beginning && $period == 1) {
  157. return 0.0;
  158. }
  159. $payment = self::pmt($rate, $periods, $present_value, $future_value, $beginning);
  160. if ($beginning) {
  161. $interest = (self::fv($rate, $period - 2, $payment, $present_value, $beginning) - $payment) * $rate;
  162. } else {
  163. $interest = self::fv($rate, $period - 1, $payment, $present_value, $beginning) * $rate;
  164. }
  165. return self::checkZero($interest);
  166. }
  167. /**
  168. * Principle on a financial payment for a loan or annuity with compound interest.
  169. * Determines the principle payment at a particular period of the annuity. For
  170. * a typical loan paid down to zero, the amount of interest and principle paid
  171. * throughout the lifetime of the loan will change, with the principle portion
  172. * of the payment increasing over time as the loan principle decreases.
  173. *
  174. * Same as the =PPMT() function in most spreadsheet software.
  175. *
  176. * See the PMT function for derivation of the formula.
  177. * See the IPMT function for derivation and use of PMT, IPMT, and PPMT.
  178. *
  179. * With derivations for PMT and IPMT, we simply compute:
  180. *
  181. * PPMT = PMT - IPMT
  182. *
  183. * Examples:
  184. * The principle on a payment on a 30-year fixed mortgage note of $265000 at 3.5% interest
  185. * paid at the end of every month, looking at the first payment:
  186. * ppmt(0.035/12, 1, 30*12, 265000, 0, false)
  187. *
  188. * @param float $rate
  189. * @param int $period
  190. * @param int $periods
  191. * @param float $present_value
  192. * @param float $future_value
  193. * @param bool $beginning adjust the payment to the beginning or end of the period
  194. *
  195. * @return float
  196. */
  197. public static function ppmt(float $rate, int $period, int $periods, float $present_value, float $future_value = 0.0, bool $beginning = false): float
  198. {
  199. $payment = self::pmt($rate, $periods, $present_value, $future_value, $beginning);
  200. $ipmt = self::ipmt($rate, $period, $periods, $present_value, $future_value, $beginning);
  201. return $payment - $ipmt;
  202. }
  203. /**
  204. * Number of payment periods of an annuity.
  205. * Solves for the number of periods in the annuity formula.
  206. *
  207. * Same as the =NPER() function in most spreadsheet software.
  208. *
  209. * Solving the basic annuity formula for number of periods:
  210. * log(PMT - FV*r)
  211. * ---------------
  212. * log(PMT + PV*r)
  213. * n = --------------------
  214. * log(1 + r)
  215. *
  216. * The (1+r*when) factor adjusts the payment to the beginning or end
  217. * of the period. In the common case of a payment at the end of a period,
  218. * the factor is 1 and reduces to the formula above. Setting when=1 computes
  219. * an "annuity due" with an immediate payment.
  220. *
  221. * Examples:
  222. * The number of periods of a $475000 mortgage with interest rate 3.5% and monthly
  223. * payment of $2132.96 paid in full:
  224. * nper(0.035/12, -2132.96, 475000, 0)
  225. *
  226. * @param float $rate
  227. * @param float $payment
  228. * @param float $present_value
  229. * @param float $future_value
  230. * @param bool $beginning adjust the payment to the beginning or end of the period
  231. *
  232. * @return float
  233. */
  234. public static function periods(float $rate, float $payment, float $present_value, float $future_value, bool $beginning = false): float
  235. {
  236. $when = $beginning ? 1 : 0;
  237. if ($rate == 0) {
  238. return - ($present_value + $future_value) / $payment;
  239. }
  240. $initial = $payment * (1.0 + $rate * $when);
  241. return \log(($initial - $future_value * $rate) / ($initial + $present_value * $rate)) / \log(1.0 + $rate);
  242. }
  243. /**
  244. * Annual Equivalent Rate (AER) of an annual percentage rate (APR).
  245. * The effective yearly rate of an annual percentage rate when the
  246. * annual percentage rate is compounded periodically within the year.
  247. *
  248. * Same as the =EFFECT() function in most spreadsheet software.
  249. *
  250. * The formula:
  251. * https://en.wikipedia.org/wiki/Effective_interest_rate
  252. *
  253. * / i \ ᴺ
  254. * AER = | 1 + - | - 1
  255. * \ n /
  256. *
  257. * Examples:
  258. * The AER of APR 3.5% interest compounded monthly.
  259. * aer(0.035, 12)
  260. *
  261. * @param float $nominal
  262. * @param int $periods
  263. *
  264. * @return float
  265. */
  266. public static function aer(float $nominal, int $periods): float
  267. {
  268. if ($periods == 1) {
  269. return $nominal;
  270. }
  271. return \pow(1 + ($nominal / $periods), $periods) - 1;
  272. }
  273. /**
  274. * Annual Nominal Rate of an annual effective rate (AER).
  275. * The nominal yearly rate of an annual effective rate when the
  276. * annual effective rate is compounded periodically within the year.
  277. *
  278. * Same as the =NOMINAL() function in most spreadsheet software.
  279. *
  280. * See:
  281. * https://en.wikipedia.org/wiki/Nominal_interest_rate
  282. *
  283. * / 1/N \
  284. * NOMINAL = | (AER + 1) -1 | * N
  285. * \ /
  286. *
  287. * Examples:
  288. * The nominal rate of AER 3.557% interest compounded monthly.
  289. * nominal(0.03557, 12)
  290. *
  291. * @param float $aer
  292. * @param int $periods
  293. *
  294. * @return float
  295. */
  296. public static function nominal(float $aer, int $periods): float
  297. {
  298. if ($periods == 1) {
  299. return $aer;
  300. }
  301. return (\pow($aer + 1, 1 / $periods) - 1) * $periods;
  302. }
  303. /**
  304. * Future value for a loan or annuity with compound interest.
  305. *
  306. * Same as the =FV() function in most spreadsheet software.
  307. *
  308. * The basic future-value formula derivation:
  309. * https://en.wikipedia.org/wiki/Future_value
  310. *
  311. * PMT*((1+r)ᴺ - 1)
  312. * FV = -PV*(1+r)ᴺ - ----------------
  313. * r
  314. *
  315. * The (1+r*when) factor adjusts the payment to the beginning or end
  316. * of the period. In the common case of a payment at the end of a period,
  317. * the factor is 1 and reduces to the formula above. Setting when=1 computes
  318. * an "annuity due" with an immediate payment.
  319. *
  320. * Examples:
  321. * The future value in 5 years on a 30-year fixed mortgage note of $265000
  322. * at 3.5% interest paid at the end of every month. This is how much loan
  323. * principle would be outstanding:
  324. * fv(0.035/12, 5*12, 1189.97, -265000, false)
  325. *
  326. * The present_value is negative indicating money borrowed for the mortgage,
  327. * whereas payment is positive, indicating money that will be paid to the
  328. * mortgage.
  329. *
  330. * @param float $rate
  331. * @param int $periods
  332. * @param float $payment
  333. * @param float $present_value
  334. * @param bool $beginning adjust the payment to the beginning or end of the period
  335. *
  336. * @return float
  337. */
  338. public static function fv(float $rate, int $periods, float $payment, float $present_value, bool $beginning = false): float
  339. {
  340. $when = $beginning ? 1 : 0;
  341. if ($rate == 0) {
  342. $fv = -($present_value + ($payment * $periods));
  343. return self::checkZero($fv);
  344. }
  345. $initial = 1 + ($rate * $when);
  346. $compound = \pow(1 + $rate, $periods);
  347. $fv = - (($present_value * $compound) + (($payment * $initial * ($compound - 1)) / $rate));
  348. return self::checkZero($fv);
  349. }
  350. /**
  351. * Present value for a loan or annuity with compound interest.
  352. *
  353. * Same as the =PV() function in most spreadsheet software.
  354. *
  355. * The basic present-value formula derivation:
  356. * https://en.wikipedia.org/wiki/Present_value
  357. *
  358. * PMT*((1+r)ᴺ - 1)
  359. * PV = -FV - ----------------
  360. * r
  361. * ---------------------
  362. * (1 + r)ᴺ
  363. *
  364. * The (1+r*when) factor adjusts the payment to the beginning or end
  365. * of the period. In the common case of a payment at the end of a period,
  366. * the factor is 1 and reduces to the formula above. Setting when=1 computes
  367. * an "annuity due" with an immediate payment.
  368. *
  369. * Examples:
  370. * The present value of a bond's $1000 face value paid in 5 year's time
  371. * with a constant discount rate of 3.5% compounded monthly:
  372. * pv(0.035/12, 5*12, 0, -1000, false)
  373. *
  374. * The present value of a $1000 5-year bond that pays a fixed 7% ($70)
  375. * coupon at the end of each year with a discount rate of 5%:
  376. * pv(0.5, 5, -70, -1000, false)
  377. *
  378. * The payment and future_value is negative indicating money paid out.
  379. *
  380. * @param float $rate
  381. * @param int $periods
  382. * @param float $payment
  383. * @param float $future_value
  384. * @param bool $beginning adjust the payment to the beginning or end of the period
  385. *
  386. * @return float
  387. */
  388. public static function pv(float $rate, int $periods, float $payment, float $future_value, bool $beginning = false): float
  389. {
  390. $when = $beginning ? 1 : 0;
  391. if ($rate == 0) {
  392. $pv = -$future_value - ($payment * $periods);
  393. return self::checkZero($pv);
  394. }
  395. $initial = 1 + ($rate * $when);
  396. $compound = \pow(1 + $rate, $periods);
  397. $pv = (-$future_value - (($payment * $initial * ($compound - 1)) / $rate)) / $compound;
  398. return self::checkZero($pv);
  399. }
  400. /**
  401. * Net present value of cash flows. Cash flows are periodic starting
  402. * from an initial time and with a uniform discount rate.
  403. *
  404. * Similar to the =NPV() function in most spreadsheet software, except
  405. * the initial (usually negative) cash flow at time 0 is given as the
  406. * first element of the array rather than subtracted. For example,
  407. * spreadsheet: =NPV(0.01, 100, 200, 300, 400) - 1000
  408. * is done as
  409. * MathPHP::npv(0.01, [-1000, 100, 200, 300, 400])
  410. *
  411. * The basic net-present-value formula derivation:
  412. * https://en.wikipedia.org/wiki/Net_present_value
  413. *
  414. * n Rt
  415. * Σ --------
  416. * t=0 (1 / r)ᵗ
  417. *
  418. * Examples:
  419. * The net present value of 5 yearly cash flows after an initial $1000
  420. * investment with a 3% discount rate:
  421. * npv(0.03, [-1000, 100, 500, 300, 700, 700])
  422. *
  423. * @param float $rate
  424. * @param array<float> $values
  425. *
  426. * @return float
  427. */
  428. public static function npv(float $rate, array $values): float
  429. {
  430. $result = 0.0;
  431. for ($i = 0; $i < \count($values); ++$i) {
  432. $result += $values[$i] / (1 + $rate) ** $i;
  433. }
  434. return $result;
  435. }
  436. /**
  437. * Interest rate per period of an Annuity.
  438. *
  439. * Same as the =RATE() formula in most spreadsheet software.
  440. *
  441. * The basic rate formula derivation is to solve for the future value
  442. * taking into account the present value:
  443. * https://en.wikipedia.org/wiki/Future_value
  444. *
  445. * ((1+r)ᴺ - 1)
  446. * FV + PV*(1+r)ᴺ + PMT * ------------ = 0
  447. * r
  448. * The (1+r*when) factor adjusts the payment to the beginning or end
  449. * of the period. In the common case of a payment at the end of a period,
  450. * the factor is 1 and reduces to the formula above. Setting when=1 computes
  451. * an "annuity due" with an immediate payment.
  452. *
  453. * Not all solutions for the rate have real-value solutions or converge.
  454. * In these cases, NAN is returned.
  455. *
  456. * @param float $periods
  457. * @param float $payment
  458. * @param float $present_value
  459. * @param float $future_value
  460. * @param bool $beginning
  461. * @param float $initial_guess
  462. *
  463. * @return float
  464. */
  465. public static function rate(float $periods, float $payment, float $present_value, float $future_value, bool $beginning = false, float $initial_guess = 0.1): float
  466. {
  467. $when = $beginning ? 1 : 0;
  468. $func = function ($x, $periods, $payment, $present_value, $future_value, $when) {
  469. return $future_value + $present_value * (1 + $x) ** $periods + $payment * (1 + $x * $when) / $x * ((1 + $x) ** $periods - 1);
  470. };
  471. return self::checkZero(NumericalAnalysis\RootFinding\NewtonsMethod::solve($func, [$initial_guess, $periods, $payment, $present_value, $future_value, $when], 0, self::EPSILON, 0));
  472. }
  473. /**
  474. * Internal rate of return.
  475. * Periodic rate of return that would provide a net-present value (NPV) of 0.
  476. *
  477. * Same as =IRR formula in most spreadsheet software.
  478. *
  479. * Reference:
  480. * https://en.wikipedia.org/wiki/Internal_rate_of_return
  481. *
  482. * Examples:
  483. * The rate of return of an initial investment of $100 with returns
  484. * of $50, $40, and $30:
  485. * irr([-100, 50, 40, 30])
  486. *
  487. * Solves for NPV=0 using Newton's Method.
  488. * @param array<float> $values
  489. * @param float $initial_guess
  490. *
  491. * @return float
  492. *
  493. * @throws OutOfBoundsException
  494. *
  495. * @todo: Use eigenvalues to find the roots of a characteristic polynomial.
  496. * This will allow finding all solutions and eliminate the need of the initial_guess.
  497. */
  498. public static function irr(array $values, float $initial_guess = 0.1): float
  499. {
  500. $func = function ($x, $values) {
  501. return Finance::npv($x, $values);
  502. };
  503. if (\count($values) <= 1) {
  504. return \NAN;
  505. }
  506. $root = NumericalAnalysis\RootFinding\NewtonsMethod::solve($func, [$initial_guess, $values], 0, self::EPSILON, 0);
  507. if (!\is_nan($root)) {
  508. return self::CheckZero($root);
  509. }
  510. return self::checkZero(self::alternateIrr($values));
  511. }
  512. /**
  513. * Alternate IRR implementation.
  514. *
  515. * A more numerically stable implementation that converges to only one value.
  516. *
  517. * Based off of Better: https://github.com/better/irr
  518. *
  519. * @param array<float> $values
  520. *
  521. * @return float
  522. */
  523. private static function alternateIrr(array $values): float
  524. {
  525. $rate = 0.0;
  526. for ($iter = 0; $iter < 100; $iter++) {
  527. $m = -1000;
  528. for ($i = 0; $i < \count($values); $i++) {
  529. $m = \max($m, -$rate * $i);
  530. }
  531. $f = [];
  532. for ($i = 0; $i < \count($values); $i++) {
  533. $f[$i] = \exp(-$rate * $i - $m);
  534. }
  535. $t = 0;
  536. for ($i = 0; $i < \count($values); $i++) {
  537. $t += $f[$i] * $values[$i];
  538. }
  539. if (\abs($t) < (self::EPSILON * \exp($m))) {
  540. break;
  541. }
  542. $u = 0;
  543. for ($i = 0; $i < \count($values); $i++) {
  544. $u += $f[$i] * $i * $values[$i];
  545. }
  546. if ($u == 0) {
  547. return \NAN;
  548. }
  549. $rate += $t / $u;
  550. }
  551. return \exp($rate) - 1;
  552. }
  553. /**
  554. * Modified internal rate of return.
  555. * Rate of return that discounts outflows (investments) at the financing rate,
  556. * and reinvests inflows with an expected rate of return.
  557. *
  558. * Same as =MIRR formula in most spreadsheet software.
  559. *
  560. * The formula derivation:
  561. * https://en.wikipedia.org/wiki/Modified_internal_rate_of_return
  562. *
  563. * _____________________________
  564. * n/ FV(re-invested cash inflows)
  565. * - / ---------------------------- - 1.0
  566. * \/ PV(discounted cash outflows)
  567. *
  568. * Examples:
  569. * The rate of return of an initial investment of $100 at 5% financing
  570. * with returns of $50, $40, and $30 reinvested at 10%:
  571. * mirr([-100, 50, 40, 30], 0.05, 0.10)
  572. *
  573. * @param array<float> $values
  574. * @param float $finance_rate
  575. * @param float $reinvestment_rate
  576. *
  577. * @return float
  578. */
  579. public static function mirr(array $values, float $finance_rate, float $reinvestment_rate): float
  580. {
  581. $inflows = array();
  582. $outflows = array();
  583. for ($i = 0; $i < \count($values); $i++) {
  584. if ($values[$i] >= 0) {
  585. $inflows[] = $values[$i];
  586. $outflows[] = 0;
  587. } else {
  588. $inflows[] = 0;
  589. $outflows[] = $values[$i];
  590. }
  591. }
  592. $nonzero = function ($x) {
  593. return $x != 0;
  594. };
  595. if (\count(\array_filter($inflows, $nonzero)) == 0 || \count(\array_filter($outflows, $nonzero)) == 0) {
  596. return \NAN;
  597. }
  598. $root = \count($values) - 1;
  599. $pv_inflows = self::npv($reinvestment_rate, $inflows);
  600. $fv_inflows = self::fv($reinvestment_rate, $root, 0, -$pv_inflows);
  601. $pv_outflows = self::npv($finance_rate, $outflows);
  602. return self::checkZero(\pow($fv_inflows / -$pv_outflows, 1 / $root) - 1);
  603. }
  604. /**
  605. * Discounted Payback of an investment.
  606. * The number of periods to recoup cash outlays of an investment.
  607. *
  608. * This is commonly used with discount rate=0 as simple payback period,
  609. * but it is not a real financial measurement when it doesn't consider the
  610. * discount rate. Even with a discount rate, it doesn't consider the cost
  611. * of capital or re-investment of returns.
  612. *
  613. * Avoid this when possible. Consider NPV, MIRR, IRR, and other financial
  614. * functions.
  615. *
  616. * Reference:
  617. * https://en.wikipedia.org/wiki/Payback_period
  618. *
  619. * The result is given assuming cash flows are continous throughout a period.
  620. * To compute payback in terms of whole periods, use ceil() on the result.
  621. *
  622. * An investment could reach its payback period before future cash outlays occur.
  623. * The payback period returned is defined to be the final point at which the
  624. * sum of returns becomes positive.
  625. *
  626. * Examples:
  627. * The payback period of an investment with a $1,000 investment and future returns
  628. * of $100, $200, $300, $400, $500:
  629. * payback([-1000, 100, 200, 300, 400, 500])
  630. *
  631. * The discounted payback period of an investment with a $1,000 investment, future returns
  632. * of $100, $200, $300, $400, $500, and a discount rate of 0.10:
  633. * payback([-1000, 100, 200, 300, 400, 500], 0.1)
  634. *
  635. * @param array<float> $values
  636. * @param float $rate
  637. *
  638. * @return float
  639. */
  640. public static function payback(array $values, float $rate = 0.0): float
  641. {
  642. $last_outflow = -1;
  643. for ($i = 0; $i < \count($values); $i++) {
  644. if ($values[$i] < 0) {
  645. $last_outflow = $i;
  646. }
  647. }
  648. if ($last_outflow < 0) {
  649. return 0.0;
  650. }
  651. $sum = $values[0];
  652. $payback_period = -1;
  653. for ($i = 1; $i < \count($values); $i++) {
  654. $prevsum = $sum;
  655. $discounted_flow = $values[$i] / (1 + $rate) ** $i;
  656. $sum += $discounted_flow;
  657. if ($sum >= 0) {
  658. if ($i > $last_outflow) {
  659. return ($i - 1) + (-$prevsum / $discounted_flow);
  660. }
  661. if ($payback_period == -1) {
  662. $payback_period = ($i - 1) + (-$prevsum / $discounted_flow);
  663. }
  664. } else {
  665. $payback_period = -1;
  666. }
  667. }
  668. if ($sum >= 0) {
  669. return $payback_period;
  670. }
  671. return \NAN;
  672. }
  673. /**
  674. * Profitability Index.
  675. * The Profitability Index, also referred to as Profit Investment
  676. * Ratio (PIR) and Value Investment Ratio (VIR), is a comparison of
  677. * discounted cash inflows to discounted cash outflows. It can be
  678. * used as a decision criteria of an investment, using larger than 1
  679. * to choose an investment, and less than 1 to pass.
  680. *
  681. * The formula derivation:
  682. * https://en.wikipedia.org/wiki/Profitability_index
  683. *
  684. * PV(cash inflows)
  685. * ----------------
  686. * PV(cash outflows)
  687. *
  688. * The formula is usually stated in terms of the initial investmest,
  689. * but it is generalized here to discount all future outflows.
  690. *
  691. * Examples:
  692. * The profitability index of an initial $100 investment with future
  693. * returns of $50, $50, $50 with a 10% discount rate:
  694. * profitabilityIndex([-100, 50, 50, 50], 0.10)
  695. *
  696. * @param array<float> $values
  697. * @param float $rate
  698. *
  699. * @return float
  700. */
  701. public static function profitabilityIndex(array $values, float $rate): float
  702. {
  703. $inflows = array();
  704. $outflows = array();
  705. for ($i = 0; $i < \count($values); $i++) {
  706. if ($values[$i] >= 0) {
  707. $inflows[] = $values[$i];
  708. $outflows[] = 0;
  709. } else {
  710. $inflows[] = 0;
  711. $outflows[] = -$values[$i];
  712. }
  713. }
  714. $nonzero = function ($x) {
  715. return $x != 0;
  716. };
  717. if (\count(\array_filter($outflows, $nonzero)) == 0) {
  718. return \NAN;
  719. }
  720. $pv_inflows = self::npv($rate, $inflows);
  721. $pv_outflows = self::npv($rate, $outflows);
  722. return $pv_inflows / $pv_outflows;
  723. }
  724. }