Financial functions next stage of refactoring (#1943)

* First steps splitting out the Amortization and Deprecation Excel functions from Financials
* Verify which methods allow negative values for arguments
* Additional unit tests for SLN() and SYD()
* Additional unit tests for DDB()
* Additional unit tests for DB()
* Verify Amortization cases where salvage is greater than cost
* More unit tests for Amortization
* Resolve broken test in AMORLINC() and extract amortizationCoefficient calculation
* verify amortizationCoefficient calculation
* Extract YIELDDISC() and YIELDMAT() to Financial\Securities
* Additional validation for Securities Yield functions
This commit is contained in:
Mark Baker 2021-03-21 21:40:49 +01:00 committed by GitHub
parent 9beacd21be
commit 5ad5f787ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1225 additions and 421 deletions

View File

@ -273,12 +273,12 @@ class Calculation
],
'AMORDEGRC' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'AMORDEGRC'],
'functionCall' => [Financial\Amortization::class, 'AMORDEGRC'],
'argumentCount' => '6,7',
],
'AMORLINC' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'AMORLINC'],
'functionCall' => [Financial\Amortization::class, 'AMORLINC'],
'argumentCount' => '6,7',
],
'AND' => [
@ -1983,17 +1983,17 @@ class Calculation
],
'PRICE' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Securities::class, 'price'],
'functionCall' => [Financial\Securities\Price::class, 'price'],
'argumentCount' => '6,7',
],
'PRICEDISC' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Securities::class, 'discounted'],
'functionCall' => [Financial\Securities\Price::class, 'priceDiscounted'],
'argumentCount' => '4,5',
],
'PRICEMAT' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial\Securities::class, 'maturity'],
'functionCall' => [Financial\Securities\Price::class, 'priceAtMaturity'],
'argumentCount' => '5,6',
],
'PROB' => [
@ -2225,7 +2225,7 @@ class Calculation
],
'SLN' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'SLN'],
'functionCall' => [Financial\Depreciation::class, 'SLN'],
'argumentCount' => '3',
],
'SLOPE' => [
@ -2356,7 +2356,7 @@ class Calculation
],
'SYD' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'SYD'],
'functionCall' => [Financial\Depreciation::class, 'SYD'],
'argumentCount' => '4',
],
'T' => [
@ -2641,12 +2641,12 @@ class Calculation
],
'YIELDDISC' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'YIELDDISC'],
'functionCall' => [Financial\Securities\Yields::class, 'yieldDiscounted'],
'argumentCount' => '4,5',
],
'YIELDMAT' => [
'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'YIELDMAT'],
'functionCall' => [Financial\Securities\Yields::class, 'yieldAtMaturity'],
'argumentCount' => '5,6',
],
'ZTEST' => [

View File

@ -2,8 +2,13 @@
namespace PhpOffice\PhpSpreadsheet\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Amortization;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Coupons;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Depreciation;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Dollar;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\InterestRate;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\TreasuryBill;
class Financial
{
@ -147,6 +152,10 @@ class Financial
* Excel Function:
* AMORDEGRC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
*
* @Deprecated 1.18.0
*
* @see Use the AMORDEGRC() method in the Financial\Amortization class instead
*
* @param float $cost The 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
@ -164,57 +173,7 @@ class Financial
*/
public static function AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0)
{
$cost = Functions::flattenSingleValue($cost);
$purchased = Functions::flattenSingleValue($purchased);
$firstPeriod = Functions::flattenSingleValue($firstPeriod);
$salvage = Functions::flattenSingleValue($salvage);
$period = floor(Functions::flattenSingleValue($period));
$rate = Functions::flattenSingleValue($rate);
$basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
$yearFrac = DateTime::YEARFRAC($purchased, $firstPeriod, $basis);
if (is_string($yearFrac)) {
return $yearFrac;
}
// 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) {
$amortiseCoeff = 1.0;
} elseif ($fUsePer < 5.0) {
$amortiseCoeff = 1.5;
} elseif ($fUsePer <= 6.0) {
$amortiseCoeff = 2.0;
} else {
$amortiseCoeff = 2.5;
}
$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) {
switch ($period - $n) {
case 0:
case 1:
return round($cost * 0.5, 0);
default:
return 0.0;
}
}
$cost -= $fNRate;
}
return $fNRate;
return Amortization::AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis);
}
/**
@ -227,6 +186,10 @@ class Financial
* Excel Function:
* AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
*
* @Deprecated 1.18.0
*
* @see Use the AMORLINC() method in the Financial\Amortization class instead
*
* @param float $cost The 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
@ -244,39 +207,7 @@ class Financial
*/
public static function AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0)
{
$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) ? 0 : (int) Functions::flattenSingleValue($basis);
$fOneRate = $cost * $rate;
$fCostDelta = $cost - $salvage;
// Note, quirky variation for leap years on the YEARFRAC for this function
$purchasedYear = DateTime::YEAR($purchased);
$yearFrac = DateTime::YEARFRAC($purchased, $firstPeriod, $basis);
if (is_string($yearFrac)) {
return $yearFrac;
}
if (($basis == 1) && ($yearFrac < 1) && (DateTime::isLeapYear($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;
return Amortization::AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis);
}
/**
@ -613,6 +544,10 @@ class Financial
* Excel Function:
* DB(cost,salvage,life,period[,month])
*
* @Deprecated 1.18.0
*
* @see Use the DB() method in the Financial\Depreciation class instead
*
* @param float $cost Initial cost of the asset
* @param float $salvage Value at the end of the depreciation.
* (Sometimes called the salvage value of the asset)
@ -627,46 +562,7 @@ class Financial
*/
public static function DB($cost, $salvage, $life, $period, $month = 12)
{
$cost = Functions::flattenSingleValue($cost);
$salvage = Functions::flattenSingleValue($salvage);
$life = Functions::flattenSingleValue($life);
$period = Functions::flattenSingleValue($period);
$month = Functions::flattenSingleValue($month);
// Validate
if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($month))) {
$cost = (float) $cost;
$salvage = (float) $salvage;
$life = (int) $life;
$period = (int) $period;
$month = (int) $month;
if ($cost == 0) {
return 0.0;
} elseif (($cost < 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($month < 1)) {
return Functions::NAN();
}
// Set Fixed Depreciation Rate
$fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life);
$fixedDepreciationRate = round($fixedDepreciationRate, 3);
// Loop through each period calculating the depreciation
$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;
}
return Functions::VALUE();
return Depreciation::DB($cost, $salvage, $life, $period, $month);
}
/**
@ -678,6 +574,10 @@ class Financial
* Excel Function:
* DDB(cost,salvage,life,period[,factor])
*
* @Deprecated 1.18.0
*
* @see Use the DDB() method in the Financial\Depreciation class instead
*
* @param float $cost Initial cost of the asset
* @param float $salvage Value at the end of the depreciation.
* (Sometimes called the salvage value of the asset)
@ -693,38 +593,7 @@ class Financial
*/
public static function DDB($cost, $salvage, $life, $period, $factor = 2.0)
{
$cost = Functions::flattenSingleValue($cost);
$salvage = Functions::flattenSingleValue($salvage);
$life = Functions::flattenSingleValue($life);
$period = Functions::flattenSingleValue($period);
$factor = Functions::flattenSingleValue($factor);
// Validate
if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($factor))) {
$cost = (float) $cost;
$salvage = (float) $salvage;
$life = (int) $life;
$period = (int) $period;
$factor = (float) $factor;
if (($cost <= 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($factor <= 0.0) || ($period > $life)) {
return Functions::NAN();
}
// Set Fixed Depreciation Rate
$fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life);
$fixedDepreciationRate = round($fixedDepreciationRate, 3);
// Loop through each period calculating the depreciation
$previousDepreciation = 0;
$depreciation = 0;
for ($per = 1; $per <= $period; ++$per) {
$depreciation = min(($cost - $previousDepreciation) * ($factor / $life), ($cost - $salvage - $previousDepreciation));
$previousDepreciation += $depreciation;
}
return $depreciation;
}
return Functions::VALUE();
return Depreciation::DDB($cost, $salvage, $life, $period, $factor);
}
/**
@ -800,7 +669,7 @@ class Financial
*/
public static function DOLLARDE($fractional_dollar = null, $fraction = 0)
{
return Financial\Dollar::decimal($fractional_dollar, $fraction);
return Dollar::decimal($fractional_dollar, $fraction);
}
/**
@ -824,7 +693,7 @@ class Financial
*/
public static function DOLLARFR($decimal_dollar = null, $fraction = 0)
{
return Financial\Dollar::fractional($decimal_dollar, $fraction);
return Dollar::fractional($decimal_dollar, $fraction);
}
/**
@ -1368,7 +1237,7 @@ class Financial
*
* @Deprecated 1.18.0
*
* @see Use the price() method in the Financial\Securities class instead
* @see Use the price() method in the Financial\Securities\Price class instead
*
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue date when the security
@ -1393,7 +1262,7 @@ class Financial
*/
public static function PRICE($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis = 0)
{
return Financial\Securities::price($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis);
return Securities\Price::price($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis);
}
/**
@ -1403,12 +1272,13 @@ class Financial
*
* @Deprecated 1.18.0
*
* @see Use the discounted() method in the Financial\Securities class instead
* @see Use the priceDiscounted() method in the Financial\Securities\Price class instead
*
* @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.
* 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.
* The maturity date is the date when the security expires.
* @param int $discount The security's discount rate
* @param int $redemption The security's redemption value per $100 face value
* @param int $basis The type of day count to use.
@ -1422,7 +1292,7 @@ class Financial
*/
public static function PRICEDISC($settlement, $maturity, $discount, $redemption, $basis = 0)
{
return Financial\Securities::discounted($settlement, $maturity, $discount, $redemption, $basis);
return Securities\Price::priceDiscounted($settlement, $maturity, $discount, $redemption, $basis);
}
/**
@ -1432,12 +1302,13 @@ class Financial
*
* @Deprecated 1.18.0
*
* @see Use the maturity() method in the Financial\Securities class instead
* @see Use the priceAtMaturity() method in the Financial\Securities\Price class instead
*
* @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.
* 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.
* The maturity date is the date when the security expires.
* @param mixed $issue The security's issue date
* @param int $rate The security's interest rate at date of issue
* @param int $yield The security's annual yield
@ -1452,7 +1323,7 @@ class Financial
*/
public static function PRICEMAT($settlement, $maturity, $issue, $rate, $yield, $basis = 0)
{
return Financial\Securities::maturity($settlement, $maturity, $issue, $rate, $yield, $basis);
return Securities\Price::priceAtMaturity($settlement, $maturity, $issue, $rate, $yield, $basis);
}
/**
@ -1640,6 +1511,10 @@ class Financial
*
* Returns the straight-line depreciation of an asset for one period
*
* @Deprecated 1.18.0
*
* @see Use the SLN() method in the Financial\Depreciation class instead
*
* @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
@ -1648,20 +1523,7 @@ class Financial
*/
public static function SLN($cost, $salvage, $life)
{
$cost = Functions::flattenSingleValue($cost);
$salvage = Functions::flattenSingleValue($salvage);
$life = Functions::flattenSingleValue($life);
// Calculate
if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life))) {
if ($life < 0) {
return Functions::NAN();
}
return ($cost - $salvage) / $life;
}
return Functions::VALUE();
return Depreciation::SLN($cost, $salvage, $life);
}
/**
@ -1669,6 +1531,10 @@ class Financial
*
* Returns the sum-of-years' digits depreciation of an asset for a specified period.
*
* @Deprecated 1.18.0
*
* @see Use the SYD() method in the Financial\Depreciation class instead
*
* @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
@ -1678,21 +1544,7 @@ class Financial
*/
public static function SYD($cost, $salvage, $life, $period)
{
$cost = Functions::flattenSingleValue($cost);
$salvage = Functions::flattenSingleValue($salvage);
$life = Functions::flattenSingleValue($life);
$period = Functions::flattenSingleValue($period);
// Calculate
if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period))) {
if (($life < 1) || ($period > $life)) {
return Functions::NAN();
}
return (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
}
return Functions::VALUE();
return Depreciation::SYD($cost, $salvage, $life, $period);
}
/**
@ -1714,7 +1566,7 @@ class Financial
*/
public static function TBILLEQ($settlement, $maturity, $discount)
{
return Financial\TreasuryBill::bondEquivalentYield($settlement, $maturity, $discount);
return TreasuryBill::bondEquivalentYield($settlement, $maturity, $discount);
}
/**
@ -1737,7 +1589,7 @@ class Financial
*/
public static function TBILLPRICE($settlement, $maturity, $discount)
{
return Financial\TreasuryBill::price($settlement, $maturity, $discount);
return TreasuryBill::price($settlement, $maturity, $discount);
}
/**
@ -1760,7 +1612,7 @@ class Financial
*/
public static function TBILLYIELD($settlement, $maturity, $price)
{
return Financial\TreasuryBill::yield($settlement, $maturity, $price);
return TreasuryBill::yield($settlement, $maturity, $price);
}
private static function bothNegAndPos($neg, $pos)
@ -1977,10 +1829,13 @@ class Financial
*
* Returns the annual yield of a security that pays interest at maturity.
*
* @see Use the yieldDiscounted() method in the Financial\Securities\Yields class instead
*
* @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.
* 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.
* The maturity date is the date when the security expires.
* @param int $price The security's price per $100 face value
* @param int $redemption The security's redemption value per $100 face value
* @param int $basis The type of day count to use.
@ -1994,32 +1849,7 @@ class Financial
*/
public static function YIELDDISC($settlement, $maturity, $price, $redemption, $basis = 0)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$price = Functions::flattenSingleValue($price);
$redemption = Functions::flattenSingleValue($redemption);
$basis = (int) Functions::flattenSingleValue($basis);
// Validate
if (is_numeric($price) && is_numeric($redemption)) {
if (($price <= 0) || ($redemption <= 0)) {
return Functions::NAN();
}
$daysPerYear = Financial\Helpers::daysPerYear(DateTime::YEAR($settlement), $basis);
if (!is_numeric($daysPerYear)) {
return $daysPerYear;
}
$daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
}
$daysBetweenSettlementAndMaturity *= $daysPerYear;
return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity);
}
return Functions::VALUE();
return Securities\Yields::yieldDiscounted($settlement, $maturity, $price, $redemption, $basis);
}
/**
@ -2027,10 +1857,15 @@ class Financial
*
* Returns the annual yield of a security that pays interest at maturity.
*
* @Deprecated 1.18.0
*
* @see Use the yieldAtMaturity() method in the Financial\Securities\Yields class instead
*
* @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.
* 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.
* The maturity date is the date when the security expires.
* @param mixed $issue The security's issue date
* @param int $rate The security's interest rate at date of issue
* @param int $price The security's price per $100 face value
@ -2045,46 +1880,6 @@ class Financial
*/
public static function YIELDMAT($settlement, $maturity, $issue, $rate, $price, $basis = 0)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$issue = Functions::flattenSingleValue($issue);
$rate = Functions::flattenSingleValue($rate);
$price = Functions::flattenSingleValue($price);
$basis = (int) Functions::flattenSingleValue($basis);
// Validate
if (is_numeric($rate) && is_numeric($price)) {
if (($rate <= 0) || ($price <= 0)) {
return Functions::NAN();
}
$daysPerYear = Financial\Helpers::daysPerYear(DateTime::YEAR($settlement), $basis);
if (!is_numeric($daysPerYear)) {
return $daysPerYear;
}
$daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
}
$daysBetweenIssueAndSettlement *= $daysPerYear;
$daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis);
if (!is_numeric($daysBetweenIssueAndMaturity)) {
// return date error
return $daysBetweenIssueAndMaturity;
}
$daysBetweenIssueAndMaturity *= $daysPerYear;
$daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($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);
}
return Functions::VALUE();
return Securities\Yields::yieldAtMaturity($settlement, $maturity, $issue, $rate, $price, $basis);
}
}

View File

@ -0,0 +1,163 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
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 float $cost The 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 float $period The period
* @param float $rate Rate of depreciation
* @param int $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 (string containing the error type if there is an error)
*/
public static function AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0)
{
$cost = Functions::flattenSingleValue($cost);
$purchased = Functions::flattenSingleValue($purchased);
$firstPeriod = Functions::flattenSingleValue($firstPeriod);
$salvage = Functions::flattenSingleValue($salvage);
$period = floor(Functions::flattenSingleValue($period));
$rate = Functions::flattenSingleValue($rate);
$basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
$yearFrac = DateTime::YEARFRAC($purchased, $firstPeriod, $basis);
if (is_string($yearFrac)) {
return $yearFrac;
}
$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) {
switch ($period - $n) {
case 0:
case 1:
return round($cost * 0.5, 0);
default:
return 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 float $cost The 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 float $period The period
* @param float $rate Rate of depreciation
* @param int $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 (string containing the error type if there is an error)
*/
public static function AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0)
{
$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) ? 0 : (int) Functions::flattenSingleValue($basis);
$fOneRate = $cost * $rate;
$fCostDelta = $cost - $salvage;
// Note, quirky variation for leap years on the YEARFRAC for this function
$purchasedYear = DateTime::YEAR($purchased);
$yearFrac = DateTime::YEARFRAC($purchased, $firstPeriod, $basis);
if (is_string($yearFrac)) {
return $yearFrac;
}
if (($basis == 1) && ($yearFrac < 1) && (DateTime::isLeapYear($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;
}
}

View File

@ -0,0 +1,287 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Depreciation
{
/**
* 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 float $cost Initial cost of the asset
* @param float $salvage Value at the end of the depreciation.
* (Sometimes called the salvage value of the asset)
* @param int $life Number of periods over which the asset is depreciated.
* (Sometimes called the useful life of the asset)
* @param int $period The period for which you want to calculate the
* depreciation. Period must use the same units as life.
* @param int $month Number of months in the first year. If month is omitted,
* it defaults to 12.
*
* @return float|string
*/
public static function DB($cost, $salvage, $life, $period, $month = 12)
{
$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 === 0.0) {
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 float $cost Initial cost of the asset
* @param float $salvage Value at the end of the depreciation.
* (Sometimes called the salvage value of the asset)
* @param int $life Number of periods over which the asset is depreciated.
* (Sometimes called the useful life of the asset)
* @param int $period The period for which you want to calculate the
* depreciation. Period must use the same units as life.
* @param float $factor The rate at which the balance declines.
* If factor is omitted, it is assumed to be 2 (the
* double-declining balance method).
*
* @return float|string
*/
public static function DDB($cost, $salvage, $life, $period, $factor = 2.0)
{
$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 Functions::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($cost, $salvage, $life)
{
$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 === 0.0) {
return Functions::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($cost, $salvage, $life, $period)
{
$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 Functions::NAN();
}
$syd = (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
return $syd;
}
private static function validateCost($cost, bool $negativeValueAllowed = false): float
{
if (!is_numeric($cost)) {
throw new Exception(Functions::VALUE());
}
$cost = (float) $cost;
if ($cost < 0.0 && $negativeValueAllowed === false) {
throw new Exception(Functions::NAN());
}
return $cost;
}
private static function validateSalvage($salvage, bool $negativeValueAllowed = false): float
{
if (!is_numeric($salvage)) {
throw new Exception(Functions::VALUE());
}
$salvage = (float) $salvage;
if ($salvage < 0.0 && $negativeValueAllowed === false) {
throw new Exception(Functions::NAN());
}
return $salvage;
}
private static function validateLife($life, bool $negativeValueAllowed = false): float
{
if (!is_numeric($life)) {
throw new Exception(Functions::VALUE());
}
$life = (float) $life;
if ($life < 0.0 && $negativeValueAllowed === false) {
throw new Exception(Functions::NAN());
}
return $life;
}
private static function validatePeriod($period, bool $negativeValueAllowed = false): float
{
if (!is_numeric($period)) {
throw new Exception(Functions::VALUE());
}
$period = (float) $period;
if ($period <= 0.0 && $negativeValueAllowed === false) {
throw new Exception(Functions::NAN());
}
return $period;
}
private static function validateMonth($month): int
{
if (!is_numeric($month)) {
throw new Exception(Functions::VALUE());
}
$month = (int) $month;
if ($month < 1) {
throw new Exception(Functions::NAN());
}
return $month;
}
private static function validateFactor($factor): float
{
if (!is_numeric($factor)) {
throw new Exception(Functions::VALUE());
}
$factor = (float) $factor;
if ($factor <= 0.0) {
throw new Exception(Functions::NAN());
}
return $factor;
}
}

View File

@ -0,0 +1,145 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities\Constants as SecuritiesConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
abstract class BaseValidations
{
protected static function validateInputDate($date)
{
$date = DateTime::getDateValue($date);
if (is_string($date)) {
throw new Exception(Functions::VALUE());
}
return $date;
}
protected static function validateSettlementDate($settlement)
{
return self::validateInputDate($settlement);
}
protected static function validateMaturityDate($maturity)
{
return self::validateInputDate($maturity);
}
protected static function validateIssueDate($issue)
{
return self::validateInputDate($issue);
}
protected static function validateSecurityPeriod($settlement, $maturity): void
{
if ($settlement >= $maturity) {
throw new Exception(Functions::NAN());
}
}
protected static function validateRate($rate): float
{
if (!is_numeric($rate)) {
throw new Exception(Functions::VALUE());
}
$rate = (float) $rate;
if ($rate < 0.0) {
throw new Exception(Functions::NAN());
}
return $rate;
}
protected static function validatePrice($price): float
{
if (!is_numeric($price)) {
throw new Exception(Functions::VALUE());
}
$price = (float) $price;
if ($price < 0.0) {
throw new Exception(Functions::NAN());
}
return $price;
}
protected static function validateYield($yield): float
{
if (!is_numeric($yield)) {
throw new Exception(Functions::VALUE());
}
$yield = (float) $yield;
if ($yield < 0.0) {
throw new Exception(Functions::NAN());
}
return $yield;
}
protected static function validateRedemption($redemption): float
{
if (!is_numeric($redemption)) {
throw new Exception(Functions::VALUE());
}
$redemption = (float) $redemption;
if ($redemption <= 0.0) {
throw new Exception(Functions::NAN());
}
return $redemption;
}
protected static function validateDiscount($discount): float
{
if (!is_numeric($discount)) {
throw new Exception(Functions::VALUE());
}
$discount = (float) $discount;
if ($discount <= 0.0) {
throw new Exception(Functions::NAN());
}
return $discount;
}
protected static function validateFrequency($frequency): int
{
if (!is_numeric($frequency)) {
throw new Exception(Functions::VALUE());
}
$frequency = (int) $frequency;
if (
($frequency !== SecuritiesConstants::FREQUENCY_ANNUAL) &&
($frequency !== SecuritiesConstants::FREQUENCY_SEMI_ANNUAL) &&
($frequency !== SecuritiesConstants::FREQUENCY_QUARTERLY)
) {
throw new Exception(Functions::NAN());
}
return $frequency;
}
protected static function validateBasis($basis): int
{
if (!is_numeric($basis)) {
throw new Exception(Functions::VALUE());
}
$basis = (int) $basis;
if (($basis < 0) || ($basis > 4)) {
throw new Exception(Functions::NAN());
}
return $basis;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
class Constants
{
public const FREQUENCY_ANNUAL = 1;
public const FREQUENCY_SEMI_ANNUAL = 2;
public const FREQUENCY_QUARTERLY = 4;
}

View File

@ -1,17 +1,15 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Coupons;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Securities
class Price extends BaseValidations
{
public const FREQUENCY_ANNUAL = 1;
public const FREQUENCY_SEMI_ANNUAL = 2;
public const FREQUENCY_QUARTERLY = 4;
/**
* PRICE.
*
@ -100,7 +98,7 @@ class Securities
*
* @return float|string Result, or a string containing an error
*/
public static function discounted($settlement, $maturity, $discount, $redemption, $basis = 0)
public static function priceDiscounted($settlement, $maturity, $discount, $redemption, $basis = 0)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
@ -150,7 +148,7 @@ class Securities
*
* @return float|string Result, or a string containing an error
*/
public static function maturity($settlement, $maturity, $issue, $rate, $yield, $basis = 0)
public static function priceAtMaturity($settlement, $maturity, $issue, $rate, $yield, $basis = 0)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
@ -198,124 +196,4 @@ class Securities
(1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) -
(($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100);
}
private static function validateInputDate($date)
{
$date = DateTime::getDateValue($date);
if (is_string($date)) {
throw new Exception(Functions::VALUE());
}
return $date;
}
private static function validateSettlementDate($settlement)
{
return self::validateInputDate($settlement);
}
private static function validateMaturityDate($maturity)
{
return self::validateInputDate($maturity);
}
private static function validateIssueDate($issue)
{
return self::validateInputDate($issue);
}
private static function validateSecurityPeriod($settlement, $maturity): void
{
if ($settlement >= $maturity) {
throw new Exception(Functions::NAN());
}
}
private static function validateRate($rate): float
{
if (!is_numeric($rate)) {
throw new Exception(Functions::VALUE());
}
$rate = (float) $rate;
if ($rate < 0.0) {
throw new Exception(Functions::NAN());
}
return $rate;
}
private static function validateYield($yield): float
{
if (!is_numeric($yield)) {
throw new Exception(Functions::VALUE());
}
$yield = (float) $yield;
if ($yield < 0.0) {
throw new Exception(Functions::NAN());
}
return $yield;
}
private static function validateRedemption($redemption): float
{
if (!is_numeric($redemption)) {
throw new Exception(Functions::VALUE());
}
$redemption = (float) $redemption;
if ($redemption <= 0.0) {
throw new Exception(Functions::NAN());
}
return $redemption;
}
private static function validateDiscount($discount): float
{
if (!is_numeric($discount)) {
throw new Exception(Functions::VALUE());
}
$discount = (float) $discount;
if ($discount <= 0.0) {
throw new Exception(Functions::NAN());
}
return $discount;
}
private static function validateFrequency($frequency): int
{
if (!is_numeric($frequency)) {
throw new Exception(Functions::VALUE());
}
$frequency = (int) $frequency;
if (
($frequency !== self::FREQUENCY_ANNUAL) &&
($frequency !== self::FREQUENCY_SEMI_ANNUAL) &&
($frequency !== self::FREQUENCY_QUARTERLY)
) {
throw new Exception(Functions::NAN());
}
return $frequency;
}
private static function validateBasis($basis): int
{
if (!is_numeric($basis)) {
throw new Exception(Functions::VALUE());
}
$basis = (int) $basis;
if (($basis < 0) || ($basis > 4)) {
throw new Exception(Functions::NAN());
}
return $basis;
}
}

View File

@ -0,0 +1,136 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Yields extends BaseValidations
{
/**
* 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 int $price The security's price per $100 face value
* @param int $redemption The security's redemption value per $100 face value
* @param int $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($settlement, $maturity, $price, $redemption, $basis = 0)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$price = Functions::flattenSingleValue($price);
$redemption = Functions::flattenSingleValue($redemption);
$basis = (int) Functions::flattenSingleValue($basis);
try {
$settlement = self::validateSettlementDate($settlement);
$maturity = self::validateMaturityDate($maturity);
self::validateSecurityPeriod($settlement, $maturity);
$price = self::validatePrice($price);
$redemption = self::validateRedemption($redemption);
$basis = self::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$daysPerYear = Helpers::daysPerYear(DateTime::YEAR($settlement), $basis);
if (!is_numeric($daysPerYear)) {
return $daysPerYear;
}
$daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($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 int $rate The security's interest rate at date of issue
* @param int $price The security's price per $100 face value
* @param int $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($settlement, $maturity, $issue, $rate, $price, $basis = 0)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$issue = Functions::flattenSingleValue($issue);
$rate = Functions::flattenSingleValue($rate);
$price = Functions::flattenSingleValue($price);
$basis = Functions::flattenSingleValue($basis);
try {
$settlement = self::validateSettlementDate($settlement);
$maturity = self::validateMaturityDate($maturity);
self::validateSecurityPeriod($settlement, $maturity);
$issue = self::validateIssueDate($issue);
$rate = self::validateRate($rate);
$price = self::validatePrice($price);
$basis = self::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$daysPerYear = Helpers::daysPerYear(DateTime::YEAR($settlement), $basis);
if (!is_numeric($daysPerYear)) {
return $daysPerYear;
}
$daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
}
$daysBetweenIssueAndSettlement *= $daysPerYear;
$daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis);
if (!is_numeric($daysBetweenIssueAndMaturity)) {
// return date error
return $daysBetweenIssueAndMaturity;
}
$daysBetweenIssueAndMaturity *= $daysPerYear;
$daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($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);
}
}

View File

@ -7,6 +7,30 @@ return [
776,
2400, '2008-08-19', '2008-12-31', 300, 1, 0.15, 1,
],
[
820,
2400, '2008-08-19', '2008-12-31', 300, 1, 0.2, 1,
],
[
492,
2400, '2008-08-19', '2008-12-31', 300, 2, 0.2, 1,
],
[
886,
2400, '2008-08-19', '2008-12-31', 300, 1, 0.22, 1,
],
[
949,
2400, '2008-08-19', '2008-12-31', 300, 1, 0.24, 1,
],
[
494,
2400, '2008-08-19', '2008-12-31', 300, 2, 0.24, 1,
],
[
902,
2400, '2008-08-19', '2008-12-31', 300, 1, 0.3, 1,
],
[
42,
150, '2011-01-01', '2011-09-30', 20, 1, 0.2, 4,
@ -27,4 +51,8 @@ return [
'#VALUE!',
550, 'notADate', '2020-12-25', 20, 1, 0.2, 4,
],
[
'#VALUE!',
550, '2011-01-01', 'notADate', 20, 1, 0.2, 4,
],
];

View File

@ -5,26 +5,30 @@
return [
[
360,
2400, '2008-08-19', '2008-12-31', 300, 1, 0.14999999999999999, 1,
2400, '2008-08-19', '2008-12-31', 300, 1, 0.15, 1,
],
[
576,
2400, '2008-08-19', '2008-12-31', 300, 2, 0.24, 1,
],
[
30,
150, '2011-01-01', '2011-09-30', 20, 1, 0.20000000000000001, 4,
150, '2011-01-01', '2011-09-30', 20, 1, 0.2, 4,
],
[
22.41666667,
150, '2011-01-01', '2011-09-30', 20, 0, 0.20000000000000001, 4,
150, '2011-01-01', '2011-09-30', 20, 0, 0.2, 4,
],
[
17.58333333,
150, '2011-01-01', '2011-09-30', 20, 4, 0.20000000000000001, 4,
150, '2011-01-01', '2011-09-30', 20, 4, 0.2, 4,
],
[
0.0,
150, '2011-01-01', '2011-09-30', 20, 5, 0.20000000000000001, 4,
150, '2011-01-01', '2011-09-30', 20, 5, 0.2, 4,
],
[
'#VALUE!',
150, 'notADate', '2011-09-30', 20, 1, 0.20000000000000001, 4,
150, 'notADate', '2011-09-30', 20, 1, 0.2, 4,
],
];

View File

@ -115,6 +115,47 @@ return [
6,
6,
],
[
4651.199,
12000,
2000,
3.5,
2,
1,
],
[
3521.399,
12000,
2000,
5,
2.5,
1,
],
[
3521.399,
12000,
2000,
5,
2.5,
1.2,
],
// Period value between 0 and 1 not yet handled in code
// [
// 301.0,
// 12000,
// 2000,
// 5,
// 0.5,
// 1,
// ],
[
-554.116,
12000,
15000,
5,
2,
1,
],
[
'#NUM!',
-1000,
@ -125,10 +166,82 @@ return [
],
[
'#VALUE!',
'ABC',
100,
'Invalid',
1000,
5,
6,
6,
2,
1,
],
[
'#VALUE!',
12000,
'Invalid',
5,
2,
1,
],
[
'#VALUE!',
12000,
1000,
'Invalid',
2,
1,
],
[
'#VALUE!',
12000,
1000,
5,
'Invalid',
1,
],
[
'#VALUE!',
12000,
1000,
5,
2,
'Invalid',
],
[
'#NUM!',
-12000,
1000,
5,
2,
1,
],
[
'#NUM!',
12000,
-1000,
5,
2,
1,
],
[
'#NUM!',
12000,
1000,
5,
0,
1,
],
[
'#NUM!',
12000,
1000,
5,
-2,
1,
],
[
'#NUM!',
12000,
1000,
5,
2,
0,
],
];

View File

@ -98,6 +98,47 @@ return [
5,
5,
],
[
972.0,
12000,
1000,
5,
3,
0.5,
],
[
1259.4752186588921,
12000,
1000,
3.5,
3,
0.5,
],
[
1080.00,
12000,
1000,
5,
2,
0.5,
],
[
0.0,
12000,
15000,
5,
2,
0.5,
],
// Code does not yet handle fractional period values for DDB, only integer
// [
// 1024.58,
// 12000,
// 1000,
// 5,
// 2.5,
// 0.5,
// ],
[
'#NUM!',
-2400,
@ -112,4 +153,76 @@ return [
36500,
1,
],
[
'#VALUE!',
12000,
'INVALID',
5,
3,
0.5,
],
[
'#VALUE!',
12000,
1000,
'INVALID',
3,
0.5,
],
[
'#VALUE!',
12000,
1000,
5,
'INVALID',
0.5,
],
[
'#VALUE!',
12000,
1000,
5,
3,
'INVALID',
],
[
'#NUM!',
12000,
-1000,
5,
3,
0.5,
],
[
'#NUM!',
12000,
1000,
5,
-3,
0.5,
],
[
'#NUM!',
12000,
1000,
5,
3,
-0.5,
],
[
'#NUM!',
12000,
1000,
5,
0,
0.5,
],
[
'#NUM!',
12000,
1000,
2,
3,
0.5,
],
];

View File

@ -30,11 +30,39 @@ return [
[45000, 7500, 10],
],
[
'#NUM!',
[10000, 1000, -1],
-10500,
[12000, 1500, -1],
],
[
21000,
[12000, 1500, 0.5],
],
[
3250,
[12000, -1000, 4],
],
[
-250,
[0, 1000, 4],
],
[
-600,
[12000, 15000, 5],
],
[
'#DIV/0!',
[12000, 1500, 0],
],
[
'#VALUE!',
['INVALID', 1000, -1],
['INVALID', 1000, 1],
],
[
'#VALUE!',
[12000, 'INVALID', 1],
],
[
'#VALUE!',
[12000, 1000, 'INVALID'],
],
];

View File

@ -33,6 +33,30 @@ return [
409.09090909090907,
[30000, 7500, 10, 10],
],
[
-800,
[-2000, 1000, 5, 2],
],
[
3771.4285714285716,
[12000, 1000, 2.5, 2],
],
[
5028.571428571428,
[12000, 1000, 2.5, 1.5],
],
[
-600,
[-2000, 1000, 5, 3],
],
[
-800,
[12000, 15000, 5, 2],
],
[
'#NUM!',
[12000, -1000, 5, 3],
],
[
'#NUM!',
[10000, 1000, 5, 10],
@ -41,4 +65,32 @@ return [
'#VALUE!',
['INVALID', 1000, 5, 1],
],
[
'#VALUE!',
[12000, 'INVALID', 5, 1],
],
[
'#VALUE!',
[12000, 1000, 'INVALID', 1],
],
[
'#VALUE!',
[12000, 1000, 5, 'INVALID'],
],
[
'#NUM!',
[12000, -1, 5, 2],
],
[
'#NUM!',
[12000, 1000, -5, 1],
],
[
'#NUM!',
[12000, 1000, 5, 0],
],
[
'#NUM!',
[12000, 1000, 5, -1],
],
];

View File

@ -9,4 +9,28 @@ return [
0.06220123250590336,
'1-Jan-2017', '30-Jun-2017', 97, 100,
],
[
'#VALUE!',
'Invalid', '30-Jun-2017', 97, 100,
],
[
'#VALUE!',
'1-Jan-2017', 'Invalid', 97, 100,
],
[
'#VALUE!',
'1-Jan-2017', '30-Jun-2017', 'NaN', 100,
],
[
'#VALUE!',
'1-Jan-2017', '30-Jun-2017', 97, 'NaN',
],
[
'#NUM!',
'1-Jan-2017', '30-Jun-2017', -97, 100,
],
[
'#NUM!',
'1-Jan-2017', '30-Jun-2017', 97, -100,
],
];

View File

@ -9,4 +9,32 @@ return [
0.04210977320221025,
'1-Jan-2017', '30-Jun-2018', '01-Jul-2014', 0.055, 101,
],
[
'#VALUE!',
'Invalid', '30-Jun-2018', '01-Jul-2014', 0.055, 101,
],
[
'#VALUE!',
'1-Jan-2017', 'Invalid', '01-Jul-2014', 0.055, 101,
],
[
'#VALUE!',
'1-Jan-2017', '30-Jun-2018', 'Invalid', 0.055, 101,
],
[
'#VALUE!',
'1-Jan-2017', '30-Jun-2018', '01-Jul-2014', 'NaN', 101,
],
[
'#VALUE!',
'1-Jan-2017', '30-Jun-2018', '01-Jul-2014', 0.055, 'NaN',
],
[
'#NUM!',
'1-Jan-2017', '30-Jun-2018', '01-Jul-2014', -0.055, 101,
],
[
'#NUM!',
'1-Jan-2017', '30-Jun-2018', '01-Jul-2014', 0.055, -101,
],
];