init
This commit is contained in:
commit
72a26edcff
22092 changed files with 2101903 additions and 0 deletions
212
lib/PhpSpreadsheet/Calculation/Financial/Amortization.php
Normal file
212
lib/PhpSpreadsheet/Calculation/Financial/Amortization.php
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Amortization
|
||||
{
|
||||
/**
|
||||
* AMORDEGRC.
|
||||
*
|
||||
* Returns the depreciation for each accounting period.
|
||||
* This function is provided for the French accounting system. If an asset is purchased in
|
||||
* the middle of the accounting period, the prorated depreciation is taken into account.
|
||||
* The function is similar to AMORLINC, except that a depreciation coefficient is applied in
|
||||
* the calculation depending on the life of the assets.
|
||||
* This function will return the depreciation until the last period of the life of the assets
|
||||
* or until the cumulated value of depreciation is greater than the cost of the assets minus
|
||||
* the salvage value.
|
||||
*
|
||||
* Excel Function:
|
||||
* AMORDEGRC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
|
||||
*
|
||||
* @param mixed $cost The float cost of the asset
|
||||
* @param mixed $purchased Date of the purchase of the asset
|
||||
* @param mixed $firstPeriod Date of the end of the first period
|
||||
* @param mixed $salvage The salvage value at the end of the life of the asset
|
||||
* @param mixed $period the period (float)
|
||||
* @param mixed $rate rate of depreciation (float)
|
||||
* @param mixed $basis The type of day count to use (int).
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float|string (string containing the error type if there is an error)
|
||||
*/
|
||||
public static function AMORDEGRC(
|
||||
mixed $cost,
|
||||
mixed $purchased,
|
||||
mixed $firstPeriod,
|
||||
mixed $salvage,
|
||||
mixed $period,
|
||||
mixed $rate,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
): string|float {
|
||||
$cost = Functions::flattenSingleValue($cost);
|
||||
$purchased = Functions::flattenSingleValue($purchased);
|
||||
$firstPeriod = Functions::flattenSingleValue($firstPeriod);
|
||||
$salvage = Functions::flattenSingleValue($salvage);
|
||||
$period = Functions::flattenSingleValue($period);
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$cost = FinancialValidations::validateFloat($cost);
|
||||
$purchased = FinancialValidations::validateDate($purchased);
|
||||
$firstPeriod = FinancialValidations::validateDate($firstPeriod);
|
||||
$salvage = FinancialValidations::validateFloat($salvage);
|
||||
$period = FinancialValidations::validateInt($period);
|
||||
$rate = FinancialValidations::validateFloat($rate);
|
||||
$basis = FinancialValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$yearFracx = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis);
|
||||
if (is_string($yearFracx)) {
|
||||
return $yearFracx;
|
||||
}
|
||||
/** @var float $yearFrac */
|
||||
$yearFrac = $yearFracx;
|
||||
|
||||
$amortiseCoeff = self::getAmortizationCoefficient($rate);
|
||||
|
||||
$rate *= $amortiseCoeff;
|
||||
$fNRate = round($yearFrac * $rate * $cost, 0);
|
||||
$cost -= $fNRate;
|
||||
$fRest = $cost - $salvage;
|
||||
|
||||
for ($n = 0; $n < $period; ++$n) {
|
||||
$fNRate = round($rate * $cost, 0);
|
||||
$fRest -= $fNRate;
|
||||
|
||||
if ($fRest < 0.0) {
|
||||
return match ($period - $n) {
|
||||
1 => round($cost * 0.5, 0),
|
||||
default => 0.0,
|
||||
};
|
||||
}
|
||||
$cost -= $fNRate;
|
||||
}
|
||||
|
||||
return $fNRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* AMORLINC.
|
||||
*
|
||||
* Returns the depreciation for each accounting period.
|
||||
* This function is provided for the French accounting system. If an asset is purchased in
|
||||
* the middle of the accounting period, the prorated depreciation is taken into account.
|
||||
*
|
||||
* Excel Function:
|
||||
* AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
|
||||
*
|
||||
* @param mixed $cost The cost of the asset as a float
|
||||
* @param mixed $purchased Date of the purchase of the asset
|
||||
* @param mixed $firstPeriod Date of the end of the first period
|
||||
* @param mixed $salvage The salvage value at the end of the life of the asset
|
||||
* @param mixed $period The period as a float
|
||||
* @param mixed $rate Rate of depreciation as float
|
||||
* @param mixed $basis Integer indicating the type of day count to use.
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float|string (string containing the error type if there is an error)
|
||||
*/
|
||||
public static function AMORLINC(
|
||||
mixed $cost,
|
||||
mixed $purchased,
|
||||
mixed $firstPeriod,
|
||||
mixed $salvage,
|
||||
mixed $period,
|
||||
mixed $rate,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
): string|float {
|
||||
$cost = Functions::flattenSingleValue($cost);
|
||||
$purchased = Functions::flattenSingleValue($purchased);
|
||||
$firstPeriod = Functions::flattenSingleValue($firstPeriod);
|
||||
$salvage = Functions::flattenSingleValue($salvage);
|
||||
$period = Functions::flattenSingleValue($period);
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$cost = FinancialValidations::validateFloat($cost);
|
||||
$purchased = FinancialValidations::validateDate($purchased);
|
||||
$firstPeriod = FinancialValidations::validateDate($firstPeriod);
|
||||
$salvage = FinancialValidations::validateFloat($salvage);
|
||||
$period = FinancialValidations::validateFloat($period);
|
||||
$rate = FinancialValidations::validateFloat($rate);
|
||||
$basis = FinancialValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$fOneRate = $cost * $rate;
|
||||
$fCostDelta = $cost - $salvage;
|
||||
// Note, quirky variation for leap years on the YEARFRAC for this function
|
||||
$purchasedYear = DateTimeExcel\DateParts::year($purchased);
|
||||
$yearFracx = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis);
|
||||
if (is_string($yearFracx)) {
|
||||
return $yearFracx;
|
||||
}
|
||||
/** @var float $yearFrac */
|
||||
$yearFrac = $yearFracx;
|
||||
|
||||
if (
|
||||
$basis == FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL
|
||||
&& $yearFrac < 1
|
||||
&& DateTimeExcel\Helpers::isLeapYear(Functions::scalar($purchasedYear))
|
||||
) {
|
||||
$yearFrac *= 365 / 366;
|
||||
}
|
||||
|
||||
$f0Rate = $yearFrac * $rate * $cost;
|
||||
$nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate);
|
||||
|
||||
if ($period == 0) {
|
||||
return $f0Rate;
|
||||
} elseif ($period <= $nNumOfFullPeriods) {
|
||||
return $fOneRate;
|
||||
} elseif ($period == ($nNumOfFullPeriods + 1)) {
|
||||
return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private static function getAmortizationCoefficient(float $rate): float
|
||||
{
|
||||
// The depreciation coefficients are:
|
||||
// Life of assets (1/rate) Depreciation coefficient
|
||||
// Less than 3 years 1
|
||||
// Between 3 and 4 years 1.5
|
||||
// Between 5 and 6 years 2
|
||||
// More than 6 years 2.5
|
||||
$fUsePer = 1.0 / $rate;
|
||||
|
||||
if ($fUsePer < 3.0) {
|
||||
return 1.0;
|
||||
} elseif ($fUsePer < 4.0) {
|
||||
return 1.5;
|
||||
} elseif ($fUsePer <= 6.0) {
|
||||
return 2.0;
|
||||
}
|
||||
|
||||
return 2.5;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\FinancialValidations;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class CashFlowValidations extends FinancialValidations
|
||||
{
|
||||
public static function validateRate(mixed $rate): float
|
||||
{
|
||||
$rate = self::validateFloat($rate);
|
||||
|
||||
return $rate;
|
||||
}
|
||||
|
||||
public static function validatePeriodType(mixed $type): int
|
||||
{
|
||||
$rate = self::validateInt($type);
|
||||
if (
|
||||
$type !== FinancialConstants::PAYMENT_END_OF_PERIOD
|
||||
&& $type !== FinancialConstants::PAYMENT_BEGINNING_OF_PERIOD
|
||||
) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $rate;
|
||||
}
|
||||
|
||||
public static function validatePresentValue(mixed $presentValue): float
|
||||
{
|
||||
return self::validateFloat($presentValue);
|
||||
}
|
||||
|
||||
public static function validateFutureValue(mixed $futureValue): float
|
||||
{
|
||||
return self::validateFloat($futureValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Periodic
|
||||
{
|
||||
/**
|
||||
* FV.
|
||||
*
|
||||
* Returns the Future Value of a cash flow with constant payments and interest rate (annuities).
|
||||
*
|
||||
* Excel Function:
|
||||
* FV(rate,nper,pmt[,pv[,type]])
|
||||
*
|
||||
* @param mixed $rate The interest rate per period
|
||||
* @param mixed $numberOfPeriods Total number of payment periods in an annuity as an integer
|
||||
* @param mixed $payment The payment made each period: it cannot change over the
|
||||
* life of the annuity. Typically, pmt contains principal
|
||||
* and interest but no other fees or taxes.
|
||||
* @param mixed $presentValue present Value, or the lump-sum amount that a series of
|
||||
* future payments is worth right now
|
||||
* @param mixed $type A number 0 or 1 and indicates when payments are due:
|
||||
* 0 or omitted At the end of the period.
|
||||
* 1 At the beginning of the period.
|
||||
*/
|
||||
public static function futureValue(
|
||||
mixed $rate,
|
||||
mixed $numberOfPeriods,
|
||||
mixed $payment = 0.0,
|
||||
mixed $presentValue = 0.0,
|
||||
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
|
||||
): string|float {
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
|
||||
$payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment);
|
||||
$presentValue = ($presentValue === null) ? 0.0 : Functions::flattenSingleValue($presentValue);
|
||||
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
|
||||
|
||||
try {
|
||||
$rate = CashFlowValidations::validateRate($rate);
|
||||
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
|
||||
$payment = CashFlowValidations::validateFloat($payment);
|
||||
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
|
||||
$type = CashFlowValidations::validatePeriodType($type);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return self::calculateFutureValue($rate, $numberOfPeriods, $payment, $presentValue, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* PV.
|
||||
*
|
||||
* Returns the Present Value of a cash flow with constant payments and interest rate (annuities).
|
||||
*
|
||||
* @param mixed $rate Interest rate per period
|
||||
* @param mixed $numberOfPeriods Number of periods as an integer
|
||||
* @param mixed $payment Periodic payment (annuity)
|
||||
* @param mixed $futureValue Future Value
|
||||
* @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function presentValue(
|
||||
mixed $rate,
|
||||
mixed $numberOfPeriods,
|
||||
mixed $payment = 0.0,
|
||||
mixed $futureValue = 0.0,
|
||||
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
|
||||
): string|float {
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
|
||||
$payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment);
|
||||
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
|
||||
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
|
||||
|
||||
try {
|
||||
$rate = CashFlowValidations::validateRate($rate);
|
||||
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
|
||||
$payment = CashFlowValidations::validateFloat($payment);
|
||||
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
|
||||
$type = CashFlowValidations::validatePeriodType($type);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if ($numberOfPeriods < 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return self::calculatePresentValue($rate, $numberOfPeriods, $payment, $futureValue, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* NPER.
|
||||
*
|
||||
* Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate.
|
||||
*
|
||||
* @param mixed $rate Interest rate per period
|
||||
* @param mixed $payment Periodic payment (annuity)
|
||||
* @param mixed $presentValue Present Value
|
||||
* @param mixed $futureValue Future Value
|
||||
* @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function periods(
|
||||
mixed $rate,
|
||||
mixed $payment,
|
||||
mixed $presentValue,
|
||||
mixed $futureValue = 0.0,
|
||||
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
|
||||
) {
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$payment = Functions::flattenSingleValue($payment);
|
||||
$presentValue = Functions::flattenSingleValue($presentValue);
|
||||
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
|
||||
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
|
||||
|
||||
try {
|
||||
$rate = CashFlowValidations::validateRate($rate);
|
||||
$payment = CashFlowValidations::validateFloat($payment);
|
||||
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
|
||||
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
|
||||
$type = CashFlowValidations::validatePeriodType($type);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if ($payment == 0.0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return self::calculatePeriods($rate, $payment, $presentValue, $futureValue, $type);
|
||||
}
|
||||
|
||||
private static function calculateFutureValue(
|
||||
float $rate,
|
||||
int $numberOfPeriods,
|
||||
float $payment,
|
||||
float $presentValue,
|
||||
int $type
|
||||
): float {
|
||||
if ($rate !== null && $rate != 0) {
|
||||
return -$presentValue
|
||||
* (1 + $rate) ** $numberOfPeriods - $payment * (1 + $rate * $type) * ((1 + $rate) ** $numberOfPeriods - 1)
|
||||
/ $rate;
|
||||
}
|
||||
|
||||
return -$presentValue - $payment * $numberOfPeriods;
|
||||
}
|
||||
|
||||
private static function calculatePresentValue(
|
||||
float $rate,
|
||||
int $numberOfPeriods,
|
||||
float $payment,
|
||||
float $futureValue,
|
||||
int $type
|
||||
): float {
|
||||
if ($rate != 0.0) {
|
||||
return (-$payment * (1 + $rate * $type)
|
||||
* (((1 + $rate) ** $numberOfPeriods - 1) / $rate) - $futureValue) / (1 + $rate) ** $numberOfPeriods;
|
||||
}
|
||||
|
||||
return -$futureValue - $payment * $numberOfPeriods;
|
||||
}
|
||||
|
||||
private static function calculatePeriods(
|
||||
float $rate,
|
||||
float $payment,
|
||||
float $presentValue,
|
||||
float $futureValue,
|
||||
int $type
|
||||
): string|float {
|
||||
if ($rate != 0.0) {
|
||||
if ($presentValue == 0.0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return log(($payment * (1 + $rate * $type) / $rate - $futureValue)
|
||||
/ ($presentValue + $payment * (1 + $rate * $type) / $rate)) / log(1 + $rate);
|
||||
}
|
||||
|
||||
return (-$presentValue - $futureValue) / $payment;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Cumulative
|
||||
{
|
||||
/**
|
||||
* CUMIPMT.
|
||||
*
|
||||
* Returns the cumulative interest paid on a loan between the start and end periods.
|
||||
*
|
||||
* Excel Function:
|
||||
* CUMIPMT(rate,nper,pv,start,end[,type])
|
||||
*
|
||||
* @param mixed $rate The Interest rate
|
||||
* @param mixed $periods The total number of payment periods
|
||||
* @param mixed $presentValue Present Value
|
||||
* @param mixed $start The first period in the calculation.
|
||||
* Payment periods are numbered beginning with 1.
|
||||
* @param mixed $end the last period in the calculation
|
||||
* @param mixed $type A number 0 or 1 and indicates when payments are due:
|
||||
* 0 or omitted At the end of the period.
|
||||
* 1 At the beginning of the period.
|
||||
*/
|
||||
public static function interest(
|
||||
mixed $rate,
|
||||
mixed $periods,
|
||||
mixed $presentValue,
|
||||
mixed $start,
|
||||
mixed $end,
|
||||
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
|
||||
): string|float|int {
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$periods = Functions::flattenSingleValue($periods);
|
||||
$presentValue = Functions::flattenSingleValue($presentValue);
|
||||
$start = Functions::flattenSingleValue($start);
|
||||
$end = Functions::flattenSingleValue($end);
|
||||
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
|
||||
|
||||
try {
|
||||
$rate = CashFlowValidations::validateRate($rate);
|
||||
$periods = CashFlowValidations::validateInt($periods);
|
||||
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
|
||||
$start = CashFlowValidations::validateInt($start);
|
||||
$end = CashFlowValidations::validateInt($end);
|
||||
$type = CashFlowValidations::validatePeriodType($type);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if ($start < 1 || $start > $end) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
// Calculate
|
||||
$interest = 0;
|
||||
for ($per = $start; $per <= $end; ++$per) {
|
||||
$ipmt = Interest::payment($rate, $per, $periods, $presentValue, 0, $type);
|
||||
if (is_string($ipmt)) {
|
||||
return $ipmt;
|
||||
}
|
||||
|
||||
$interest += $ipmt;
|
||||
}
|
||||
|
||||
return $interest;
|
||||
}
|
||||
|
||||
/**
|
||||
* CUMPRINC.
|
||||
*
|
||||
* Returns the cumulative principal paid on a loan between the start and end periods.
|
||||
*
|
||||
* Excel Function:
|
||||
* CUMPRINC(rate,nper,pv,start,end[,type])
|
||||
*
|
||||
* @param mixed $rate The Interest rate
|
||||
* @param mixed $periods The total number of payment periods as an integer
|
||||
* @param mixed $presentValue Present Value
|
||||
* @param mixed $start The first period in the calculation.
|
||||
* Payment periods are numbered beginning with 1.
|
||||
* @param mixed $end the last period in the calculation
|
||||
* @param mixed $type A number 0 or 1 and indicates when payments are due:
|
||||
* 0 or omitted At the end of the period.
|
||||
* 1 At the beginning of the period.
|
||||
*/
|
||||
public static function principal(
|
||||
mixed $rate,
|
||||
mixed $periods,
|
||||
mixed $presentValue,
|
||||
mixed $start,
|
||||
mixed $end,
|
||||
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
|
||||
): string|float|int {
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$periods = Functions::flattenSingleValue($periods);
|
||||
$presentValue = Functions::flattenSingleValue($presentValue);
|
||||
$start = Functions::flattenSingleValue($start);
|
||||
$end = Functions::flattenSingleValue($end);
|
||||
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
|
||||
|
||||
try {
|
||||
$rate = CashFlowValidations::validateRate($rate);
|
||||
$periods = CashFlowValidations::validateInt($periods);
|
||||
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
|
||||
$start = CashFlowValidations::validateInt($start);
|
||||
$end = CashFlowValidations::validateInt($end);
|
||||
$type = CashFlowValidations::validatePeriodType($type);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if ($start < 1 || $start > $end) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
// Calculate
|
||||
$principal = 0;
|
||||
for ($per = $start; $per <= $end; ++$per) {
|
||||
$ppmt = Payments::interestPayment($rate, $per, $periods, $presentValue, 0, $type);
|
||||
if (is_string($ppmt)) {
|
||||
return $ppmt;
|
||||
}
|
||||
|
||||
$principal += $ppmt;
|
||||
}
|
||||
|
||||
return $principal;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Interest
|
||||
{
|
||||
private const FINANCIAL_MAX_ITERATIONS = 128;
|
||||
|
||||
private const FINANCIAL_PRECISION = 1.0e-08;
|
||||
|
||||
/**
|
||||
* IPMT.
|
||||
*
|
||||
* Returns the interest payment for a given period for an investment based on periodic, constant payments
|
||||
* and a constant interest rate.
|
||||
*
|
||||
* Excel Function:
|
||||
* IPMT(rate,per,nper,pv[,fv][,type])
|
||||
*
|
||||
* @param mixed $interestRate Interest rate per period
|
||||
* @param mixed $period Period for which we want to find the interest
|
||||
* @param mixed $numberOfPeriods Number of periods
|
||||
* @param mixed $presentValue Present Value
|
||||
* @param mixed $futureValue Future Value
|
||||
* @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
|
||||
*/
|
||||
public static function payment(
|
||||
mixed $interestRate,
|
||||
mixed $period,
|
||||
mixed $numberOfPeriods,
|
||||
mixed $presentValue,
|
||||
mixed $futureValue = 0,
|
||||
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
|
||||
): string|float {
|
||||
$interestRate = Functions::flattenSingleValue($interestRate);
|
||||
$period = Functions::flattenSingleValue($period);
|
||||
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
|
||||
$presentValue = Functions::flattenSingleValue($presentValue);
|
||||
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
|
||||
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
|
||||
|
||||
try {
|
||||
$interestRate = CashFlowValidations::validateRate($interestRate);
|
||||
$period = CashFlowValidations::validateInt($period);
|
||||
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
|
||||
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
|
||||
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
|
||||
$type = CashFlowValidations::validatePeriodType($type);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if ($period <= 0 || $period > $numberOfPeriods) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
// Calculate
|
||||
$interestAndPrincipal = new InterestAndPrincipal(
|
||||
$interestRate,
|
||||
$period,
|
||||
$numberOfPeriods,
|
||||
$presentValue,
|
||||
$futureValue,
|
||||
$type
|
||||
);
|
||||
|
||||
return $interestAndPrincipal->interest();
|
||||
}
|
||||
|
||||
/**
|
||||
* ISPMT.
|
||||
*
|
||||
* Returns the interest payment for an investment based on an interest rate and a constant payment schedule.
|
||||
*
|
||||
* Excel Function:
|
||||
* =ISPMT(interest_rate, period, number_payments, pv)
|
||||
*
|
||||
* @param mixed $interestRate is the interest rate for the investment
|
||||
* @param mixed $period is the period to calculate the interest rate. It must be betweeen 1 and number_payments.
|
||||
* @param mixed $numberOfPeriods is the number of payments for the annuity
|
||||
* @param mixed $principleRemaining is the loan amount or present value of the payments
|
||||
*/
|
||||
public static function schedulePayment(mixed $interestRate, mixed $period, mixed $numberOfPeriods, mixed $principleRemaining): string|float
|
||||
{
|
||||
$interestRate = Functions::flattenSingleValue($interestRate);
|
||||
$period = Functions::flattenSingleValue($period);
|
||||
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
|
||||
$principleRemaining = Functions::flattenSingleValue($principleRemaining);
|
||||
|
||||
try {
|
||||
$interestRate = CashFlowValidations::validateRate($interestRate);
|
||||
$period = CashFlowValidations::validateInt($period);
|
||||
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
|
||||
$principleRemaining = CashFlowValidations::validateFloat($principleRemaining);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if ($period <= 0 || $period > $numberOfPeriods) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
// Return value
|
||||
$returnValue = 0;
|
||||
|
||||
// Calculate
|
||||
$principlePayment = ($principleRemaining * 1.0) / ($numberOfPeriods * 1.0);
|
||||
for ($i = 0; $i <= $period; ++$i) {
|
||||
$returnValue = $interestRate * $principleRemaining * -1;
|
||||
$principleRemaining -= $principlePayment;
|
||||
// principle needs to be 0 after the last payment, don't let floating point screw it up
|
||||
if ($i == $numberOfPeriods) {
|
||||
$returnValue = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* RATE.
|
||||
*
|
||||
* Returns the interest rate per period of an annuity.
|
||||
* RATE is calculated by iteration and can have zero or more solutions.
|
||||
* If the successive results of RATE do not converge to within 0.0000001 after 20 iterations,
|
||||
* RATE returns the #NUM! error value.
|
||||
*
|
||||
* Excel Function:
|
||||
* RATE(nper,pmt,pv[,fv[,type[,guess]]])
|
||||
*
|
||||
* @param mixed $numberOfPeriods The total number of payment periods in an annuity
|
||||
* @param mixed $payment The payment made each period and cannot change over the life of the annuity.
|
||||
* Typically, pmt includes principal and interest but no other fees or taxes.
|
||||
* @param mixed $presentValue The present value - the total amount that a series of future payments is worth now
|
||||
* @param mixed $futureValue The future value, or a cash balance you want to attain after the last payment is made.
|
||||
* If fv is omitted, it is assumed to be 0 (the future value of a loan,
|
||||
* for example, is 0).
|
||||
* @param mixed $type A number 0 or 1 and indicates when payments are due:
|
||||
* 0 or omitted At the end of the period.
|
||||
* 1 At the beginning of the period.
|
||||
* @param mixed $guess Your guess for what the rate will be.
|
||||
* If you omit guess, it is assumed to be 10 percent.
|
||||
*/
|
||||
public static function rate(
|
||||
mixed $numberOfPeriods,
|
||||
mixed $payment,
|
||||
mixed $presentValue,
|
||||
mixed $futureValue = 0.0,
|
||||
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD,
|
||||
mixed $guess = 0.1
|
||||
): string|float {
|
||||
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
|
||||
$payment = Functions::flattenSingleValue($payment);
|
||||
$presentValue = Functions::flattenSingleValue($presentValue);
|
||||
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
|
||||
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
|
||||
$guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess);
|
||||
|
||||
try {
|
||||
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
|
||||
$payment = CashFlowValidations::validateFloat($payment);
|
||||
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
|
||||
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
|
||||
$type = CashFlowValidations::validatePeriodType($type);
|
||||
$guess = CashFlowValidations::validateFloat($guess);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$rate = $guess;
|
||||
// rest of code adapted from python/numpy
|
||||
$close = false;
|
||||
$iter = 0;
|
||||
while (!$close && $iter < self::FINANCIAL_MAX_ITERATIONS) {
|
||||
$nextdiff = self::rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type);
|
||||
if (!is_numeric($nextdiff)) {
|
||||
break;
|
||||
}
|
||||
$rate1 = $rate - $nextdiff;
|
||||
$close = abs($rate1 - $rate) < self::FINANCIAL_PRECISION;
|
||||
++$iter;
|
||||
$rate = $rate1;
|
||||
}
|
||||
|
||||
return $close ? $rate : ExcelError::NAN();
|
||||
}
|
||||
|
||||
private static function rateNextGuess(float $rate, int $numberOfPeriods, float $payment, float $presentValue, float $futureValue, int $type): string|float
|
||||
{
|
||||
if ($rate == 0.0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
$tt1 = ($rate + 1) ** $numberOfPeriods;
|
||||
$tt2 = ($rate + 1) ** ($numberOfPeriods - 1);
|
||||
$numerator = $futureValue + $tt1 * $presentValue + $payment * ($tt1 - 1) * ($rate * $type + 1) / $rate;
|
||||
$denominator = $numberOfPeriods * $tt2 * $presentValue - $payment * ($tt1 - 1)
|
||||
* ($rate * $type + 1) / ($rate * $rate) + $numberOfPeriods
|
||||
* $payment * $tt2 * ($rate * $type + 1) / $rate + $payment * ($tt1 - 1) * $type / $rate;
|
||||
if ($denominator == 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return $numerator / $denominator;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
|
||||
class InterestAndPrincipal
|
||||
{
|
||||
protected float $interest;
|
||||
|
||||
protected float $principal;
|
||||
|
||||
public function __construct(
|
||||
float $rate = 0.0,
|
||||
int $period = 0,
|
||||
int $numberOfPeriods = 0,
|
||||
float $presentValue = 0,
|
||||
float $futureValue = 0,
|
||||
int $type = FinancialConstants::PAYMENT_END_OF_PERIOD
|
||||
) {
|
||||
$payment = Payments::annuity($rate, $numberOfPeriods, $presentValue, $futureValue, $type);
|
||||
$capital = $presentValue;
|
||||
$interest = 0.0;
|
||||
$principal = 0.0;
|
||||
for ($i = 1; $i <= $period; ++$i) {
|
||||
$interest = ($type === FinancialConstants::PAYMENT_BEGINNING_OF_PERIOD && $i == 1) ? 0 : -$capital * $rate;
|
||||
$principal = (float) $payment - $interest;
|
||||
$capital += $principal;
|
||||
}
|
||||
|
||||
$this->interest = $interest;
|
||||
$this->principal = $principal;
|
||||
}
|
||||
|
||||
public function interest(): float
|
||||
{
|
||||
return $this->interest;
|
||||
}
|
||||
|
||||
public function principal(): float
|
||||
{
|
||||
return $this->principal;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Payments
|
||||
{
|
||||
/**
|
||||
* PMT.
|
||||
*
|
||||
* Returns the constant payment (annuity) for a cash flow with a constant interest rate.
|
||||
*
|
||||
* @param mixed $interestRate Interest rate per period
|
||||
* @param mixed $numberOfPeriods Number of periods
|
||||
* @param mixed $presentValue Present Value
|
||||
* @param mixed $futureValue Future Value
|
||||
* @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function annuity(
|
||||
mixed $interestRate,
|
||||
mixed $numberOfPeriods,
|
||||
mixed $presentValue,
|
||||
mixed $futureValue = 0,
|
||||
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
|
||||
): string|float {
|
||||
$interestRate = Functions::flattenSingleValue($interestRate);
|
||||
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
|
||||
$presentValue = Functions::flattenSingleValue($presentValue);
|
||||
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
|
||||
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
|
||||
|
||||
try {
|
||||
$interestRate = CashFlowValidations::validateRate($interestRate);
|
||||
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
|
||||
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
|
||||
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
|
||||
$type = CashFlowValidations::validatePeriodType($type);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Calculate
|
||||
if ($interestRate != 0.0) {
|
||||
return (-$futureValue - $presentValue * (1 + $interestRate) ** $numberOfPeriods)
|
||||
/ (1 + $interestRate * $type) / (((1 + $interestRate) ** $numberOfPeriods - 1) / $interestRate);
|
||||
}
|
||||
|
||||
return (-$presentValue - $futureValue) / $numberOfPeriods;
|
||||
}
|
||||
|
||||
/**
|
||||
* PPMT.
|
||||
*
|
||||
* Returns the interest payment for a given period for an investment based on periodic, constant payments
|
||||
* and a constant interest rate.
|
||||
*
|
||||
* @param mixed $interestRate Interest rate per period
|
||||
* @param mixed $period Period for which we want to find the interest
|
||||
* @param mixed $numberOfPeriods Number of periods
|
||||
* @param mixed $presentValue Present Value
|
||||
* @param mixed $futureValue Future Value
|
||||
* @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function interestPayment(
|
||||
mixed $interestRate,
|
||||
mixed $period,
|
||||
mixed $numberOfPeriods,
|
||||
mixed $presentValue,
|
||||
mixed $futureValue = 0,
|
||||
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
|
||||
): string|float {
|
||||
$interestRate = Functions::flattenSingleValue($interestRate);
|
||||
$period = Functions::flattenSingleValue($period);
|
||||
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
|
||||
$presentValue = Functions::flattenSingleValue($presentValue);
|
||||
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
|
||||
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
|
||||
|
||||
try {
|
||||
$interestRate = CashFlowValidations::validateRate($interestRate);
|
||||
$period = CashFlowValidations::validateInt($period);
|
||||
$numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
|
||||
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
|
||||
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
|
||||
$type = CashFlowValidations::validatePeriodType($type);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if ($period <= 0 || $period > $numberOfPeriods) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
// Calculate
|
||||
$interestAndPrincipal = new InterestAndPrincipal(
|
||||
$interestRate,
|
||||
$period,
|
||||
$numberOfPeriods,
|
||||
$presentValue,
|
||||
$futureValue,
|
||||
$type
|
||||
);
|
||||
|
||||
return $interestAndPrincipal->principal();
|
||||
}
|
||||
}
|
||||
107
lib/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php
Normal file
107
lib/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Single
|
||||
{
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static function futureValue(mixed $principal, array $schedule): string|float
|
||||
{
|
||||
$principal = Functions::flattenSingleValue($principal);
|
||||
$schedule = Functions::flattenArray($schedule);
|
||||
|
||||
try {
|
||||
$principal = CashFlowValidations::validateFloat($principal);
|
||||
|
||||
foreach ($schedule as $rate) {
|
||||
$rate = CashFlowValidations::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 mixed $rate Interest rate per period
|
||||
* @param mixed $presentValue Present Value
|
||||
* @param mixed $futureValue Future Value
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function periods(mixed $rate, mixed $presentValue, mixed $futureValue): string|float
|
||||
{
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$presentValue = Functions::flattenSingleValue($presentValue);
|
||||
$futureValue = Functions::flattenSingleValue($futureValue);
|
||||
|
||||
try {
|
||||
$rate = CashFlowValidations::validateRate($rate);
|
||||
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
|
||||
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if ($rate <= 0.0 || $presentValue <= 0.0 || $futureValue <= 0.0) {
|
||||
return ExcelError::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 array|float $periods The number of periods over which the investment is made
|
||||
* @param array|float $presentValue Present Value
|
||||
* @param array|float $futureValue Future Value
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function interestRate(array|float $periods = 0.0, array|float $presentValue = 0.0, array|float $futureValue = 0.0): string|float
|
||||
{
|
||||
$periods = Functions::flattenSingleValue($periods);
|
||||
$presentValue = Functions::flattenSingleValue($presentValue);
|
||||
$futureValue = Functions::flattenSingleValue($futureValue);
|
||||
|
||||
try {
|
||||
$periods = CashFlowValidations::validateFloat($periods);
|
||||
$presentValue = CashFlowValidations::validatePresentValue($presentValue);
|
||||
$futureValue = CashFlowValidations::validateFutureValue($futureValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if ($periods <= 0.0 || $presentValue <= 0.0 || $futureValue < 0.0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return ($futureValue / $presentValue) ** (1 / $periods) - 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class NonPeriodic
|
||||
{
|
||||
const FINANCIAL_MAX_ITERATIONS = 128;
|
||||
|
||||
const FINANCIAL_PRECISION = 1.0e-08;
|
||||
|
||||
const DEFAULT_GUESS = 0.1;
|
||||
|
||||
/**
|
||||
* 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 mixed $guess An optional guess at the expected answer
|
||||
*/
|
||||
public static function rate(array $values, array $dates, mixed $guess = self::DEFAULT_GUESS): float|string
|
||||
{
|
||||
$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) ?? self::DEFAULT_GUESS;
|
||||
if (!is_numeric($guess)) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
$guess = ($guess + 0.0) ?: self::DEFAULT_GUESS;
|
||||
$x1 = 0.0;
|
||||
$x2 = $guess + 0.0;
|
||||
$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)) {
|
||||
return $f1;
|
||||
}
|
||||
if (!is_numeric($f2)) {
|
||||
return $f2;
|
||||
}
|
||||
$f1 = (float) $f1;
|
||||
$f2 = (float) $f2;
|
||||
if (($f1 * $f2) < 0.0) {
|
||||
$found = true;
|
||||
|
||||
break;
|
||||
} elseif (abs($f1) < abs($f2)) {
|
||||
$x1 += 1.6 * ($x1 - $x2);
|
||||
$f1 = self::xnpvOrdered($x1, $values, $dates, false);
|
||||
} else {
|
||||
$x2 += 1.6 * ($x2 - $x1);
|
||||
$f2 = self::xnpvOrdered($x2, $values, $dates, false);
|
||||
}
|
||||
}
|
||||
if ($found) {
|
||||
return self::xirrPart3($values, $dates, $x1, $x2);
|
||||
}
|
||||
|
||||
// Newton-Raphson didn't work - try bisection
|
||||
$x1 = $guess - 0.5;
|
||||
$x2 = $guess + 0.5;
|
||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||
$f1 = self::xnpvOrdered($x1, $values, $dates, false, true);
|
||||
$f2 = self::xnpvOrdered($x2, $values, $dates, false, true);
|
||||
if (!is_numeric($f1) || !is_numeric($f2)) {
|
||||
break;
|
||||
}
|
||||
if ($f1 * $f2 <= 0) {
|
||||
$found = true;
|
||||
|
||||
break;
|
||||
}
|
||||
$x1 -= 0.5;
|
||||
$x2 += 0.5;
|
||||
}
|
||||
if ($found) {
|
||||
return self::xirrBisection($values, $dates, $x1, $x2);
|
||||
}
|
||||
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 array|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.
|
||||
*/
|
||||
public static function presentValue(array|float $rate, array $values, array $dates): float|string
|
||||
{
|
||||
return self::xnpvOrdered($rate, $values, $dates, true);
|
||||
}
|
||||
|
||||
private static function bothNegAndPos(bool $neg, bool $pos): bool
|
||||
{
|
||||
return $neg && $pos;
|
||||
}
|
||||
|
||||
private static function xirrPart1(mixed &$values, mixed &$dates): string
|
||||
{
|
||||
$values = Functions::flattenArray($values);
|
||||
$dates = Functions::flattenArray($dates);
|
||||
$valuesIsArray = count($values) > 1;
|
||||
$datesIsArray = count($dates) > 1;
|
||||
if (!$valuesIsArray && !$datesIsArray) {
|
||||
return ExcelError::NA();
|
||||
}
|
||||
if (count($values) != count($dates)) {
|
||||
return ExcelError::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(array &$values): string
|
||||
{
|
||||
$valCount = count($values);
|
||||
$foundpos = false;
|
||||
$foundneg = false;
|
||||
for ($i = 0; $i < $valCount; ++$i) {
|
||||
$fld = $values[$i];
|
||||
if (!is_numeric($fld)) {
|
||||
return ExcelError::VALUE();
|
||||
} elseif ($fld > 0) {
|
||||
$foundpos = true;
|
||||
} elseif ($fld < 0) {
|
||||
$foundneg = true;
|
||||
}
|
||||
}
|
||||
if (!self::bothNegAndPos($foundneg, $foundpos)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private static function xirrPart3(array $values, array $dates, float $x1, float $x2): float|string
|
||||
{
|
||||
$f = self::xnpvOrdered($x1, $values, $dates, false);
|
||||
if ($f < 0.0) {
|
||||
$rtb = $x1;
|
||||
$dx = $x2 - $x1;
|
||||
} else {
|
||||
$rtb = $x2;
|
||||
$dx = $x1 - $x2;
|
||||
}
|
||||
|
||||
$rslt = ExcelError::VALUE();
|
||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||
$dx *= 0.5;
|
||||
$x_mid = $rtb + $dx;
|
||||
$f_mid = (float) 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 xirrBisection(array $values, array $dates, float $x1, float $x2): string|float
|
||||
{
|
||||
$rslt = ExcelError::NAN();
|
||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||
$rslt = ExcelError::NAN();
|
||||
$f1 = self::xnpvOrdered($x1, $values, $dates, false, true);
|
||||
$f2 = self::xnpvOrdered($x2, $values, $dates, false, true);
|
||||
if (!is_numeric($f1) || !is_numeric($f2)) {
|
||||
break;
|
||||
}
|
||||
$f1 = (float) $f1;
|
||||
$f2 = (float) $f2;
|
||||
if (abs($f1) < self::FINANCIAL_PRECISION && abs($f2) < self::FINANCIAL_PRECISION) {
|
||||
break;
|
||||
}
|
||||
if ($f1 * $f2 > 0) {
|
||||
break;
|
||||
}
|
||||
$rslt = ($x1 + $x2) / 2;
|
||||
$f3 = self::xnpvOrdered($rslt, $values, $dates, false, true);
|
||||
if (!is_float($f3)) {
|
||||
break;
|
||||
}
|
||||
if ($f3 * $f1 < 0) {
|
||||
$x2 = $rslt;
|
||||
} else {
|
||||
$x1 = $rslt;
|
||||
}
|
||||
if (abs($f3) < self::FINANCIAL_PRECISION) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $rslt;
|
||||
}
|
||||
|
||||
private static function xnpvOrdered(mixed $rate, mixed $values, mixed $dates, bool $ordered = true, bool $capAtNegative1 = false): float|string
|
||||
{
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$values = Functions::flattenArray($values);
|
||||
$dates = Functions::flattenArray($dates);
|
||||
$valCount = count($values);
|
||||
|
||||
try {
|
||||
self::validateXnpv($rate, $values, $dates);
|
||||
if ($capAtNegative1 && $rate <= -1) {
|
||||
$rate = -1.0 + 1.0E-10;
|
||||
}
|
||||
$date0 = DateTimeExcel\Helpers::getDateValue($dates[0]);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$xnpv = 0.0;
|
||||
for ($i = 0; $i < $valCount; ++$i) {
|
||||
if (!is_numeric($values[$i])) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
try {
|
||||
$datei = DateTimeExcel\Helpers::getDateValue($dates[$i]);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
if ($date0 > $datei) {
|
||||
$dif = $ordered ? ExcelError::NAN() : -((int) DateTimeExcel\Difference::interval($datei, $date0, 'd'));
|
||||
} else {
|
||||
$dif = Functions::scalar(DateTimeExcel\Difference::interval($date0, $datei, 'd'));
|
||||
}
|
||||
if (!is_numeric($dif)) {
|
||||
return $dif;
|
||||
}
|
||||
if ($rate <= -1.0) {
|
||||
$xnpv += -abs($values[$i]) / (-1 - $rate) ** ($dif / 365);
|
||||
} else {
|
||||
$xnpv += $values[$i] / (1 + $rate) ** ($dif / 365);
|
||||
}
|
||||
}
|
||||
|
||||
return is_finite($xnpv) ? $xnpv : ExcelError::VALUE();
|
||||
}
|
||||
|
||||
private static function validateXnpv(mixed $rate, array $values, array $dates): void
|
||||
{
|
||||
if (!is_numeric($rate)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
$valCount = count($values);
|
||||
if ($valCount != count($dates)) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
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
|
||||
*/
|
||||
public static function rate(mixed $values, mixed $guess = 0.1): string|float
|
||||
{
|
||||
if (!is_array($values)) {
|
||||
return ExcelError::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 ExcelError::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 ExcelError::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(mixed $values, mixed $financeRate, mixed $reinvestmentRate): string|float
|
||||
{
|
||||
if (!is_array($values)) {
|
||||
return ExcelError::DIV0();
|
||||
}
|
||||
$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) {
|
||||
return ExcelError::DIV0();
|
||||
}
|
||||
|
||||
$mirr = ((-$npvPos * $rr ** $n)
|
||||
/ ($npvNeg * ($rr))) ** (1.0 / ($n - 1)) - 1.0;
|
||||
|
||||
return is_finite($mirr) ? $mirr : ExcelError::NAN();
|
||||
}
|
||||
|
||||
/**
|
||||
* NPV.
|
||||
*
|
||||
* Returns the Net Present Value of a cash flow series given a discount rate.
|
||||
*
|
||||
* @param array $args
|
||||
*/
|
||||
public static function presentValue(mixed $rate, ...$args): int|float
|
||||
{
|
||||
$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;
|
||||
}
|
||||
}
|
||||
19
lib/PhpSpreadsheet/Calculation/Financial/Constants.php
Normal file
19
lib/PhpSpreadsheet/Calculation/Financial/Constants.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
class Constants
|
||||
{
|
||||
public const BASIS_DAYS_PER_YEAR_NASD = 0;
|
||||
public const BASIS_DAYS_PER_YEAR_ACTUAL = 1;
|
||||
public const BASIS_DAYS_PER_YEAR_360 = 2;
|
||||
public const BASIS_DAYS_PER_YEAR_365 = 3;
|
||||
public const BASIS_DAYS_PER_YEAR_360_EUROPEAN = 4;
|
||||
|
||||
public const FREQUENCY_ANNUAL = 1;
|
||||
public const FREQUENCY_SEMI_ANNUAL = 2;
|
||||
public const FREQUENCY_QUARTERLY = 4;
|
||||
|
||||
public const PAYMENT_END_OF_PERIOD = 0;
|
||||
public const PAYMENT_BEGINNING_OF_PERIOD = 1;
|
||||
}
|
||||
407
lib/PhpSpreadsheet/Calculation/Financial/Coupons.php
Normal file
407
lib/PhpSpreadsheet/Calculation/Financial/Coupons.php
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use DateTime;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class Coupons
|
||||
{
|
||||
private const PERIOD_DATE_PREVIOUS = false;
|
||||
private const PERIOD_DATE_NEXT = true;
|
||||
|
||||
/**
|
||||
* COUPDAYBS.
|
||||
*
|
||||
* Returns the number of days from the beginning of the coupon period to the settlement date.
|
||||
*
|
||||
* Excel Function:
|
||||
* COUPDAYBS(settlement,maturity,frequency[,basis])
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security settlement date is the date after the issue
|
||||
* date when the security is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $frequency The number of coupon payments per year (int).
|
||||
* Valid frequency values are:
|
||||
* 1 Annual
|
||||
* 2 Semi-Annual
|
||||
* 4 Quarterly
|
||||
* @param mixed $basis The type of day count to use (int).
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*/
|
||||
public static function COUPDAYBS(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $frequency,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
): string|int|float {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$frequency = Functions::flattenSingleValue($frequency);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = FinancialValidations::validateSettlementDate($settlement);
|
||||
$maturity = FinancialValidations::validateMaturityDate($maturity);
|
||||
self::validateCouponPeriod($settlement, $maturity);
|
||||
$frequency = FinancialValidations::validateFrequency($frequency);
|
||||
$basis = FinancialValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
|
||||
if (is_string($daysPerYear)) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
$prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
|
||||
|
||||
if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL) {
|
||||
return abs((float) DateTimeExcel\Days::between($prev, $settlement));
|
||||
}
|
||||
|
||||
return (float) DateTimeExcel\YearFrac::fraction($prev, $settlement, $basis) * $daysPerYear;
|
||||
}
|
||||
|
||||
/**
|
||||
* COUPDAYS.
|
||||
*
|
||||
* Returns the number of days in the coupon period that contains the settlement date.
|
||||
*
|
||||
* Excel Function:
|
||||
* COUPDAYS(settlement,maturity,frequency[,basis])
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security settlement date is the date after the issue
|
||||
* date when the security is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $frequency The number of coupon payments per year.
|
||||
* Valid frequency values are:
|
||||
* 1 Annual
|
||||
* 2 Semi-Annual
|
||||
* 4 Quarterly
|
||||
* @param mixed $basis The type of day count to use (int).
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*/
|
||||
public static function COUPDAYS(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $frequency,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
): string|int|float {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$frequency = Functions::flattenSingleValue($frequency);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = FinancialValidations::validateSettlementDate($settlement);
|
||||
$maturity = FinancialValidations::validateMaturityDate($maturity);
|
||||
self::validateCouponPeriod($settlement, $maturity);
|
||||
$frequency = FinancialValidations::validateFrequency($frequency);
|
||||
$basis = FinancialValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
switch ($basis) {
|
||||
case FinancialConstants::BASIS_DAYS_PER_YEAR_365:
|
||||
// Actual/365
|
||||
return 365 / $frequency;
|
||||
case FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL:
|
||||
// Actual/actual
|
||||
if ($frequency == FinancialConstants::FREQUENCY_ANNUAL) {
|
||||
$daysPerYear = (int) Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
|
||||
|
||||
return $daysPerYear / $frequency;
|
||||
}
|
||||
$prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
|
||||
$next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
|
||||
|
||||
return $next - $prev;
|
||||
default:
|
||||
// US (NASD) 30/360, Actual/360 or European 30/360
|
||||
return 360 / $frequency;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* COUPDAYSNC.
|
||||
*
|
||||
* Returns the number of days from the settlement date to the next coupon date.
|
||||
*
|
||||
* Excel Function:
|
||||
* COUPDAYSNC(settlement,maturity,frequency[,basis])
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security settlement date is the date after the issue
|
||||
* date when the security is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $frequency The number of coupon payments per year.
|
||||
* Valid frequency values are:
|
||||
* 1 Annual
|
||||
* 2 Semi-Annual
|
||||
* 4 Quarterly
|
||||
* @param mixed $basis The type of day count to use (int) .
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*/
|
||||
public static function COUPDAYSNC(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $frequency,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
): string|float {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$frequency = Functions::flattenSingleValue($frequency);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = FinancialValidations::validateSettlementDate($settlement);
|
||||
$maturity = FinancialValidations::validateMaturityDate($maturity);
|
||||
self::validateCouponPeriod($settlement, $maturity);
|
||||
$frequency = FinancialValidations::validateFrequency($frequency);
|
||||
$basis = FinancialValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
/** @var int $daysPerYear */
|
||||
$daysPerYear = Helpers::daysPerYear(Functions::Scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
|
||||
$next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
|
||||
|
||||
if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_NASD) {
|
||||
$settlementDate = Date::excelToDateTimeObject($settlement);
|
||||
$settlementEoM = Helpers::isLastDayOfMonth($settlementDate);
|
||||
if ($settlementEoM) {
|
||||
++$settlement;
|
||||
}
|
||||
}
|
||||
|
||||
return (float) DateTimeExcel\YearFrac::fraction($settlement, $next, $basis) * $daysPerYear;
|
||||
}
|
||||
|
||||
/**
|
||||
* COUPNCD.
|
||||
*
|
||||
* Returns the next coupon date after the settlement date.
|
||||
*
|
||||
* Excel Function:
|
||||
* COUPNCD(settlement,maturity,frequency[,basis])
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security settlement date is the date after the issue
|
||||
* date when the security is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $frequency The number of coupon payments per year.
|
||||
* Valid frequency values are:
|
||||
* 1 Annual
|
||||
* 2 Semi-Annual
|
||||
* 4 Quarterly
|
||||
* @param mixed $basis The type of day count to use (int).
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float|string Excel date/time serial value or error message
|
||||
*/
|
||||
public static function COUPNCD(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $frequency,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
): string|float {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$frequency = Functions::flattenSingleValue($frequency);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = FinancialValidations::validateSettlementDate($settlement);
|
||||
$maturity = FinancialValidations::validateMaturityDate($maturity);
|
||||
self::validateCouponPeriod($settlement, $maturity);
|
||||
$frequency = FinancialValidations::validateFrequency($frequency);
|
||||
FinancialValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* COUPNUM.
|
||||
*
|
||||
* Returns the number of coupons payable between the settlement date and maturity date,
|
||||
* rounded up to the nearest whole coupon.
|
||||
*
|
||||
* Excel Function:
|
||||
* COUPNUM(settlement,maturity,frequency[,basis])
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security settlement date is the date after the issue
|
||||
* date when the security is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $frequency The number of coupon payments per year.
|
||||
* Valid frequency values are:
|
||||
* 1 Annual
|
||||
* 2 Semi-Annual
|
||||
* 4 Quarterly
|
||||
* @param mixed $basis The type of day count to use (int).
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*/
|
||||
public static function COUPNUM(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $frequency,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
): string|int {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$frequency = Functions::flattenSingleValue($frequency);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = FinancialValidations::validateSettlementDate($settlement);
|
||||
$maturity = FinancialValidations::validateMaturityDate($maturity);
|
||||
self::validateCouponPeriod($settlement, $maturity);
|
||||
$frequency = FinancialValidations::validateFrequency($frequency);
|
||||
FinancialValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$yearsBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction(
|
||||
$settlement,
|
||||
$maturity,
|
||||
FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
);
|
||||
|
||||
return (int) ceil((float) $yearsBetweenSettlementAndMaturity * $frequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* COUPPCD.
|
||||
*
|
||||
* Returns the previous coupon date before the settlement date.
|
||||
*
|
||||
* Excel Function:
|
||||
* COUPPCD(settlement,maturity,frequency[,basis])
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security settlement date is the date after the issue
|
||||
* date when the security is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $frequency The number of coupon payments per year.
|
||||
* Valid frequency values are:
|
||||
* 1 Annual
|
||||
* 2 Semi-Annual
|
||||
* 4 Quarterly
|
||||
* @param mixed $basis The type of day count to use (int).
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float|string Excel date/time serial value or error message
|
||||
*/
|
||||
public static function COUPPCD(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $frequency,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
): string|float {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$frequency = Functions::flattenSingleValue($frequency);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = FinancialValidations::validateSettlementDate($settlement);
|
||||
$maturity = FinancialValidations::validateMaturityDate($maturity);
|
||||
self::validateCouponPeriod($settlement, $maturity);
|
||||
$frequency = FinancialValidations::validateFrequency($frequency);
|
||||
FinancialValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
|
||||
}
|
||||
|
||||
private static function monthsDiff(DateTime $result, int $months, string $plusOrMinus, int $day, bool $lastDayFlag): void
|
||||
{
|
||||
$result->setDate((int) $result->format('Y'), (int) $result->format('m'), 1);
|
||||
$result->modify("$plusOrMinus $months months");
|
||||
$daysInMonth = (int) $result->format('t');
|
||||
$result->setDate((int) $result->format('Y'), (int) $result->format('m'), $lastDayFlag ? $daysInMonth : min($day, $daysInMonth));
|
||||
}
|
||||
|
||||
private static function couponFirstPeriodDate(float $settlement, float $maturity, int $frequency, bool $next): float
|
||||
{
|
||||
$months = 12 / $frequency;
|
||||
|
||||
$result = Date::excelToDateTimeObject($maturity);
|
||||
$day = (int) $result->format('d');
|
||||
$lastDayFlag = Helpers::isLastDayOfMonth($result);
|
||||
|
||||
while ($settlement < Date::PHPToExcel($result)) {
|
||||
self::monthsDiff($result, $months, '-', $day, $lastDayFlag);
|
||||
}
|
||||
if ($next === true) {
|
||||
self::monthsDiff($result, $months, '+', $day, $lastDayFlag);
|
||||
}
|
||||
|
||||
return (float) Date::PHPToExcel($result);
|
||||
}
|
||||
|
||||
private static function validateCouponPeriod(float $settlement, float $maturity): void
|
||||
{
|
||||
if ($settlement >= $maturity) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
}
|
||||
}
|
||||
265
lib/PhpSpreadsheet/Calculation/Financial/Depreciation.php
Normal file
265
lib/PhpSpreadsheet/Calculation/Financial/Depreciation.php
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Depreciation
|
||||
{
|
||||
private static float $zeroPointZero = 0.0;
|
||||
|
||||
/**
|
||||
* DB.
|
||||
*
|
||||
* Returns the depreciation of an asset for a specified period using the
|
||||
* fixed-declining balance method.
|
||||
* This form of depreciation is used if you want to get a higher depreciation value
|
||||
* at the beginning of the depreciation (as opposed to linear depreciation). The
|
||||
* depreciation value is reduced with every depreciation period by the depreciation
|
||||
* already deducted from the initial cost.
|
||||
*
|
||||
* Excel Function:
|
||||
* DB(cost,salvage,life,period[,month])
|
||||
*
|
||||
* @param mixed $cost Initial cost of the asset
|
||||
* @param mixed $salvage Value at the end of the depreciation.
|
||||
* (Sometimes called the salvage value of the asset)
|
||||
* @param mixed $life Number of periods over which the asset is depreciated.
|
||||
* (Sometimes called the useful life of the asset)
|
||||
* @param mixed $period The period for which you want to calculate the
|
||||
* depreciation. Period must use the same units as life.
|
||||
* @param mixed $month Number of months in the first year. If month is omitted,
|
||||
* it defaults to 12.
|
||||
*/
|
||||
public static function DB(mixed $cost, mixed $salvage, mixed $life, mixed $period, mixed $month = 12): string|float|int
|
||||
{
|
||||
$cost = Functions::flattenSingleValue($cost);
|
||||
$salvage = Functions::flattenSingleValue($salvage);
|
||||
$life = Functions::flattenSingleValue($life);
|
||||
$period = Functions::flattenSingleValue($period);
|
||||
$month = Functions::flattenSingleValue($month);
|
||||
|
||||
try {
|
||||
$cost = self::validateCost($cost);
|
||||
$salvage = self::validateSalvage($salvage);
|
||||
$life = self::validateLife($life);
|
||||
$period = self::validatePeriod($period);
|
||||
$month = self::validateMonth($month);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($cost === self::$zeroPointZero) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Set Fixed Depreciation Rate
|
||||
$fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life);
|
||||
$fixedDepreciationRate = round($fixedDepreciationRate, 3);
|
||||
|
||||
// Loop through each period calculating the depreciation
|
||||
// TODO Handle period value between 0 and 1 (e.g. 0.5)
|
||||
$previousDepreciation = 0;
|
||||
$depreciation = 0;
|
||||
for ($per = 1; $per <= $period; ++$per) {
|
||||
if ($per == 1) {
|
||||
$depreciation = $cost * $fixedDepreciationRate * $month / 12;
|
||||
} elseif ($per == ($life + 1)) {
|
||||
$depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12;
|
||||
} else {
|
||||
$depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate;
|
||||
}
|
||||
$previousDepreciation += $depreciation;
|
||||
}
|
||||
|
||||
return $depreciation;
|
||||
}
|
||||
|
||||
/**
|
||||
* DDB.
|
||||
*
|
||||
* Returns the depreciation of an asset for a specified period using the
|
||||
* double-declining balance method or some other method you specify.
|
||||
*
|
||||
* Excel Function:
|
||||
* DDB(cost,salvage,life,period[,factor])
|
||||
*
|
||||
* @param mixed $cost Initial cost of the asset
|
||||
* @param mixed $salvage Value at the end of the depreciation.
|
||||
* (Sometimes called the salvage value of the asset)
|
||||
* @param mixed $life Number of periods over which the asset is depreciated.
|
||||
* (Sometimes called the useful life of the asset)
|
||||
* @param mixed $period The period for which you want to calculate the
|
||||
* depreciation. Period must use the same units as life.
|
||||
* @param mixed $factor The rate at which the balance declines.
|
||||
* If factor is omitted, it is assumed to be 2 (the
|
||||
* double-declining balance method).
|
||||
*/
|
||||
public static function DDB(mixed $cost, mixed $salvage, mixed $life, mixed $period, mixed $factor = 2.0): float|string
|
||||
{
|
||||
$cost = Functions::flattenSingleValue($cost);
|
||||
$salvage = Functions::flattenSingleValue($salvage);
|
||||
$life = Functions::flattenSingleValue($life);
|
||||
$period = Functions::flattenSingleValue($period);
|
||||
$factor = Functions::flattenSingleValue($factor);
|
||||
|
||||
try {
|
||||
$cost = self::validateCost($cost);
|
||||
$salvage = self::validateSalvage($salvage);
|
||||
$life = self::validateLife($life);
|
||||
$period = self::validatePeriod($period);
|
||||
$factor = self::validateFactor($factor);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($period > $life) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
// Loop through each period calculating the depreciation
|
||||
// TODO Handling for fractional $period values
|
||||
$previousDepreciation = 0;
|
||||
$depreciation = 0;
|
||||
for ($per = 1; $per <= $period; ++$per) {
|
||||
$depreciation = min(
|
||||
($cost - $previousDepreciation) * ($factor / $life),
|
||||
($cost - $salvage - $previousDepreciation)
|
||||
);
|
||||
$previousDepreciation += $depreciation;
|
||||
}
|
||||
|
||||
return $depreciation;
|
||||
}
|
||||
|
||||
/**
|
||||
* SLN.
|
||||
*
|
||||
* Returns the straight-line depreciation of an asset for one period
|
||||
*
|
||||
* @param mixed $cost Initial cost of the asset
|
||||
* @param mixed $salvage Value at the end of the depreciation
|
||||
* @param mixed $life Number of periods over which the asset is depreciated
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function SLN(mixed $cost, mixed $salvage, mixed $life): string|float
|
||||
{
|
||||
$cost = Functions::flattenSingleValue($cost);
|
||||
$salvage = Functions::flattenSingleValue($salvage);
|
||||
$life = Functions::flattenSingleValue($life);
|
||||
|
||||
try {
|
||||
$cost = self::validateCost($cost, true);
|
||||
$salvage = self::validateSalvage($salvage, true);
|
||||
$life = self::validateLife($life, true);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($life === self::$zeroPointZero) {
|
||||
return ExcelError::DIV0();
|
||||
}
|
||||
|
||||
return ($cost - $salvage) / $life;
|
||||
}
|
||||
|
||||
/**
|
||||
* SYD.
|
||||
*
|
||||
* Returns the sum-of-years' digits depreciation of an asset for a specified period.
|
||||
*
|
||||
* @param mixed $cost Initial cost of the asset
|
||||
* @param mixed $salvage Value at the end of the depreciation
|
||||
* @param mixed $life Number of periods over which the asset is depreciated
|
||||
* @param mixed $period Period
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function SYD(mixed $cost, mixed $salvage, mixed $life, mixed $period): string|float
|
||||
{
|
||||
$cost = Functions::flattenSingleValue($cost);
|
||||
$salvage = Functions::flattenSingleValue($salvage);
|
||||
$life = Functions::flattenSingleValue($life);
|
||||
$period = Functions::flattenSingleValue($period);
|
||||
|
||||
try {
|
||||
$cost = self::validateCost($cost, true);
|
||||
$salvage = self::validateSalvage($salvage);
|
||||
$life = self::validateLife($life);
|
||||
$period = self::validatePeriod($period);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($period > $life) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$syd = (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
|
||||
|
||||
return $syd;
|
||||
}
|
||||
|
||||
private static function validateCost(mixed $cost, bool $negativeValueAllowed = false): float
|
||||
{
|
||||
$cost = FinancialValidations::validateFloat($cost);
|
||||
if ($cost < 0.0 && $negativeValueAllowed === false) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $cost;
|
||||
}
|
||||
|
||||
private static function validateSalvage(mixed $salvage, bool $negativeValueAllowed = false): float
|
||||
{
|
||||
$salvage = FinancialValidations::validateFloat($salvage);
|
||||
if ($salvage < 0.0 && $negativeValueAllowed === false) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $salvage;
|
||||
}
|
||||
|
||||
private static function validateLife(mixed $life, bool $negativeValueAllowed = false): float
|
||||
{
|
||||
$life = FinancialValidations::validateFloat($life);
|
||||
if ($life < 0.0 && $negativeValueAllowed === false) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $life;
|
||||
}
|
||||
|
||||
private static function validatePeriod(mixed $period, bool $negativeValueAllowed = false): float
|
||||
{
|
||||
$period = FinancialValidations::validateFloat($period);
|
||||
if ($period <= 0.0 && $negativeValueAllowed === false) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $period;
|
||||
}
|
||||
|
||||
private static function validateMonth(mixed $month): int
|
||||
{
|
||||
$month = FinancialValidations::validateInt($month);
|
||||
if ($month < 1) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $month;
|
||||
}
|
||||
|
||||
private static function validateFactor(mixed $factor): float
|
||||
{
|
||||
$factor = FinancialValidations::validateFloat($factor);
|
||||
if ($factor <= 0.0) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $factor;
|
||||
}
|
||||
}
|
||||
127
lib/PhpSpreadsheet/Calculation/Financial/Dollar.php
Normal file
127
lib/PhpSpreadsheet/Calculation/Financial/Dollar.php
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\TextData\Format;
|
||||
|
||||
class Dollar
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* DOLLAR.
|
||||
*
|
||||
* This function converts a number to text using currency format, with the decimals rounded to the specified place.
|
||||
* The format used is $#,##0.00_);($#,##0.00)..
|
||||
*
|
||||
* @param mixed $number The value to format, or can be an array of numbers
|
||||
* Or can be an array of values
|
||||
* @param mixed $precision The number of digits to display to the right of the decimal point (as an integer).
|
||||
* If precision is negative, number is rounded to the left of the decimal point.
|
||||
* If you omit precision, it is assumed to be 2
|
||||
* Or can be an array of precision values
|
||||
*
|
||||
* @return array|string If an array of values is passed for either of the arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function format(mixed $number, mixed $precision = 2)
|
||||
{
|
||||
return Format::DOLLAR($number, $precision);
|
||||
}
|
||||
|
||||
/**
|
||||
* DOLLARDE.
|
||||
*
|
||||
* Converts a dollar price expressed as an integer part and a fraction
|
||||
* part into a dollar price expressed as a decimal number.
|
||||
* Fractional dollar numbers are sometimes used for security prices.
|
||||
*
|
||||
* Excel Function:
|
||||
* DOLLARDE(fractional_dollar,fraction)
|
||||
*
|
||||
* @param mixed $fractionalDollar Fractional Dollar
|
||||
* Or can be an array of values
|
||||
* @param mixed $fraction Fraction
|
||||
* Or can be an array of values
|
||||
*/
|
||||
public static function decimal(mixed $fractionalDollar = null, mixed $fraction = 0): array|string|float
|
||||
{
|
||||
if (is_array($fractionalDollar) || is_array($fraction)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $fractionalDollar, $fraction);
|
||||
}
|
||||
|
||||
try {
|
||||
$fractionalDollar = FinancialValidations::validateFloat(
|
||||
Functions::flattenSingleValue($fractionalDollar) ?? 0.0
|
||||
);
|
||||
$fraction = FinancialValidations::validateInt(Functions::flattenSingleValue($fraction));
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Additional parameter validations
|
||||
if ($fraction < 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
if ($fraction == 0) {
|
||||
return ExcelError::DIV0();
|
||||
}
|
||||
|
||||
$dollars = ($fractionalDollar < 0) ? ceil($fractionalDollar) : floor($fractionalDollar);
|
||||
$cents = fmod($fractionalDollar, 1.0);
|
||||
$cents /= $fraction;
|
||||
$cents *= 10 ** ceil(log10($fraction));
|
||||
|
||||
return $dollars + $cents;
|
||||
}
|
||||
|
||||
/**
|
||||
* DOLLARFR.
|
||||
*
|
||||
* Converts a dollar price expressed as a decimal number into a dollar price
|
||||
* expressed as a fraction.
|
||||
* Fractional dollar numbers are sometimes used for security prices.
|
||||
*
|
||||
* Excel Function:
|
||||
* DOLLARFR(decimal_dollar,fraction)
|
||||
*
|
||||
* @param mixed $decimalDollar Decimal Dollar
|
||||
* Or can be an array of values
|
||||
* @param mixed $fraction Fraction
|
||||
* Or can be an array of values
|
||||
*/
|
||||
public static function fractional(mixed $decimalDollar = null, mixed $fraction = 0): array|string|float
|
||||
{
|
||||
if (is_array($decimalDollar) || is_array($fraction)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $decimalDollar, $fraction);
|
||||
}
|
||||
|
||||
try {
|
||||
$decimalDollar = FinancialValidations::validateFloat(
|
||||
Functions::flattenSingleValue($decimalDollar) ?? 0.0
|
||||
);
|
||||
$fraction = FinancialValidations::validateInt(Functions::flattenSingleValue($fraction));
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Additional parameter validations
|
||||
if ($fraction < 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
if ($fraction == 0) {
|
||||
return ExcelError::DIV0();
|
||||
}
|
||||
|
||||
$dollars = ($decimalDollar < 0.0) ? ceil($decimalDollar) : floor($decimalDollar);
|
||||
$cents = fmod($decimalDollar, 1);
|
||||
$cents *= $fraction;
|
||||
$cents *= 10 ** (-ceil(log10($fraction)));
|
||||
|
||||
return $dollars + $cents;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class FinancialValidations
|
||||
{
|
||||
public static function validateDate(mixed $date): float
|
||||
{
|
||||
return DateTimeExcel\Helpers::getDateValue($date);
|
||||
}
|
||||
|
||||
public static function validateSettlementDate(mixed $settlement): float
|
||||
{
|
||||
return self::validateDate($settlement);
|
||||
}
|
||||
|
||||
public static function validateMaturityDate(mixed $maturity): float
|
||||
{
|
||||
return self::validateDate($maturity);
|
||||
}
|
||||
|
||||
public static function validateFloat(mixed $value): float
|
||||
{
|
||||
if (!is_numeric($value)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return (float) $value;
|
||||
}
|
||||
|
||||
public static function validateInt(mixed $value): int
|
||||
{
|
||||
if (!is_numeric($value)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return (int) floor((float) $value);
|
||||
}
|
||||
|
||||
public static function validateRate(mixed $rate): float
|
||||
{
|
||||
$rate = self::validateFloat($rate);
|
||||
if ($rate < 0.0) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $rate;
|
||||
}
|
||||
|
||||
public static function validateFrequency(mixed $frequency): int
|
||||
{
|
||||
$frequency = self::validateInt($frequency);
|
||||
if (
|
||||
($frequency !== FinancialConstants::FREQUENCY_ANNUAL)
|
||||
&& ($frequency !== FinancialConstants::FREQUENCY_SEMI_ANNUAL)
|
||||
&& ($frequency !== FinancialConstants::FREQUENCY_QUARTERLY)
|
||||
) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $frequency;
|
||||
}
|
||||
|
||||
public static function validateBasis(mixed $basis): int
|
||||
{
|
||||
if (!is_numeric($basis)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
$basis = (int) $basis;
|
||||
if (($basis < 0) || ($basis > 4)) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $basis;
|
||||
}
|
||||
|
||||
public static function validatePrice(mixed $price): float
|
||||
{
|
||||
$price = self::validateFloat($price);
|
||||
if ($price < 0.0) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $price;
|
||||
}
|
||||
|
||||
public static function validateParValue(mixed $parValue): float
|
||||
{
|
||||
$parValue = self::validateFloat($parValue);
|
||||
if ($parValue < 0.0) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $parValue;
|
||||
}
|
||||
|
||||
public static function validateYield(mixed $yield): float
|
||||
{
|
||||
$yield = self::validateFloat($yield);
|
||||
if ($yield < 0.0) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $yield;
|
||||
}
|
||||
|
||||
public static function validateDiscount(mixed $discount): float
|
||||
{
|
||||
$discount = self::validateFloat($discount);
|
||||
if ($discount <= 0.0) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $discount;
|
||||
}
|
||||
}
|
||||
58
lib/PhpSpreadsheet/Calculation/Financial/Helpers.php
Normal file
58
lib/PhpSpreadsheet/Calculation/Financial/Helpers.php
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use DateTimeInterface;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Helpers
|
||||
{
|
||||
/**
|
||||
* daysPerYear.
|
||||
*
|
||||
* Returns the number of days in a specified year, as defined by the "basis" value
|
||||
*
|
||||
* @param int|string $year The year against which we're testing
|
||||
* @param int|string $basis The type of day count:
|
||||
* 0 or omitted US (NASD) 360
|
||||
* 1 Actual (365 or 366 in a leap year)
|
||||
* 2 360
|
||||
* 3 365
|
||||
* 4 European 360
|
||||
*
|
||||
* @return int|string Result, or a string containing an error
|
||||
*/
|
||||
public static function daysPerYear($year, $basis = 0): string|int
|
||||
{
|
||||
if (!is_numeric($basis)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
switch ($basis) {
|
||||
case FinancialConstants::BASIS_DAYS_PER_YEAR_NASD:
|
||||
case FinancialConstants::BASIS_DAYS_PER_YEAR_360:
|
||||
case FinancialConstants::BASIS_DAYS_PER_YEAR_360_EUROPEAN:
|
||||
return 360;
|
||||
case FinancialConstants::BASIS_DAYS_PER_YEAR_365:
|
||||
return 365;
|
||||
case FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL:
|
||||
return (DateTimeExcel\Helpers::isLeapYear($year)) ? 366 : 365;
|
||||
}
|
||||
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
/**
|
||||
* isLastDayOfMonth.
|
||||
*
|
||||
* Returns a boolean TRUE/FALSE indicating if this date is the last date of the month
|
||||
*
|
||||
* @param DateTimeInterface $date The date for testing
|
||||
*/
|
||||
public static function isLastDayOfMonth(DateTimeInterface $date): bool
|
||||
{
|
||||
return $date->format('d') === $date->format('t');
|
||||
}
|
||||
}
|
||||
71
lib/PhpSpreadsheet/Calculation/Financial/InterestRate.php
Normal file
71
lib/PhpSpreadsheet/Calculation/Financial/InterestRate.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class InterestRate
|
||||
{
|
||||
/**
|
||||
* EFFECT.
|
||||
*
|
||||
* Returns the effective interest rate given the nominal rate and the number of
|
||||
* compounding payments per year.
|
||||
*
|
||||
* Excel Function:
|
||||
* EFFECT(nominal_rate,npery)
|
||||
*
|
||||
* @param mixed $nominalRate Nominal interest rate as a float
|
||||
* @param mixed $periodsPerYear Integer number of compounding payments per year
|
||||
*/
|
||||
public static function effective(mixed $nominalRate = 0, mixed $periodsPerYear = 0): string|float
|
||||
{
|
||||
$nominalRate = Functions::flattenSingleValue($nominalRate);
|
||||
$periodsPerYear = Functions::flattenSingleValue($periodsPerYear);
|
||||
|
||||
try {
|
||||
$nominalRate = FinancialValidations::validateFloat($nominalRate);
|
||||
$periodsPerYear = FinancialValidations::validateInt($periodsPerYear);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($nominalRate <= 0 || $periodsPerYear < 1) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return ((1 + $nominalRate / $periodsPerYear) ** $periodsPerYear) - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOMINAL.
|
||||
*
|
||||
* Returns the nominal interest rate given the effective rate and the number of compounding payments per year.
|
||||
*
|
||||
* @param mixed $effectiveRate Effective interest rate as a float
|
||||
* @param mixed $periodsPerYear Integer number of compounding payments per year
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function nominal(mixed $effectiveRate = 0, mixed $periodsPerYear = 0): string|float
|
||||
{
|
||||
$effectiveRate = Functions::flattenSingleValue($effectiveRate);
|
||||
$periodsPerYear = Functions::flattenSingleValue($periodsPerYear);
|
||||
|
||||
try {
|
||||
$effectiveRate = FinancialValidations::validateFloat($effectiveRate);
|
||||
$periodsPerYear = FinancialValidations::validateInt($periodsPerYear);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($effectiveRate <= 0 || $periodsPerYear < 1) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
// Calculate
|
||||
return $periodsPerYear * (($effectiveRate + 1) ** (1 / $periodsPerYear) - 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\YearFrac;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class AccruedInterest
|
||||
{
|
||||
public const ACCRINT_CALCMODE_ISSUE_TO_SETTLEMENT = true;
|
||||
|
||||
public const ACCRINT_CALCMODE_FIRST_INTEREST_TO_SETTLEMENT = false;
|
||||
|
||||
/**
|
||||
* ACCRINT.
|
||||
*
|
||||
* Returns the accrued interest for a security that pays periodic interest.
|
||||
*
|
||||
* Excel Function:
|
||||
* ACCRINT(issue,firstinterest,settlement,rate,par,frequency[,basis][,calc_method])
|
||||
*
|
||||
* @param mixed $issue the security's issue date
|
||||
* @param mixed $firstInterest the security's first interest date
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security settlement date is the date after the issue date
|
||||
* when the security is traded to the buyer.
|
||||
* @param mixed $rate The security's annual coupon rate
|
||||
* @param mixed $parValue The security's par value.
|
||||
* If you omit par, ACCRINT uses $1,000.
|
||||
* @param mixed $frequency The number of coupon payments per year.
|
||||
* Valid frequency values are:
|
||||
* 1 Annual
|
||||
* 2 Semi-Annual
|
||||
* 4 Quarterly
|
||||
* @param mixed $basis The type of day count to use.
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
* @param mixed $calcMethod Unused by PhpSpreadsheet, and apparently by Excel (https://exceljet.net/functions/accrint-function)
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function periodic(
|
||||
mixed $issue,
|
||||
mixed $firstInterest,
|
||||
mixed $settlement,
|
||||
mixed $rate,
|
||||
mixed $parValue = 1000,
|
||||
mixed $frequency = FinancialConstants::FREQUENCY_ANNUAL,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD,
|
||||
mixed $calcMethod = self::ACCRINT_CALCMODE_ISSUE_TO_SETTLEMENT
|
||||
) {
|
||||
$issue = Functions::flattenSingleValue($issue);
|
||||
$firstInterest = Functions::flattenSingleValue($firstInterest);
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue);
|
||||
$frequency = ($frequency === null)
|
||||
? FinancialConstants::FREQUENCY_ANNUAL
|
||||
: Functions::flattenSingleValue($frequency);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$issue = SecurityValidations::validateIssueDate($issue);
|
||||
$settlement = SecurityValidations::validateSettlementDate($settlement);
|
||||
SecurityValidations::validateSecurityPeriod($issue, $settlement);
|
||||
$rate = SecurityValidations::validateRate($rate);
|
||||
$parValue = SecurityValidations::validateParValue($parValue);
|
||||
SecurityValidations::validateFrequency($frequency);
|
||||
$basis = SecurityValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$daysBetweenIssueAndSettlement = Functions::scalar(YearFrac::fraction($issue, $settlement, $basis));
|
||||
if (!is_numeric($daysBetweenIssueAndSettlement)) {
|
||||
// return date error
|
||||
return $daysBetweenIssueAndSettlement;
|
||||
}
|
||||
$daysBetweenFirstInterestAndSettlement = Functions::scalar(YearFrac::fraction($firstInterest, $settlement, $basis));
|
||||
if (!is_numeric($daysBetweenFirstInterestAndSettlement)) {
|
||||
// return date error
|
||||
return $daysBetweenFirstInterestAndSettlement;
|
||||
}
|
||||
|
||||
return $parValue * $rate * $daysBetweenIssueAndSettlement;
|
||||
}
|
||||
|
||||
/**
|
||||
* ACCRINTM.
|
||||
*
|
||||
* Returns the accrued interest for a security that pays interest at maturity.
|
||||
*
|
||||
* Excel Function:
|
||||
* ACCRINTM(issue,settlement,rate[,par[,basis]])
|
||||
*
|
||||
* @param mixed $issue The security's issue date
|
||||
* @param mixed $settlement The security's settlement (or maturity) date
|
||||
* @param mixed $rate The security's annual coupon rate
|
||||
* @param mixed $parValue The security's par value.
|
||||
* If you omit parValue, ACCRINT uses $1,000.
|
||||
* @param mixed $basis The type of day count to use.
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function atMaturity(
|
||||
mixed $issue,
|
||||
mixed $settlement,
|
||||
mixed $rate,
|
||||
mixed $parValue = 1000,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
) {
|
||||
$issue = Functions::flattenSingleValue($issue);
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$issue = SecurityValidations::validateIssueDate($issue);
|
||||
$settlement = SecurityValidations::validateSettlementDate($settlement);
|
||||
SecurityValidations::validateSecurityPeriod($issue, $settlement);
|
||||
$rate = SecurityValidations::validateRate($rate);
|
||||
$parValue = SecurityValidations::validateParValue($parValue);
|
||||
$basis = SecurityValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$daysBetweenIssueAndSettlement = Functions::scalar(YearFrac::fraction($issue, $settlement, $basis));
|
||||
if (!is_numeric($daysBetweenIssueAndSettlement)) {
|
||||
// return date error
|
||||
return $daysBetweenIssueAndSettlement;
|
||||
}
|
||||
|
||||
return $parValue * $rate * $daysBetweenIssueAndSettlement;
|
||||
}
|
||||
}
|
||||
283
lib/PhpSpreadsheet/Calculation/Financial/Securities/Price.php
Normal file
283
lib/PhpSpreadsheet/Calculation/Financial/Securities/Price.php
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Coupons;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Price
|
||||
{
|
||||
/**
|
||||
* PRICE.
|
||||
*
|
||||
* Returns the price per $100 face value of a security that pays periodic interest.
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security settlement date is the date after the issue date when the security
|
||||
* is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $rate the security's annual coupon rate
|
||||
* @param mixed $yield the security's annual yield
|
||||
* @param mixed $redemption The number of coupon payments per year.
|
||||
* For annual payments, frequency = 1;
|
||||
* for semiannual, frequency = 2;
|
||||
* for quarterly, frequency = 4.
|
||||
* @param mixed $basis The type of day count to use.
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function price(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $rate,
|
||||
mixed $yield,
|
||||
mixed $redemption,
|
||||
mixed $frequency,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
): string|float {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$yield = Functions::flattenSingleValue($yield);
|
||||
$redemption = Functions::flattenSingleValue($redemption);
|
||||
$frequency = Functions::flattenSingleValue($frequency);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = SecurityValidations::validateSettlementDate($settlement);
|
||||
$maturity = SecurityValidations::validateMaturityDate($maturity);
|
||||
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
|
||||
$rate = SecurityValidations::validateRate($rate);
|
||||
$yield = SecurityValidations::validateYield($yield);
|
||||
$redemption = SecurityValidations::validateRedemption($redemption);
|
||||
$frequency = SecurityValidations::validateFrequency($frequency);
|
||||
$basis = SecurityValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$dsc = (float) Coupons::COUPDAYSNC($settlement, $maturity, $frequency, $basis);
|
||||
$e = (float) Coupons::COUPDAYS($settlement, $maturity, $frequency, $basis);
|
||||
$n = (int) Coupons::COUPNUM($settlement, $maturity, $frequency, $basis);
|
||||
$a = (float) Coupons::COUPDAYBS($settlement, $maturity, $frequency, $basis);
|
||||
|
||||
$baseYF = 1.0 + ($yield / $frequency);
|
||||
$rfp = 100 * ($rate / $frequency);
|
||||
$de = $dsc / $e;
|
||||
|
||||
$result = $redemption / $baseYF ** (--$n + $de);
|
||||
for ($k = 0; $k <= $n; ++$k) {
|
||||
$result += $rfp / ($baseYF ** ($k + $de));
|
||||
}
|
||||
$result -= $rfp * ($a / $e);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* PRICEDISC.
|
||||
*
|
||||
* Returns the price per $100 face value of a discounted security.
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security settlement date is the date after the issue date when the security
|
||||
* is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $discount The security's discount rate
|
||||
* @param mixed $redemption The security's redemption value per $100 face value
|
||||
* @param mixed $basis The type of day count to use.
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function priceDiscounted(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $discount,
|
||||
mixed $redemption,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
) {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$discount = Functions::flattenSingleValue($discount);
|
||||
$redemption = Functions::flattenSingleValue($redemption);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = SecurityValidations::validateSettlementDate($settlement);
|
||||
$maturity = SecurityValidations::validateMaturityDate($maturity);
|
||||
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
|
||||
$discount = SecurityValidations::validateDiscount($discount);
|
||||
$redemption = SecurityValidations::validateRedemption($redemption);
|
||||
$basis = SecurityValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
|
||||
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenSettlementAndMaturity;
|
||||
}
|
||||
|
||||
return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity);
|
||||
}
|
||||
|
||||
/**
|
||||
* PRICEMAT.
|
||||
*
|
||||
* Returns the price per $100 face value of a security that pays interest at maturity.
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security's settlement date is the date after the issue date when the
|
||||
* security is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $issue The security's issue date
|
||||
* @param mixed $rate The security's interest rate at date of issue
|
||||
* @param mixed $yield The security's annual yield
|
||||
* @param mixed $basis The type of day count to use.
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function priceAtMaturity(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $issue,
|
||||
mixed $rate,
|
||||
mixed $yield,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
) {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$issue = Functions::flattenSingleValue($issue);
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$yield = Functions::flattenSingleValue($yield);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = SecurityValidations::validateSettlementDate($settlement);
|
||||
$maturity = SecurityValidations::validateMaturityDate($maturity);
|
||||
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
|
||||
$issue = SecurityValidations::validateIssueDate($issue);
|
||||
$rate = SecurityValidations::validateRate($rate);
|
||||
$yield = SecurityValidations::validateYield($yield);
|
||||
$basis = SecurityValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
|
||||
if (!is_numeric($daysPerYear)) {
|
||||
return $daysPerYear;
|
||||
}
|
||||
$daysBetweenIssueAndSettlement = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis));
|
||||
if (!is_numeric($daysBetweenIssueAndSettlement)) {
|
||||
// return date error
|
||||
return $daysBetweenIssueAndSettlement;
|
||||
}
|
||||
$daysBetweenIssueAndSettlement *= $daysPerYear;
|
||||
$daysBetweenIssueAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis));
|
||||
if (!is_numeric($daysBetweenIssueAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenIssueAndMaturity;
|
||||
}
|
||||
$daysBetweenIssueAndMaturity *= $daysPerYear;
|
||||
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
|
||||
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenSettlementAndMaturity;
|
||||
}
|
||||
$daysBetweenSettlementAndMaturity *= $daysPerYear;
|
||||
|
||||
return (100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100))
|
||||
/ (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield))
|
||||
- (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* RECEIVED.
|
||||
*
|
||||
* Returns the amount received at maturity for a fully invested Security.
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security settlement date is the date after the issue date when the security
|
||||
* is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $investment The amount invested in the security
|
||||
* @param mixed $discount The security's discount rate
|
||||
* @param mixed $basis The type of day count to use.
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function received(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $investment,
|
||||
mixed $discount,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
) {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$investment = Functions::flattenSingleValue($investment);
|
||||
$discount = Functions::flattenSingleValue($discount);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = SecurityValidations::validateSettlementDate($settlement);
|
||||
$maturity = SecurityValidations::validateMaturityDate($maturity);
|
||||
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
|
||||
$investment = SecurityValidations::validateFloat($investment);
|
||||
$discount = SecurityValidations::validateDiscount($discount);
|
||||
$basis = SecurityValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($investment <= 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis);
|
||||
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
|
||||
// return date error
|
||||
return Functions::scalar($daysBetweenSettlementAndMaturity);
|
||||
}
|
||||
|
||||
return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity));
|
||||
}
|
||||
}
|
||||
134
lib/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php
Normal file
134
lib/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Rates
|
||||
{
|
||||
/**
|
||||
* DISC.
|
||||
*
|
||||
* Returns the discount rate for a security.
|
||||
*
|
||||
* Excel Function:
|
||||
* DISC(settlement,maturity,price,redemption[,basis])
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security settlement date is the date after the issue
|
||||
* date when the security is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $price The security's price per $100 face value
|
||||
* @param mixed $redemption The security's redemption value per $100 face value
|
||||
* @param mixed $basis The type of day count to use.
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*/
|
||||
public static function discount(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $price,
|
||||
mixed $redemption,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
): float|string {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$price = Functions::flattenSingleValue($price);
|
||||
$redemption = Functions::flattenSingleValue($redemption);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = SecurityValidations::validateSettlementDate($settlement);
|
||||
$maturity = SecurityValidations::validateMaturityDate($maturity);
|
||||
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
|
||||
$price = SecurityValidations::validatePrice($price);
|
||||
$redemption = SecurityValidations::validateRedemption($redemption);
|
||||
$basis = SecurityValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($price <= 0.0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
|
||||
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenSettlementAndMaturity;
|
||||
}
|
||||
|
||||
return (1 - $price / $redemption) / $daysBetweenSettlementAndMaturity;
|
||||
}
|
||||
|
||||
/**
|
||||
* INTRATE.
|
||||
*
|
||||
* Returns the interest rate for a fully invested security.
|
||||
*
|
||||
* Excel Function:
|
||||
* INTRATE(settlement,maturity,investment,redemption[,basis])
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security settlement date is the date after the issue date when the security
|
||||
* is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $investment the amount invested in the security
|
||||
* @param mixed $redemption the amount to be received at maturity
|
||||
* @param mixed $basis The type of day count to use.
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*/
|
||||
public static function interest(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $investment,
|
||||
mixed $redemption,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
): float|string {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$investment = Functions::flattenSingleValue($investment);
|
||||
$redemption = Functions::flattenSingleValue($redemption);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = SecurityValidations::validateSettlementDate($settlement);
|
||||
$maturity = SecurityValidations::validateMaturityDate($maturity);
|
||||
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
|
||||
$investment = SecurityValidations::validateFloat($investment);
|
||||
$redemption = SecurityValidations::validateRedemption($redemption);
|
||||
$basis = SecurityValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($investment <= 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
|
||||
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenSettlementAndMaturity;
|
||||
}
|
||||
|
||||
return (($redemption / $investment) - 1) / ($daysBetweenSettlementAndMaturity);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\FinancialValidations;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class SecurityValidations extends FinancialValidations
|
||||
{
|
||||
public static function validateIssueDate(mixed $issue): float
|
||||
{
|
||||
return self::validateDate($issue);
|
||||
}
|
||||
|
||||
public static function validateSecurityPeriod(mixed $settlement, mixed $maturity): void
|
||||
{
|
||||
if ($settlement >= $maturity) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
}
|
||||
|
||||
public static function validateRedemption(mixed $redemption): float
|
||||
{
|
||||
$redemption = self::validateFloat($redemption);
|
||||
if ($redemption <= 0.0) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $redemption;
|
||||
}
|
||||
}
|
||||
153
lib/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php
Normal file
153
lib/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Yields
|
||||
{
|
||||
/**
|
||||
* YIELDDISC.
|
||||
*
|
||||
* Returns the annual yield of a security that pays interest at maturity.
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security's settlement date is the date after the issue date when the security
|
||||
* is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $price The security's price per $100 face value
|
||||
* @param mixed $redemption The security's redemption value per $100 face value
|
||||
* @param mixed $basis The type of day count to use.
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function yieldDiscounted(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $price,
|
||||
mixed $redemption,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
) {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$price = Functions::flattenSingleValue($price);
|
||||
$redemption = Functions::flattenSingleValue($redemption);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = SecurityValidations::validateSettlementDate($settlement);
|
||||
$maturity = SecurityValidations::validateMaturityDate($maturity);
|
||||
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
|
||||
$price = SecurityValidations::validatePrice($price);
|
||||
$redemption = SecurityValidations::validateRedemption($redemption);
|
||||
$basis = SecurityValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
|
||||
if (!is_numeric($daysPerYear)) {
|
||||
return $daysPerYear;
|
||||
}
|
||||
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
|
||||
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenSettlementAndMaturity;
|
||||
}
|
||||
$daysBetweenSettlementAndMaturity *= $daysPerYear;
|
||||
|
||||
return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity);
|
||||
}
|
||||
|
||||
/**
|
||||
* YIELDMAT.
|
||||
*
|
||||
* Returns the annual yield of a security that pays interest at maturity.
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security's settlement date is the date after the issue date when the security
|
||||
* is traded to the buyer.
|
||||
* @param mixed $maturity The security's maturity date.
|
||||
* The maturity date is the date when the security expires.
|
||||
* @param mixed $issue The security's issue date
|
||||
* @param mixed $rate The security's interest rate at date of issue
|
||||
* @param mixed $price The security's price per $100 face value
|
||||
* @param mixed $basis The type of day count to use.
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function yieldAtMaturity(
|
||||
mixed $settlement,
|
||||
mixed $maturity,
|
||||
mixed $issue,
|
||||
mixed $rate,
|
||||
mixed $price,
|
||||
mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
) {
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$issue = Functions::flattenSingleValue($issue);
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$price = Functions::flattenSingleValue($price);
|
||||
$basis = ($basis === null)
|
||||
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
|
||||
: Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = SecurityValidations::validateSettlementDate($settlement);
|
||||
$maturity = SecurityValidations::validateMaturityDate($maturity);
|
||||
SecurityValidations::validateSecurityPeriod($settlement, $maturity);
|
||||
$issue = SecurityValidations::validateIssueDate($issue);
|
||||
$rate = SecurityValidations::validateRate($rate);
|
||||
$price = SecurityValidations::validatePrice($price);
|
||||
$basis = SecurityValidations::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
|
||||
if (!is_numeric($daysPerYear)) {
|
||||
return $daysPerYear;
|
||||
}
|
||||
$daysBetweenIssueAndSettlement = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis));
|
||||
if (!is_numeric($daysBetweenIssueAndSettlement)) {
|
||||
// return date error
|
||||
return $daysBetweenIssueAndSettlement;
|
||||
}
|
||||
$daysBetweenIssueAndSettlement *= $daysPerYear;
|
||||
$daysBetweenIssueAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis));
|
||||
if (!is_numeric($daysBetweenIssueAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenIssueAndMaturity;
|
||||
}
|
||||
$daysBetweenIssueAndMaturity *= $daysPerYear;
|
||||
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
|
||||
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenSettlementAndMaturity;
|
||||
}
|
||||
$daysBetweenSettlementAndMaturity *= $daysPerYear;
|
||||
|
||||
return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate)
|
||||
- (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate)))
|
||||
/ (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate)))
|
||||
* ($daysPerYear / $daysBetweenSettlementAndMaturity);
|
||||
}
|
||||
}
|
||||
146
lib/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php
Normal file
146
lib/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class TreasuryBill
|
||||
{
|
||||
/**
|
||||
* TBILLEQ.
|
||||
*
|
||||
* Returns the bond-equivalent yield for a Treasury bill.
|
||||
*
|
||||
* @param mixed $settlement The Treasury bill's settlement date.
|
||||
* The Treasury bill's settlement date is the date after the issue date
|
||||
* when the Treasury bill is traded to the buyer.
|
||||
* @param mixed $maturity The Treasury bill's maturity date.
|
||||
* The maturity date is the date when the Treasury bill expires.
|
||||
* @param mixed $discount The Treasury bill's discount rate
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function bondEquivalentYield(mixed $settlement, mixed $maturity, mixed $discount): string|float
|
||||
{
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$discount = Functions::flattenSingleValue($discount);
|
||||
|
||||
try {
|
||||
$settlement = FinancialValidations::validateSettlementDate($settlement);
|
||||
$maturity = FinancialValidations::validateMaturityDate($maturity);
|
||||
$discount = FinancialValidations::validateFloat($discount);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($discount <= 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
|
||||
$daysPerYear = Helpers::daysPerYear(
|
||||
Functions::scalar(DateTimeExcel\DateParts::year($maturity)),
|
||||
FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL
|
||||
);
|
||||
|
||||
if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity);
|
||||
}
|
||||
|
||||
/**
|
||||
* TBILLPRICE.
|
||||
*
|
||||
* Returns the price per $100 face value for a Treasury bill.
|
||||
*
|
||||
* @param mixed $settlement The Treasury bill's settlement date.
|
||||
* The Treasury bill's settlement date is the date after the issue date
|
||||
* when the Treasury bill is traded to the buyer.
|
||||
* @param mixed $maturity The Treasury bill's maturity date.
|
||||
* The maturity date is the date when the Treasury bill expires.
|
||||
* @param mixed $discount The Treasury bill's discount rate
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function price(mixed $settlement, mixed $maturity, mixed $discount): string|float
|
||||
{
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$discount = Functions::flattenSingleValue($discount);
|
||||
|
||||
try {
|
||||
$settlement = FinancialValidations::validateSettlementDate($settlement);
|
||||
$maturity = FinancialValidations::validateMaturityDate($maturity);
|
||||
$discount = FinancialValidations::validateFloat($discount);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($discount <= 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
|
||||
$daysPerYear = Helpers::daysPerYear(
|
||||
Functions::scalar(DateTimeExcel\DateParts::year($maturity)),
|
||||
FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL
|
||||
);
|
||||
|
||||
if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360));
|
||||
if ($price < 0.0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return $price;
|
||||
}
|
||||
|
||||
/**
|
||||
* TBILLYIELD.
|
||||
*
|
||||
* Returns the yield for a Treasury bill.
|
||||
*
|
||||
* @param mixed $settlement The Treasury bill's settlement date.
|
||||
* The Treasury bill's settlement date is the date after the issue date when
|
||||
* the Treasury bill is traded to the buyer.
|
||||
* @param mixed $maturity The Treasury bill's maturity date.
|
||||
* The maturity date is the date when the Treasury bill expires.
|
||||
* @param float|string $price The Treasury bill's price per $100 face value
|
||||
*/
|
||||
public static function yield(mixed $settlement, mixed $maturity, $price): string|float
|
||||
{
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$price = Functions::flattenSingleValue($price);
|
||||
|
||||
try {
|
||||
$settlement = FinancialValidations::validateSettlementDate($settlement);
|
||||
$maturity = FinancialValidations::validateMaturityDate($maturity);
|
||||
$price = FinancialValidations::validatePrice($price);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
|
||||
$daysPerYear = Helpers::daysPerYear(
|
||||
Functions::scalar(DateTimeExcel\DateParts::year($maturity)),
|
||||
FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL
|
||||
);
|
||||
|
||||
if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue