Financial start refactoring cash flow functions (#1986)
* Start extracting CashFlow functions from Financial, beginning with the simple Single Rate flows * Extracting Variable Periodic and NonPeriodic CashFlow functions from Financial * Some more unit tests for exception cases
This commit is contained in:
parent
6446039f4f
commit
42761f90b7
|
|
@ -756,21 +756,19 @@ class Financial
|
||||||
* Excel Function:
|
* Excel Function:
|
||||||
* FVSCHEDULE(principal,schedule)
|
* FVSCHEDULE(principal,schedule)
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
|
* @see Financial\CashFlow\Single::futureValue()
|
||||||
|
* Use the futureValue() method in the Financial\CashFlow\Single class instead
|
||||||
|
*
|
||||||
* @param float $principal the present value
|
* @param float $principal the present value
|
||||||
* @param float[] $schedule an array of interest rates to apply
|
* @param float[] $schedule an array of interest rates to apply
|
||||||
*
|
*
|
||||||
* @return float
|
* @return float|string
|
||||||
*/
|
*/
|
||||||
public static function FVSCHEDULE($principal, $schedule)
|
public static function FVSCHEDULE($principal, $schedule)
|
||||||
{
|
{
|
||||||
$principal = Functions::flattenSingleValue($principal);
|
return Financial\CashFlow\Single::futureValue($principal, $schedule);
|
||||||
$schedule = Functions::flattenArray($schedule);
|
|
||||||
|
|
||||||
foreach ($schedule as $rate) {
|
|
||||||
$principal *= 1 + $rate;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $principal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -876,6 +874,8 @@ class Financial
|
||||||
* Excel Function:
|
* Excel Function:
|
||||||
* IRR(values[,guess])
|
* IRR(values[,guess])
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
* @param mixed $values An array or a reference to cells that contain numbers for which you want
|
* @param mixed $values An array or a reference to cells that contain numbers for which you want
|
||||||
* to calculate the internal rate of return.
|
* to calculate the internal rate of return.
|
||||||
* Values must contain at least one positive value and one negative value to
|
* Values must contain at least one positive value and one negative value to
|
||||||
|
|
@ -883,56 +883,13 @@ class Financial
|
||||||
* @param mixed $guess A number that you guess is close to the result of IRR
|
* @param mixed $guess A number that you guess is close to the result of IRR
|
||||||
*
|
*
|
||||||
* @return float|string
|
* @return float|string
|
||||||
|
*
|
||||||
|
*@see Financial\CashFlow\Variable\Periodic::rate()
|
||||||
|
* Use the IRR() method in the Financial\CashFlow\Variable\Periodic class instead
|
||||||
*/
|
*/
|
||||||
public static function IRR($values, $guess = 0.1)
|
public static function IRR($values, $guess = 0.1)
|
||||||
{
|
{
|
||||||
if (!is_array($values)) {
|
return Financial\CashFlow\Variable\Periodic::rate($values, $guess);
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
|
||||||
$values = Functions::flattenArray($values);
|
|
||||||
$guess = Functions::flattenSingleValue($guess);
|
|
||||||
|
|
||||||
// create an initial range, with a root somewhere between 0 and guess
|
|
||||||
$x1 = 0.0;
|
|
||||||
$x2 = $guess;
|
|
||||||
$f1 = self::NPV($x1, $values);
|
|
||||||
$f2 = self::NPV($x2, $values);
|
|
||||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
|
||||||
if (($f1 * $f2) < 0.0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (abs($f1) < abs($f2)) {
|
|
||||||
$f1 = self::NPV($x1 += 1.6 * ($x1 - $x2), $values);
|
|
||||||
} else {
|
|
||||||
$f2 = self::NPV($x2 += 1.6 * ($x2 - $x1), $values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (($f1 * $f2) > 0.0) {
|
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
|
||||||
|
|
||||||
$f = self::NPV($x1, $values);
|
|
||||||
if ($f < 0.0) {
|
|
||||||
$rtb = $x1;
|
|
||||||
$dx = $x2 - $x1;
|
|
||||||
} else {
|
|
||||||
$rtb = $x2;
|
|
||||||
$dx = $x1 - $x2;
|
|
||||||
}
|
|
||||||
|
|
||||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
|
||||||
$dx *= 0.5;
|
|
||||||
$x_mid = $rtb + $dx;
|
|
||||||
$f_mid = self::NPV($x_mid, $values);
|
|
||||||
if ($f_mid <= 0.0) {
|
|
||||||
$rtb = $x_mid;
|
|
||||||
}
|
|
||||||
if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
|
|
||||||
return $x_mid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -986,6 +943,11 @@ class Financial
|
||||||
* Excel Function:
|
* Excel Function:
|
||||||
* MIRR(values,finance_rate, reinvestment_rate)
|
* MIRR(values,finance_rate, reinvestment_rate)
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
|
* @see Financial\CashFlow\Variable\Periodic::modifiedRate()
|
||||||
|
* Use the MIRR() method in the Financial\CashFlow\Variable\Periodic class instead
|
||||||
|
*
|
||||||
* @param mixed $values An array or a reference to cells that contain a series of payments and
|
* @param mixed $values An array or a reference to cells that contain a series of payments and
|
||||||
* income occurring at regular intervals.
|
* income occurring at regular intervals.
|
||||||
* Payments are negative value, income is positive values.
|
* Payments are negative value, income is positive values.
|
||||||
|
|
@ -996,34 +958,7 @@ class Financial
|
||||||
*/
|
*/
|
||||||
public static function MIRR($values, $finance_rate, $reinvestment_rate)
|
public static function MIRR($values, $finance_rate, $reinvestment_rate)
|
||||||
{
|
{
|
||||||
if (!is_array($values)) {
|
return Financial\CashFlow\Variable\Periodic::modifiedRate($values, $finance_rate, $reinvestment_rate);
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
|
||||||
$values = Functions::flattenArray($values);
|
|
||||||
$finance_rate = Functions::flattenSingleValue($finance_rate);
|
|
||||||
$reinvestment_rate = Functions::flattenSingleValue($reinvestment_rate);
|
|
||||||
$n = count($values);
|
|
||||||
|
|
||||||
$rr = 1.0 + $reinvestment_rate;
|
|
||||||
$fr = 1.0 + $finance_rate;
|
|
||||||
|
|
||||||
$npv_pos = $npv_neg = 0.0;
|
|
||||||
foreach ($values as $i => $v) {
|
|
||||||
if ($v >= 0) {
|
|
||||||
$npv_pos += $v / $rr ** $i;
|
|
||||||
} else {
|
|
||||||
$npv_neg += $v / $fr ** $i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($npv_neg == 0) || ($npv_pos == 0) || ($reinvestment_rate <= -1)) {
|
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
|
||||||
|
|
||||||
$mirr = ((-$npv_pos * $rr ** $n)
|
|
||||||
/ ($npv_neg * ($rr))) ** (1.0 / ($n - 1)) - 1.0;
|
|
||||||
|
|
||||||
return is_finite($mirr) ? $mirr : Functions::VALUE();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1094,28 +1029,16 @@ class Financial
|
||||||
*
|
*
|
||||||
* Returns the Net Present Value of a cash flow series given a discount rate.
|
* Returns the Net Present Value of a cash flow series given a discount rate.
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
* @return float
|
* @return float
|
||||||
|
*
|
||||||
|
*@see Financial\CashFlow\Variable\Periodic::presentValue()
|
||||||
|
* Use the NPV() method in the Financial\CashFlow\Variable\Periodic class instead
|
||||||
*/
|
*/
|
||||||
public static function NPV(...$args)
|
public static function NPV(...$args)
|
||||||
{
|
{
|
||||||
// Return value
|
return Financial\CashFlow\Variable\Periodic::presentValue(...$args);
|
||||||
$returnValue = 0;
|
|
||||||
|
|
||||||
// Loop through arguments
|
|
||||||
$aArgs = Functions::flattenArray($args);
|
|
||||||
|
|
||||||
// Calculate
|
|
||||||
$rate = array_shift($aArgs);
|
|
||||||
$countArgs = count($aArgs);
|
|
||||||
for ($i = 1; $i <= $countArgs; ++$i) {
|
|
||||||
// Is it a numeric value?
|
|
||||||
if (is_numeric($aArgs[$i - 1])) {
|
|
||||||
$returnValue += $aArgs[$i - 1] / (1 + $rate) ** $i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return
|
|
||||||
return $returnValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1123,6 +1046,11 @@ class Financial
|
||||||
*
|
*
|
||||||
* Calculates the number of periods required for an investment to reach a specified value.
|
* Calculates the number of periods required for an investment to reach a specified value.
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
|
* @see Financial\CashFlow\Single::periods()
|
||||||
|
* Use the periods() method in the Financial\CashFlow\Single class instead
|
||||||
|
*
|
||||||
* @param float $rate Interest rate per period
|
* @param float $rate Interest rate per period
|
||||||
* @param float $pv Present Value
|
* @param float $pv Present Value
|
||||||
* @param float $fv Future Value
|
* @param float $fv Future Value
|
||||||
|
|
@ -1131,18 +1059,7 @@ class Financial
|
||||||
*/
|
*/
|
||||||
public static function PDURATION($rate = 0, $pv = 0, $fv = 0)
|
public static function PDURATION($rate = 0, $pv = 0, $fv = 0)
|
||||||
{
|
{
|
||||||
$rate = Functions::flattenSingleValue($rate);
|
return Financial\CashFlow\Single::periods($rate, $pv, $fv);
|
||||||
$pv = Functions::flattenSingleValue($pv);
|
|
||||||
$fv = Functions::flattenSingleValue($fv);
|
|
||||||
|
|
||||||
// Validate parameters
|
|
||||||
if (!is_numeric($rate) || !is_numeric($pv) || !is_numeric($fv)) {
|
|
||||||
return Functions::VALUE();
|
|
||||||
} elseif ($rate <= 0.0 || $pv <= 0.0 || $fv <= 0.0) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (log($fv) - log($pv)) / log(1 + $rate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1470,6 +1387,11 @@ class Financial
|
||||||
*
|
*
|
||||||
* Calculates the interest rate required for an investment to grow to a specified future value .
|
* Calculates the interest rate required for an investment to grow to a specified future value .
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
|
* @see Financial\CashFlow\Single::interestRate()
|
||||||
|
* Use the interestRate() method in the Financial\CashFlow\Single class instead
|
||||||
|
*
|
||||||
* @param float $nper The number of periods over which the investment is made
|
* @param float $nper The number of periods over which the investment is made
|
||||||
* @param float $pv Present Value
|
* @param float $pv Present Value
|
||||||
* @param float $fv Future Value
|
* @param float $fv Future Value
|
||||||
|
|
@ -1478,18 +1400,7 @@ class Financial
|
||||||
*/
|
*/
|
||||||
public static function RRI($nper = 0, $pv = 0, $fv = 0)
|
public static function RRI($nper = 0, $pv = 0, $fv = 0)
|
||||||
{
|
{
|
||||||
$nper = Functions::flattenSingleValue($nper);
|
return Financial\CashFlow\Single::interestRate($nper, $pv, $fv);
|
||||||
$pv = Functions::flattenSingleValue($pv);
|
|
||||||
$fv = Functions::flattenSingleValue($fv);
|
|
||||||
|
|
||||||
// Validate parameters
|
|
||||||
if (!is_numeric($nper) || !is_numeric($pv) || !is_numeric($fv)) {
|
|
||||||
return Functions::VALUE();
|
|
||||||
} elseif ($nper <= 0.0 || $pv <= 0.0 || $fv < 0.0) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ($fv / $pv) ** (1 / $nper) - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1601,85 +1512,6 @@ class Financial
|
||||||
return TreasuryBill::yield($settlement, $maturity, $price);
|
return TreasuryBill::yield($settlement, $maturity, $price);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function bothNegAndPos($neg, $pos)
|
|
||||||
{
|
|
||||||
return $neg && $pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function xirrPart2(&$values)
|
|
||||||
{
|
|
||||||
$valCount = count($values);
|
|
||||||
$foundpos = false;
|
|
||||||
$foundneg = false;
|
|
||||||
for ($i = 0; $i < $valCount; ++$i) {
|
|
||||||
$fld = $values[$i];
|
|
||||||
if (!is_numeric($fld)) {
|
|
||||||
return Functions::VALUE();
|
|
||||||
} elseif ($fld > 0) {
|
|
||||||
$foundpos = true;
|
|
||||||
} elseif ($fld < 0) {
|
|
||||||
$foundneg = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!self::bothNegAndPos($foundneg, $foundpos)) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function xirrPart1(&$values, &$dates)
|
|
||||||
{
|
|
||||||
if ((!is_array($values)) && (!is_array($dates))) {
|
|
||||||
return Functions::NA();
|
|
||||||
}
|
|
||||||
$values = Functions::flattenArray($values);
|
|
||||||
$dates = Functions::flattenArray($dates);
|
|
||||||
if (count($values) != count($dates)) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
|
|
||||||
$datesCount = count($dates);
|
|
||||||
for ($i = 0; $i < $datesCount; ++$i) {
|
|
||||||
try {
|
|
||||||
$dates[$i] = DateTimeExcel\Helpers::getDateValue($dates[$i]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
return $e->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::xirrPart2($values);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function xirrPart3($values, $dates, $x1, $x2)
|
|
||||||
{
|
|
||||||
$f = self::xnpvOrdered($x1, $values, $dates, false);
|
|
||||||
if ($f < 0.0) {
|
|
||||||
$rtb = $x1;
|
|
||||||
$dx = $x2 - $x1;
|
|
||||||
} else {
|
|
||||||
$rtb = $x2;
|
|
||||||
$dx = $x1 - $x2;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rslt = Functions::VALUE();
|
|
||||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
|
||||||
$dx *= 0.5;
|
|
||||||
$x_mid = $rtb + $dx;
|
|
||||||
$f_mid = self::xnpvOrdered($x_mid, $values, $dates, false);
|
|
||||||
if ($f_mid <= 0.0) {
|
|
||||||
$rtb = $x_mid;
|
|
||||||
}
|
|
||||||
if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
|
|
||||||
$rslt = $x_mid;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $rslt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XIRR.
|
* XIRR.
|
||||||
*
|
*
|
||||||
|
|
@ -1688,6 +1520,11 @@ class Financial
|
||||||
* Excel Function:
|
* Excel Function:
|
||||||
* =XIRR(values,dates,guess)
|
* =XIRR(values,dates,guess)
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
|
* @see Financial\CashFlow\Variable\NonPeriodic::rate()
|
||||||
|
* Use the rate() method in the Financial\CashFlow\Variable\NonPeriodic class instead
|
||||||
|
*
|
||||||
* @param float[] $values A series of cash flow payments
|
* @param float[] $values A series of cash flow payments
|
||||||
* The series of values must contain at least one positive value & one negative value
|
* The series of values must contain at least one positive value & one negative value
|
||||||
* @param mixed[] $dates A series of payment dates
|
* @param mixed[] $dates A series of payment dates
|
||||||
|
|
@ -1699,37 +1536,7 @@ class Financial
|
||||||
*/
|
*/
|
||||||
public static function XIRR($values, $dates, $guess = 0.1)
|
public static function XIRR($values, $dates, $guess = 0.1)
|
||||||
{
|
{
|
||||||
$rslt = self::xirrPart1($values, $dates);
|
return Financial\CashFlow\Variable\NonPeriodic::rate($values, $dates, $guess);
|
||||||
if ($rslt) {
|
|
||||||
return $rslt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create an initial range, with a root somewhere between 0 and guess
|
|
||||||
$guess = Functions::flattenSingleValue($guess);
|
|
||||||
$x1 = 0.0;
|
|
||||||
$x2 = $guess ?: 0.1;
|
|
||||||
$f1 = self::xnpvOrdered($x1, $values, $dates, false);
|
|
||||||
$f2 = self::xnpvOrdered($x2, $values, $dates, false);
|
|
||||||
$found = false;
|
|
||||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
|
||||||
if (!is_numeric($f1) || !is_numeric($f2)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (($f1 * $f2) < 0.0) {
|
|
||||||
$found = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
} elseif (abs($f1) < abs($f2)) {
|
|
||||||
$f1 = self::xnpvOrdered($x1 += 1.6 * ($x1 - $x2), $values, $dates, false);
|
|
||||||
} else {
|
|
||||||
$f2 = self::xnpvOrdered($x2 += 1.6 * ($x2 - $x1), $values, $dates, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!$found) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::xirrPart3($values, $dates, $x1, $x2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1741,6 +1548,11 @@ class Financial
|
||||||
* Excel Function:
|
* Excel Function:
|
||||||
* =XNPV(rate,values,dates)
|
* =XNPV(rate,values,dates)
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
|
* @see Financial\CashFlow\Variable\NonPeriodic::presentValue()
|
||||||
|
* Use the presentValue() method in the Financial\CashFlow\Variable\NonPeriodic class instead
|
||||||
|
*
|
||||||
* @param float $rate the discount rate to apply to the cash flows
|
* @param float $rate the discount rate to apply to the cash flows
|
||||||
* @param float[] $values A series of cash flows that corresponds to a schedule of payments in dates.
|
* @param float[] $values A series of cash flows that corresponds to a schedule of payments in dates.
|
||||||
* The first payment is optional and corresponds to a cost or payment that occurs at the beginning of the investment.
|
* The first payment is optional and corresponds to a cost or payment that occurs at the beginning of the investment.
|
||||||
|
|
@ -1754,68 +1566,7 @@ class Financial
|
||||||
*/
|
*/
|
||||||
public static function XNPV($rate, $values, $dates)
|
public static function XNPV($rate, $values, $dates)
|
||||||
{
|
{
|
||||||
return self::xnpvOrdered($rate, $values, $dates, true);
|
return Financial\CashFlow\Variable\NonPeriodic::presentValue($rate, $values, $dates);
|
||||||
}
|
|
||||||
|
|
||||||
private static function validateXnpv($rate, $values, $dates)
|
|
||||||
{
|
|
||||||
if (!is_numeric($rate)) {
|
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
|
||||||
$valCount = count($values);
|
|
||||||
if ($valCount != count($dates)) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
$date0 = DateTimeExcel\Helpers::getDateValue($dates[0]);
|
|
||||||
if (is_string($date0)) {
|
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function xnpvOrdered($rate, $values, $dates, $ordered = true)
|
|
||||||
{
|
|
||||||
$rate = Functions::flattenSingleValue($rate);
|
|
||||||
$values = Functions::flattenArray($values);
|
|
||||||
$dates = Functions::flattenArray($dates);
|
|
||||||
$valCount = count($values);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$date0 = DateTimeExcel\Helpers::getDateValue($dates[0]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
return $e->getMessage();
|
|
||||||
}
|
|
||||||
$rslt = self::validateXnpv($rate, $values, $dates);
|
|
||||||
if ($rslt) {
|
|
||||||
return $rslt;
|
|
||||||
}
|
|
||||||
$xnpv = 0.0;
|
|
||||||
for ($i = 0; $i < $valCount; ++$i) {
|
|
||||||
if (!is_numeric($values[$i])) {
|
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$datei = DateTimeExcel\Helpers::getDateValue($dates[$i]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
return $e->getMessage();
|
|
||||||
}
|
|
||||||
if ($date0 > $datei) {
|
|
||||||
$dif = $ordered ? Functions::NAN() : -DateTimeExcel\DateDif::funcDateDif($datei, $date0, 'd');
|
|
||||||
} else {
|
|
||||||
$dif = DateTimeExcel\DateDif::funcDateDif($date0, $datei, 'd');
|
|
||||||
}
|
|
||||||
if (!is_numeric($dif)) {
|
|
||||||
return $dif;
|
|
||||||
}
|
|
||||||
$xnpv += $values[$i] / (1 + $rate) ** ($dif / 365);
|
|
||||||
}
|
|
||||||
|
|
||||||
return is_finite($xnpv) ? $xnpv : Functions::VALUE();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1823,7 +1574,10 @@ class Financial
|
||||||
*
|
*
|
||||||
* Returns the annual yield of a security that pays interest at maturity.
|
* Returns the annual yield of a security that pays interest at maturity.
|
||||||
*
|
*
|
||||||
* @see Use the yieldDiscounted() method in the Financial\Securities\Yields class instead
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
|
* @see Financial\Securities\Yields::yieldDiscounted()
|
||||||
|
* Use the yieldDiscounted() method in the Financial\Securities\Yields class instead
|
||||||
*
|
*
|
||||||
* @param mixed $settlement The security's settlement date.
|
* @param mixed $settlement The security's settlement date.
|
||||||
* The security's settlement date is the date after the issue date when the security
|
* The security's settlement date is the date after the issue date when the security
|
||||||
|
|
@ -1853,7 +1607,8 @@ class Financial
|
||||||
*
|
*
|
||||||
* @Deprecated 1.18.0
|
* @Deprecated 1.18.0
|
||||||
*
|
*
|
||||||
* @see Use the yieldAtMaturity() method in the Financial\Securities\Yields class instead
|
* @see Financial\Securities\Yields::yieldAtMaturity()
|
||||||
|
* Use the yieldAtMaturity() method in the Financial\Securities\Yields class instead
|
||||||
*
|
*
|
||||||
* @param mixed $settlement The security's settlement date.
|
* @param mixed $settlement The security's settlement date.
|
||||||
* The security's settlement date is the date after the issue date when the security
|
* The security's settlement date is the date after the issue date when the security
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,110 @@
|
||||||
|
|
||||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow;
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Financial\BaseValidations;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
|
|
||||||
class Single
|
class Single
|
||||||
{
|
{
|
||||||
|
use BaseValidations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FVSCHEDULE.
|
||||||
|
*
|
||||||
|
* Returns the future value of an initial principal after applying a series of compound interest rates.
|
||||||
|
* Use FVSCHEDULE to calculate the future value of an investment with a variable or adjustable rate.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* FVSCHEDULE(principal,schedule)
|
||||||
|
*
|
||||||
|
* @param mixed $principal the present value
|
||||||
|
* @param float[] $schedule an array of interest rates to apply
|
||||||
|
*
|
||||||
|
* @return float|string
|
||||||
|
*/
|
||||||
|
public static function futureValue($principal, $schedule)
|
||||||
|
{
|
||||||
|
$principal = Functions::flattenSingleValue($principal);
|
||||||
|
$schedule = Functions::flattenArray($schedule);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$principal = self::validateFloat($principal);
|
||||||
|
|
||||||
|
foreach ($schedule as $rate) {
|
||||||
|
$rate = self::validateFloat($rate);
|
||||||
|
$principal *= 1 + $rate;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $principal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PDURATION.
|
||||||
|
*
|
||||||
|
* Calculates the number of periods required for an investment to reach a specified value.
|
||||||
|
*
|
||||||
|
* @param float $rate Interest rate per period
|
||||||
|
* @param float $presentValue Present Value
|
||||||
|
* @param float $futureValue Future Value
|
||||||
|
*
|
||||||
|
* @return float|string Result, or a string containing an error
|
||||||
|
*/
|
||||||
|
public static function periods($rate = 0.0, $presentValue = 0.0, $futureValue = 0.0)
|
||||||
|
{
|
||||||
|
$rate = Functions::flattenSingleValue($rate);
|
||||||
|
$presentValue = Functions::flattenSingleValue($presentValue);
|
||||||
|
$futureValue = Functions::flattenSingleValue($futureValue);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$rate = self::validateFloat($rate);
|
||||||
|
$presentValue = self::validateFloat($presentValue);
|
||||||
|
$futureValue = self::validateFloat($futureValue);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate parameters
|
||||||
|
if ($rate <= 0.0 || $presentValue <= 0.0 || $futureValue <= 0.0) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (log($futureValue) - log($presentValue)) / log(1 + $rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RRI.
|
||||||
|
*
|
||||||
|
* Calculates the interest rate required for an investment to grow to a specified future value .
|
||||||
|
*
|
||||||
|
* @param float $periods The number of periods over which the investment is made
|
||||||
|
* @param float $presentValue Present Value
|
||||||
|
* @param float $futureValue Future Value
|
||||||
|
*
|
||||||
|
* @return float|string Result, or a string containing an error
|
||||||
|
*/
|
||||||
|
public static function interestRate($periods = 0.0, $presentValue = 0.0, $futureValue = 0.0)
|
||||||
|
{
|
||||||
|
$periods = Functions::flattenSingleValue($periods);
|
||||||
|
$presentValue = Functions::flattenSingleValue($presentValue);
|
||||||
|
$futureValue = Functions::flattenSingleValue($futureValue);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$periods = self::validateFloat($periods);
|
||||||
|
$presentValue = self::validateFloat($presentValue);
|
||||||
|
$futureValue = self::validateFloat($futureValue);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate parameters
|
||||||
|
if ($periods <= 0.0 || $presentValue <= 0.0 || $futureValue < 0.0) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($futureValue / $presentValue) ** (1 / $periods) - 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,233 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
|
|
||||||
|
class NonPeriodic
|
||||||
|
{
|
||||||
|
const FINANCIAL_MAX_ITERATIONS = 128;
|
||||||
|
|
||||||
|
const FINANCIAL_PRECISION = 1.0e-08;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XIRR.
|
||||||
|
*
|
||||||
|
* Returns the internal rate of return for a schedule of cash flows that is not necessarily periodic.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =XIRR(values,dates,guess)
|
||||||
|
*
|
||||||
|
* @param float[] $values A series of cash flow payments
|
||||||
|
* The series of values must contain at least one positive value & one negative value
|
||||||
|
* @param mixed[] $dates A series of payment dates
|
||||||
|
* The first payment date indicates the beginning of the schedule of payments
|
||||||
|
* All other dates must be later than this date, but they may occur in any order
|
||||||
|
* @param float $guess An optional guess at the expected answer
|
||||||
|
*
|
||||||
|
* @return float|string
|
||||||
|
*/
|
||||||
|
public static function rate($values, $dates, $guess = 0.1)
|
||||||
|
{
|
||||||
|
$rslt = self::xirrPart1($values, $dates);
|
||||||
|
if ($rslt) {
|
||||||
|
return $rslt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create an initial range, with a root somewhere between 0 and guess
|
||||||
|
$guess = Functions::flattenSingleValue($guess);
|
||||||
|
$x1 = 0.0;
|
||||||
|
$x2 = $guess ?: 0.1;
|
||||||
|
$f1 = self::xnpvOrdered($x1, $values, $dates, false);
|
||||||
|
$f2 = self::xnpvOrdered($x2, $values, $dates, false);
|
||||||
|
$found = false;
|
||||||
|
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||||
|
if (!is_numeric($f1) || !is_numeric($f2)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (($f1 * $f2) < 0.0) {
|
||||||
|
$found = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
} elseif (abs($f1) < abs($f2)) {
|
||||||
|
$f1 = self::xnpvOrdered($x1 += 1.6 * ($x1 - $x2), $values, $dates, false);
|
||||||
|
} else {
|
||||||
|
$f2 = self::xnpvOrdered($x2 += 1.6 * ($x2 - $x1), $values, $dates, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$found) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::xirrPart3($values, $dates, $x1, $x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XNPV.
|
||||||
|
*
|
||||||
|
* Returns the net present value for a schedule of cash flows that is not necessarily periodic.
|
||||||
|
* To calculate the net present value for a series of cash flows that is periodic, use the NPV function.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* =XNPV(rate,values,dates)
|
||||||
|
*
|
||||||
|
* @param float $rate the discount rate to apply to the cash flows
|
||||||
|
* @param float[] $values A series of cash flows that corresponds to a schedule of payments in dates.
|
||||||
|
* The first payment is optional and corresponds to a cost or payment that occurs
|
||||||
|
* at the beginning of the investment.
|
||||||
|
* If the first value is a cost or payment, it must be a negative value.
|
||||||
|
* All succeeding payments are discounted based on a 365-day year.
|
||||||
|
* The series of values must contain at least one positive value and one negative value.
|
||||||
|
* @param mixed[] $dates A schedule of payment dates that corresponds to the cash flow payments.
|
||||||
|
* The first payment date indicates the beginning of the schedule of payments.
|
||||||
|
* All other dates must be later than this date, but they may occur in any order.
|
||||||
|
*
|
||||||
|
* @return float|string
|
||||||
|
*/
|
||||||
|
public static function presentValue($rate, $values, $dates)
|
||||||
|
{
|
||||||
|
return self::xnpvOrdered($rate, $values, $dates, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function bothNegAndPos($neg, $pos)
|
||||||
|
{
|
||||||
|
return $neg && $pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function xirrPart1(&$values, &$dates)
|
||||||
|
{
|
||||||
|
if ((!is_array($values)) && (!is_array($dates))) {
|
||||||
|
return Functions::NA();
|
||||||
|
}
|
||||||
|
$values = Functions::flattenArray($values);
|
||||||
|
$dates = Functions::flattenArray($dates);
|
||||||
|
if (count($values) != count($dates)) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
|
||||||
|
$datesCount = count($dates);
|
||||||
|
for ($i = 0; $i < $datesCount; ++$i) {
|
||||||
|
try {
|
||||||
|
$dates[$i] = DateTimeExcel\Helpers::getDateValue($dates[$i]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::xirrPart2($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function xirrPart2(&$values)
|
||||||
|
{
|
||||||
|
$valCount = count($values);
|
||||||
|
$foundpos = false;
|
||||||
|
$foundneg = false;
|
||||||
|
for ($i = 0; $i < $valCount; ++$i) {
|
||||||
|
$fld = $values[$i];
|
||||||
|
if (!is_numeric($fld)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
} elseif ($fld > 0) {
|
||||||
|
$foundpos = true;
|
||||||
|
} elseif ($fld < 0) {
|
||||||
|
$foundneg = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!self::bothNegAndPos($foundneg, $foundpos)) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function xirrPart3($values, $dates, $x1, $x2)
|
||||||
|
{
|
||||||
|
$f = self::xnpvOrdered($x1, $values, $dates, false);
|
||||||
|
if ($f < 0.0) {
|
||||||
|
$rtb = $x1;
|
||||||
|
$dx = $x2 - $x1;
|
||||||
|
} else {
|
||||||
|
$rtb = $x2;
|
||||||
|
$dx = $x1 - $x2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rslt = Functions::VALUE();
|
||||||
|
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||||
|
$dx *= 0.5;
|
||||||
|
$x_mid = $rtb + $dx;
|
||||||
|
$f_mid = self::xnpvOrdered($x_mid, $values, $dates, false);
|
||||||
|
if ($f_mid <= 0.0) {
|
||||||
|
$rtb = $x_mid;
|
||||||
|
}
|
||||||
|
if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
|
||||||
|
$rslt = $x_mid;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rslt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function xnpvOrdered($rate, $values, $dates, $ordered = true)
|
||||||
|
{
|
||||||
|
$rate = Functions::flattenSingleValue($rate);
|
||||||
|
$values = Functions::flattenArray($values);
|
||||||
|
$dates = Functions::flattenArray($dates);
|
||||||
|
$valCount = count($values);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$date0 = DateTimeExcel\Helpers::getDateValue($dates[0]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
$rslt = self::validateXnpv($rate, $values, $dates);
|
||||||
|
if ($rslt) {
|
||||||
|
return $rslt;
|
||||||
|
}
|
||||||
|
$xnpv = 0.0;
|
||||||
|
for ($i = 0; $i < $valCount; ++$i) {
|
||||||
|
if (!is_numeric($values[$i])) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$datei = DateTimeExcel\Helpers::getDateValue($dates[$i]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
if ($date0 > $datei) {
|
||||||
|
$dif = $ordered ? Functions::NAN() : -DateTimeExcel\DateDif::funcDateDif($datei, $date0, 'd');
|
||||||
|
} else {
|
||||||
|
$dif = DateTimeExcel\DateDif::funcDateDif($date0, $datei, 'd');
|
||||||
|
}
|
||||||
|
if (!is_numeric($dif)) {
|
||||||
|
return $dif;
|
||||||
|
}
|
||||||
|
$xnpv += $values[$i] / (1 + $rate) ** ($dif / 365);
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_finite($xnpv) ? $xnpv : Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function validateXnpv($rate, $values, $dates)
|
||||||
|
{
|
||||||
|
if (!is_numeric($rate)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
$valCount = count($values);
|
||||||
|
if ($valCount != count($dates)) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
$date0 = DateTimeExcel\Helpers::getDateValue($dates[0]);
|
||||||
|
if (is_string($date0)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
|
|
||||||
|
class Periodic
|
||||||
|
{
|
||||||
|
const FINANCIAL_MAX_ITERATIONS = 128;
|
||||||
|
|
||||||
|
const FINANCIAL_PRECISION = 1.0e-08;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IRR.
|
||||||
|
*
|
||||||
|
* Returns the internal rate of return for a series of cash flows represented by the numbers in values.
|
||||||
|
* These cash flows do not have to be even, as they would be for an annuity. However, the cash flows must occur
|
||||||
|
* at regular intervals, such as monthly or annually. The internal rate of return is the interest rate received
|
||||||
|
* for an investment consisting of payments (negative values) and income (positive values) that occur at regular
|
||||||
|
* periods.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* IRR(values[,guess])
|
||||||
|
*
|
||||||
|
* @param mixed $values An array or a reference to cells that contain numbers for which you want
|
||||||
|
* to calculate the internal rate of return.
|
||||||
|
* Values must contain at least one positive value and one negative value to
|
||||||
|
* calculate the internal rate of return.
|
||||||
|
* @param mixed $guess A number that you guess is close to the result of IRR
|
||||||
|
*
|
||||||
|
* @return float|string
|
||||||
|
*/
|
||||||
|
public static function rate($values, $guess = 0.1)
|
||||||
|
{
|
||||||
|
if (!is_array($values)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
$values = Functions::flattenArray($values);
|
||||||
|
$guess = Functions::flattenSingleValue($guess);
|
||||||
|
|
||||||
|
// create an initial range, with a root somewhere between 0 and guess
|
||||||
|
$x1 = 0.0;
|
||||||
|
$x2 = $guess;
|
||||||
|
$f1 = self::presentValue($x1, $values);
|
||||||
|
$f2 = self::presentValue($x2, $values);
|
||||||
|
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||||
|
if (($f1 * $f2) < 0.0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (abs($f1) < abs($f2)) {
|
||||||
|
$f1 = self::presentValue($x1 += 1.6 * ($x1 - $x2), $values);
|
||||||
|
} else {
|
||||||
|
$f2 = self::presentValue($x2 += 1.6 * ($x2 - $x1), $values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (($f1 * $f2) > 0.0) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
$f = self::presentValue($x1, $values);
|
||||||
|
if ($f < 0.0) {
|
||||||
|
$rtb = $x1;
|
||||||
|
$dx = $x2 - $x1;
|
||||||
|
} else {
|
||||||
|
$rtb = $x2;
|
||||||
|
$dx = $x1 - $x2;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||||
|
$dx *= 0.5;
|
||||||
|
$x_mid = $rtb + $dx;
|
||||||
|
$f_mid = self::presentValue($x_mid, $values);
|
||||||
|
if ($f_mid <= 0.0) {
|
||||||
|
$rtb = $x_mid;
|
||||||
|
}
|
||||||
|
if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
|
||||||
|
return $x_mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MIRR.
|
||||||
|
*
|
||||||
|
* Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both
|
||||||
|
* the cost of the investment and the interest received on reinvestment of cash.
|
||||||
|
*
|
||||||
|
* Excel Function:
|
||||||
|
* MIRR(values,finance_rate, reinvestment_rate)
|
||||||
|
*
|
||||||
|
* @param mixed $values An array or a reference to cells that contain a series of payments and
|
||||||
|
* income occurring at regular intervals.
|
||||||
|
* Payments are negative value, income is positive values.
|
||||||
|
* @param mixed $financeRate The interest rate you pay on the money used in the cash flows
|
||||||
|
* @param mixed $reinvestmentRate The interest rate you receive on the cash flows as you reinvest them
|
||||||
|
*
|
||||||
|
* @return float|string Result, or a string containing an error
|
||||||
|
*/
|
||||||
|
public static function modifiedRate($values, $financeRate, $reinvestmentRate)
|
||||||
|
{
|
||||||
|
if (!is_array($values)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
$values = Functions::flattenArray($values);
|
||||||
|
$financeRate = Functions::flattenSingleValue($financeRate);
|
||||||
|
$reinvestmentRate = Functions::flattenSingleValue($reinvestmentRate);
|
||||||
|
$n = count($values);
|
||||||
|
|
||||||
|
$rr = 1.0 + $reinvestmentRate;
|
||||||
|
$fr = 1.0 + $financeRate;
|
||||||
|
|
||||||
|
$npvPos = $npvNeg = 0.0;
|
||||||
|
foreach ($values as $i => $v) {
|
||||||
|
if ($v >= 0) {
|
||||||
|
$npvPos += $v / $rr ** $i;
|
||||||
|
} else {
|
||||||
|
$npvNeg += $v / $fr ** $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($npvNeg === 0.0) || ($npvPos === 0.0) || ($reinvestmentRate <= -1.0)) {
|
||||||
|
return Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
$mirr = ((-$npvPos * $rr ** $n)
|
||||||
|
/ ($npvNeg * ($rr))) ** (1.0 / ($n - 1)) - 1.0;
|
||||||
|
|
||||||
|
return is_finite($mirr) ? $mirr : Functions::VALUE();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NPV.
|
||||||
|
*
|
||||||
|
* Returns the Net Present Value of a cash flow series given a discount rate.
|
||||||
|
*
|
||||||
|
* @param mixed $rate
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
public static function presentValue($rate, ...$args)
|
||||||
|
{
|
||||||
|
$returnValue = 0;
|
||||||
|
|
||||||
|
$rate = Functions::flattenSingleValue($rate);
|
||||||
|
$aArgs = Functions::flattenArray($args);
|
||||||
|
|
||||||
|
// Calculate
|
||||||
|
$countArgs = count($aArgs);
|
||||||
|
for ($i = 1; $i <= $countArgs; ++$i) {
|
||||||
|
// Is it a numeric value?
|
||||||
|
if (is_numeric($aArgs[$i - 1])) {
|
||||||
|
$returnValue += $aArgs[$i - 1] / (1 + $rate) ** $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,4 +36,22 @@ return [
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
'NaN',
|
||||||
|
[
|
||||||
|
0.089999999999999997,
|
||||||
|
0.11,
|
||||||
|
0.10000000000000001,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
100,
|
||||||
|
[
|
||||||
|
0.089999999999999997,
|
||||||
|
'NaN',
|
||||||
|
0.10000000000000001,
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ return [
|
||||||
15000,
|
15000,
|
||||||
18000,
|
18000,
|
||||||
],
|
],
|
||||||
0.10000000000000001,
|
0.10,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
-0.13618951095869,
|
-0.13618951095869,
|
||||||
|
|
@ -41,7 +41,7 @@ return [
|
||||||
[
|
[
|
||||||
20,
|
20,
|
||||||
24,
|
24,
|
||||||
28.800000000000001,
|
28.8,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
@ -52,10 +52,35 @@ return [
|
||||||
[
|
[
|
||||||
20,
|
20,
|
||||||
24,
|
24,
|
||||||
28.800000000000001,
|
28.8,
|
||||||
34.560000000000002,
|
34.56,
|
||||||
41.469999999999999,
|
41.47,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
999,
|
||||||
|
1.23,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
[
|
||||||
|
70000,
|
||||||
|
12000,
|
||||||
|
15000,
|
||||||
|
18000,
|
||||||
|
21000,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
[
|
||||||
|
-70000,
|
||||||
|
-12000,
|
||||||
|
-15000,
|
||||||
|
-18000,
|
||||||
|
-21000,
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ return [
|
||||||
46000,
|
46000,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
0.10000000000000001,
|
0.10,
|
||||||
0.12,
|
0.12,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|
@ -28,7 +28,7 @@ return [
|
||||||
21000,
|
21000,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
0.10000000000000001,
|
0.10,
|
||||||
0.12,
|
0.12,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|
@ -43,8 +43,8 @@ return [
|
||||||
46000,
|
46000,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
0.10000000000000001,
|
0.10,
|
||||||
0.14000000000000001,
|
0.14,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
0.74021752686287001,
|
0.74021752686287001,
|
||||||
|
|
@ -74,4 +74,28 @@ return [
|
||||||
5.5,
|
5.5,
|
||||||
5,
|
5,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
999,
|
||||||
|
1.23,
|
||||||
|
2.34,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
[0.12, 0.13, 0.125],
|
||||||
|
1.23,
|
||||||
|
2.34,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
[-0.12, -0.13, -0.125],
|
||||||
|
1.23,
|
||||||
|
2.34,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
[-0.12, 0.13, 0.125],
|
||||||
|
1.23,
|
||||||
|
-2.34,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
1188.4434123352,
|
1188.4434123352,
|
||||||
0.10000000000000001,
|
0.10,
|
||||||
-10000,
|
-10000,
|
||||||
3000,
|
3000,
|
||||||
4200,
|
4200,
|
||||||
|
|
@ -13,7 +13,7 @@ return [
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
41922.061554931999,
|
41922.061554931999,
|
||||||
0.080000000000000002,
|
0.08,
|
||||||
8000,
|
8000,
|
||||||
9200,
|
9200,
|
||||||
10000,
|
10000,
|
||||||
|
|
@ -22,7 +22,7 @@ return [
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
36250.534912984003,
|
36250.534912984003,
|
||||||
0.080000000000000002,
|
0.08,
|
||||||
8000,
|
8000,
|
||||||
9200,
|
9200,
|
||||||
10000,
|
10000,
|
||||||
|
|
@ -32,7 +32,7 @@ return [
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
12678.677633095,
|
12678.677633095,
|
||||||
0.050000000000000003,
|
0.05,
|
||||||
2000,
|
2000,
|
||||||
2400,
|
2400,
|
||||||
2900,
|
2900,
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
[
|
|
||||||
'#VALUE!',
|
|
||||||
['ABC', 'DEF', 'GHI'],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'#NUM!',
|
|
||||||
[0.0, 0.0, 0.0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'#NUM!',
|
|
||||||
[0.05, 10.0, -2.0],
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
10.33803507,
|
10.33803507,
|
||||||
[0.04, 10000, 15000],
|
[0.04, 10000, 15000],
|
||||||
|
|
@ -33,4 +21,28 @@ return [
|
||||||
14.206699082890474,
|
14.206699082890474,
|
||||||
[0.05, 50, 100],
|
[0.05, 50, 100],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
['NaN', 50, 100],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
[0.05, 'NaN', 100],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
[0.05, 50, 'NaN'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
[0.00, 50, 100],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
[0.05, 0, 100],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
[0.05, 50, 0],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
[
|
|
||||||
'#VALUE!',
|
|
||||||
['ABC', 'DEF', 'GHI'],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'#NUM!',
|
|
||||||
[0.0, 10.0, 20.0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'#NUM!',
|
|
||||||
[0.05, 10.0, -2.0],
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
0.04137974399241062,
|
0.04137974399241062,
|
||||||
[10, 10000, 15000],
|
[10, 10000, 15000],
|
||||||
|
|
@ -25,4 +13,28 @@ return [
|
||||||
0.6747967875721199,
|
0.6747967875721199,
|
||||||
[10, 70, 12154],
|
[10, 70, 12154],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
['NaN', 70, 12154],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
[10, 'NaN', 12154],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
[10, 70, 'NaN'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
[0.0, 70.0, 200.0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
[10, 0.0, 200.0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
[10, 70, -2.0],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue