Completed
Push — master ( 8eaceb...9ae521 )
by Adrien
86:33 queued 22:09
created

Financial::COUPPCD()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
eloc 13
nc 8
nop 4
dl 0
loc 21
ccs 0
cts 1
cp 0
crap 72
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation;
4
5
use PhpOffice\PhpSpreadsheet\Shared\Date;
6
7
class Financial
8
{
9
    const FINANCIAL_MAX_ITERATIONS = 128;
10
11
    const FINANCIAL_PRECISION = 1.0e-08;
12
13
    /**
14
     * isLastDayOfMonth.
15
     *
16
     * Returns a boolean TRUE/FALSE indicating if this date is the last date of the month
17
     *
18
     * @param \DateTime $testDate The date for testing
19
     *
20
     * @return bool
21
     */
22 15
    private static function isLastDayOfMonth(\DateTime $testDate)
23
    {
24 15
        return $testDate->format('d') == $testDate->format('t');
25
    }
26
27 15
    private static function couponFirstPeriodDate($settlement, $maturity, $frequency, $next)
28
    {
29 15
        $months = 12 / $frequency;
30
31 15
        $result = Date::excelToDateTimeObject($maturity);
32 15
        $eom = self::isLastDayOfMonth($result);
33
34 15
        while ($settlement < Date::PHPToExcel($result)) {
35 15
            $result->modify('-' . $months . ' months');
36
        }
37 15
        if ($next) {
38 8
            $result->modify('+' . $months . ' months');
39
        }
40
41 15
        if ($eom) {
42
            $result->modify('-1 day');
43
        }
44
45 15
        return Date::PHPToExcel($result);
46
    }
47
48 31
    private static function isValidFrequency($frequency)
49
    {
50 31
        if (($frequency == 1) || ($frequency == 2) || ($frequency == 4)) {
51 25
            return true;
52
        }
53 6
54 6
        return false;
55
    }
56
57
    /**
58 6
     * daysPerYear.
59
     *
60
     * Returns the number of days in a specified year, as defined by the "basis" value
61
     *
62
     * @param int|string $year The year against which we're testing
63
     * @param int|string $basis The type of day count:
64
     *                                    0 or omitted US (NASD)    360
65
     *                                    1                        Actual (365 or 366 in a leap year)
66
     *                                    2                        360
67
     *                                    3                        365
68
     *                                    4                        European 360
69
     *
70
     * @return int|string Result, or a string containing an error
71
     */
72
    private static function daysPerYear($year, $basis = 0)
73
    {
74
        switch ($basis) {
75
            case 0:
76 12
            case 2:
77
            case 4:
78
                $daysPerYear = 360;
79 12
80 6
                break;
81 6
            case 3:
82 6
                $daysPerYear = 365;
83
84 6
                break;
85 6
            case 1:
86
                $daysPerYear = (DateTime::isLeapYear($year)) ? 366 : 365;
87
88
                break;
89 6
            default:
90 6
                return Functions::NAN();
91
        }
92 6
93
        return $daysPerYear;
94
    }
95
96
    private static function interestAndPrincipal($rate = 0, $per = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0)
97 12
    {
98
        $pmt = self::PMT($rate, $nper, $pv, $fv, $type);
99
        $capital = $pv;
100 20
        for ($i = 1; $i <= $per; ++$i) {
101
            $interest = ($type && $i == 1) ? 0 : -$capital * $rate;
102 20
            $principal = $pmt - $interest;
103 20
            $capital += $principal;
104 20
        }
105 20
106 20
        return [$interest, $principal];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $principal does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $interest does not seem to be defined for all execution paths leading up to this point.
Loading history...
107 20
    }
108
109
    /**
110 20
     * ACCRINT.
111
     *
112
     * Returns the accrued interest for a security that pays periodic interest.
113
     *
114
     * Excel Function:
115
     *        ACCRINT(issue,firstinterest,settlement,rate,par,frequency[,basis])
116
     *
117
     * @category Financial Functions
118
     *
119
     * @param mixed $issue the security's issue date
120
     * @param mixed $firstinterest the security's first interest date
121
     * @param mixed $settlement The security's settlement date.
122
     *                                    The security settlement date is the date after the issue date
123
     *                                    when the security is traded to the buyer.
124
     * @param float $rate the security's annual coupon rate
125
     * @param float $par The security's par value.
126
     *                                    If you omit par, ACCRINT uses $1,000.
127
     * @param int $frequency the number of coupon payments per year.
128
     *                                    Valid frequency values are:
129
     *                                        1    Annual
130
     *                                        2    Semi-Annual
131
     *                                        4    Quarterly
132
     * @param int $basis The type of day count to use.
133
     *                                        0 or omitted    US (NASD) 30/360
134
     *                                        1                Actual/actual
135
     *                                        2                Actual/360
136
     *                                        3                Actual/365
137
     *                                        4                European 30/360
138
     *
139
     * @return float|string Result, or a string containing an error
140
     */
141
    public static function ACCRINT($issue, $firstinterest, $settlement, $rate, $par = 1000, $frequency = 1, $basis = 0)
142
    {
143
        $issue = Functions::flattenSingleValue($issue);
144
        $firstinterest = Functions::flattenSingleValue($firstinterest);
0 ignored issues
show
Unused Code introduced by
The assignment to $firstinterest is dead and can be removed.
Loading history...
145
        $settlement = Functions::flattenSingleValue($settlement);
146
        $rate = Functions::flattenSingleValue($rate);
147
        $par = ($par === null) ? 1000 : Functions::flattenSingleValue($par);
0 ignored issues
show
introduced by
The condition $par === null is always false.
Loading history...
148
        $frequency = ($frequency === null) ? 1 : Functions::flattenSingleValue($frequency);
0 ignored issues
show
Unused Code introduced by
The assignment to $frequency is dead and can be removed.
Loading history...
introduced by
The condition $frequency === null is always false.
Loading history...
149 7
        $basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
150
151 7
        //    Validate
152 7
        if ((is_numeric($rate)) && (is_numeric($par))) {
153 7
            $rate = (float) $rate;
154 7
            $par = (float) $par;
155 7
            if (($rate <= 0) || ($par <= 0)) {
156 7
                return Functions::NAN();
157 7
            }
158
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
159
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
160 7
                //    return date error
161 6
                return $daysBetweenIssueAndSettlement;
162 6
            }
163 6
164 1
            return $par * $rate * $daysBetweenIssueAndSettlement;
165
        }
166 5
167 5
        return Functions::VALUE();
168
    }
169 2
170
    /**
171
     * ACCRINTM.
172 3
     *
173
     * Returns the accrued interest for a security that pays interest at maturity.
174
     *
175 1
     * Excel Function:
176
     *        ACCRINTM(issue,settlement,rate[,par[,basis]])
177
     *
178
     * @category Financial Functions
179
     *
180
     * @param mixed $issue The security's issue date
181
     * @param mixed $settlement The security's settlement (or maturity) date
182
     * @param float $rate The security's annual coupon rate
183
     * @param float $par The security's par value.
184
     *                                    If you omit par, ACCRINT uses $1,000.
185
     * @param int $basis The type of day count to use.
186
     *                                        0 or omitted    US (NASD) 30/360
187
     *                                        1                Actual/actual
188
     *                                        2                Actual/360
189
     *                                        3                Actual/365
190
     *                                        4                European 30/360
191
     *
192
     * @return float|string Result, or a string containing an error
193
     */
194
    public static function ACCRINTM($issue, $settlement, $rate, $par = 1000, $basis = 0)
195
    {
196
        $issue = Functions::flattenSingleValue($issue);
197
        $settlement = Functions::flattenSingleValue($settlement);
198
        $rate = Functions::flattenSingleValue($rate);
199
        $par = ($par === null) ? 1000 : Functions::flattenSingleValue($par);
0 ignored issues
show
introduced by
The condition $par === null is always false.
Loading history...
200
        $basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
201
202 5
        //    Validate
203
        if ((is_numeric($rate)) && (is_numeric($par))) {
204 5
            $rate = (float) $rate;
205 5
            $par = (float) $par;
206 5
            if (($rate <= 0) || ($par <= 0)) {
207 5
                return Functions::NAN();
208 5
            }
209
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
210
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
211 5
                //    return date error
212 4
                return $daysBetweenIssueAndSettlement;
213 4
            }
214 4
215 1
            return $par * $rate * $daysBetweenIssueAndSettlement;
216
        }
217 3
218 3
        return Functions::VALUE();
219
    }
220 1
221
    /**
222
     * AMORDEGRC.
223 2
     *
224
     * Returns the depreciation for each accounting period.
225
     * This function is provided for the French accounting system. If an asset is purchased in
226 1
     * the middle of the accounting period, the prorated depreciation is taken into account.
227
     * The function is similar to AMORLINC, except that a depreciation coefficient is applied in
228
     * the calculation depending on the life of the assets.
229
     * This function will return the depreciation until the last period of the life of the assets
230
     * or until the cumulated value of depreciation is greater than the cost of the assets minus
231
     * the salvage value.
232
     *
233
     * Excel Function:
234
     *        AMORDEGRC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
235
     *
236
     * @category Financial Functions
237
     *
238
     * @param float $cost The cost of the asset
239
     * @param mixed $purchased Date of the purchase of the asset
240
     * @param mixed $firstPeriod Date of the end of the first period
241
     * @param mixed $salvage The salvage value at the end of the life of the asset
242
     * @param float $period The period
243
     * @param float $rate Rate of depreciation
244
     * @param int $basis The type of day count to use.
245
     *                                        0 or omitted    US (NASD) 30/360
246
     *                                        1                Actual/actual
247
     *                                        2                Actual/360
248
     *                                        3                Actual/365
249
     *                                        4                European 30/360
250
     *
251
     * @return float
252
     */
253
    public static function AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0)
254
    {
255
        $cost = Functions::flattenSingleValue($cost);
256
        $purchased = Functions::flattenSingleValue($purchased);
257
        $firstPeriod = Functions::flattenSingleValue($firstPeriod);
258
        $salvage = Functions::flattenSingleValue($salvage);
259
        $period = floor(Functions::flattenSingleValue($period));
260
        $rate = Functions::flattenSingleValue($rate);
261 2
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
262
263 2
        //    The depreciation coefficients are:
264 2
        //    Life of assets (1/rate)        Depreciation coefficient
265 2
        //    Less than 3 years            1
266 2
        //    Between 3 and 4 years        1.5
267 2
        //    Between 5 and 6 years        2
268 2
        //    More than 6 years            2.5
269 2
        $fUsePer = 1.0 / $rate;
270
        if ($fUsePer < 3.0) {
271
            $amortiseCoeff = 1.0;
272
        } elseif ($fUsePer < 5.0) {
273
            $amortiseCoeff = 1.5;
274
        } elseif ($fUsePer <= 6.0) {
275
            $amortiseCoeff = 2.0;
276
        } else {
277 2
            $amortiseCoeff = 2.5;
278 2
        }
279
280 2
        $rate *= $amortiseCoeff;
281
        $fNRate = round(DateTime::YEARFRAC($purchased, $firstPeriod, $basis) * $rate * $cost, 0);
282 2
        $cost -= $fNRate;
283 1
        $fRest = $cost - $salvage;
284
285 1
        for ($n = 0; $n < $period; ++$n) {
286
            $fNRate = round($rate * $cost, 0);
287
            $fRest -= $fNRate;
288 2
289 2
            if ($fRest < 0.0) {
290 2
                switch ($period - $n) {
291 2
                    case 0:
292
                    case 1:
293 2
                        return round($cost * 0.5, 0);
294 2
                    default:
295 2
                        return 0.0;
296
                }
297 2
            }
298
            $cost -= $fNRate;
299
        }
300
301
        return $fNRate;
302
    }
303
304
    /**
305
     * AMORLINC.
306 2
     *
307
     * Returns the depreciation for each accounting period.
308
     * This function is provided for the French accounting system. If an asset is purchased in
309 2
     * the middle of the accounting period, the prorated depreciation is taken into account.
310
     *
311
     * Excel Function:
312
     *        AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
313
     *
314
     * @category Financial Functions
315
     *
316
     * @param float $cost The cost of the asset
317
     * @param mixed $purchased Date of the purchase of the asset
318
     * @param mixed $firstPeriod Date of the end of the first period
319
     * @param mixed $salvage The salvage value at the end of the life of the asset
320
     * @param float $period The period
321
     * @param float $rate Rate of depreciation
322
     * @param int $basis The type of day count to use.
323
     *                                        0 or omitted    US (NASD) 30/360
324
     *                                        1                Actual/actual
325
     *                                        2                Actual/360
326
     *                                        3                Actual/365
327
     *                                        4                European 30/360
328
     *
329
     * @return float
330
     */
331
    public static function AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0)
332
    {
333
        $cost = Functions::flattenSingleValue($cost);
334
        $purchased = Functions::flattenSingleValue($purchased);
335
        $firstPeriod = Functions::flattenSingleValue($firstPeriod);
336
        $salvage = Functions::flattenSingleValue($salvage);
337
        $period = Functions::flattenSingleValue($period);
338
        $rate = Functions::flattenSingleValue($rate);
339 2
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
340
341 2
        $fOneRate = $cost * $rate;
342 2
        $fCostDelta = $cost - $salvage;
343 2
        //    Note, quirky variation for leap years on the YEARFRAC for this function
344 2
        $purchasedYear = DateTime::YEAR($purchased);
345 2
        $yearFrac = DateTime::YEARFRAC($purchased, $firstPeriod, $basis);
346 2
347 2
        if (($basis == 1) && ($yearFrac < 1) && (DateTime::isLeapYear($purchasedYear))) {
348
            $yearFrac *= 365 / 366;
349 2
        }
350 2
351
        $f0Rate = $yearFrac * $rate * $cost;
352 2
        $nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate);
353 2
354
        if ($period == 0) {
355 2
            return $f0Rate;
356 1
        } elseif ($period <= $nNumOfFullPeriods) {
357
            return $fOneRate;
358
        } elseif ($period == ($nNumOfFullPeriods + 1)) {
359 2
            return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate;
360 2
        }
361
362 2
        return 0.0;
363
    }
364 2
365 2
    /**
366
     * COUPDAYBS.
367
     *
368
     * Returns the number of days from the beginning of the coupon period to the settlement date.
369
     *
370
     * Excel Function:
371
     *        COUPDAYBS(settlement,maturity,frequency[,basis])
372
     *
373
     * @category Financial Functions
374
     *
375
     * @param mixed $settlement The security's settlement date.
376
     *                                The security settlement date is the date after the issue
377
     *                                date when the security is traded to the buyer.
378
     * @param mixed $maturity The security's maturity date.
379
     *                                The maturity date is the date when the security expires.
380
     * @param int $frequency the number of coupon payments per year.
381
     *                                    Valid frequency values are:
382
     *                                        1    Annual
383
     *                                        2    Semi-Annual
384
     *                                        4    Quarterly
385
     * @param int $basis The type of day count to use.
386
     *                                        0 or omitted    US (NASD) 30/360
387
     *                                        1                Actual/actual
388
     *                                        2                Actual/360
389
     *                                        3                Actual/365
390
     *                                        4                European 30/360
391
     *
392
     * @return float|string
393
     */
394
    public static function COUPDAYBS($settlement, $maturity, $frequency, $basis = 0)
395
    {
396
        $settlement = Functions::flattenSingleValue($settlement);
397
        $maturity = Functions::flattenSingleValue($maturity);
398
        $frequency = (int) Functions::flattenSingleValue($frequency);
399
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
400
401
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
402
            return Functions::VALUE();
403
        }
404
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
405
            return Functions::VALUE();
406 5
        }
407
408 5
        if (($settlement >= $maturity) ||
409 5
            (!self::isValidFrequency($frequency)) ||
410 5
            (($basis < 0) || ($basis > 4))) {
411 5
            return Functions::NAN();
412
        }
413 5
414 1
        $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
415
        $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, false);
416 4
417 1
        if ($basis == 1) {
418
            return abs(DateTime::DAYS($prev, $settlement));
419
        }
420 3
421 3
        return DateTime::YEARFRAC($prev, $settlement, $basis) * $daysPerYear;
422 3
    }
423 1
424
    /**
425
     * COUPDAYS.
426 2
     *
427 2
     * Returns the number of days in the coupon period that contains the settlement date.
428
     *
429 2
     * Excel Function:
430
     *        COUPDAYS(settlement,maturity,frequency[,basis])
431
     *
432
     * @category Financial Functions
433
     *
434
     * @param mixed $settlement The security's settlement date.
435
     *                                The security settlement date is the date after the issue
436
     *                                date when the security is traded to the buyer.
437
     * @param mixed $maturity The security's maturity date.
438
     *                                The maturity date is the date when the security expires.
439
     * @param mixed $frequency the number of coupon payments per year.
440
     *                                    Valid frequency values are:
441
     *                                        1    Annual
442
     *                                        2    Semi-Annual
443
     *                                        4    Quarterly
444
     * @param int $basis The type of day count to use.
445
     *                                        0 or omitted    US (NASD) 30/360
446
     *                                        1                Actual/actual
447
     *                                        2                Actual/360
448
     *                                        3                Actual/365
449
     *                                        4                European 30/360
450
     *
451
     * @return float|string
452
     */
453
    public static function COUPDAYS($settlement, $maturity, $frequency, $basis = 0)
454
    {
455
        $settlement = Functions::flattenSingleValue($settlement);
456
        $maturity = Functions::flattenSingleValue($maturity);
457
        $frequency = (int) Functions::flattenSingleValue($frequency);
458
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
459
460
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
461
            return Functions::VALUE();
462
        }
463
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
464
            return Functions::VALUE();
465 8
        }
466
467 8
        if (($settlement >= $maturity) ||
468 8
            (!self::isValidFrequency($frequency)) ||
469 8
            (($basis < 0) || ($basis > 4))) {
470 8
            return Functions::NAN();
471
        }
472 8
473 1
        switch ($basis) {
474
            case 3:
475 7
                // Actual/365
476 1
                return 365 / $frequency;
477
            case 1:
478
                // Actual/actual
479 6
                if ($frequency == 1) {
480 6
                    $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
481 6
482 1
                    return $daysPerYear / $frequency;
483
                }
484
                $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, false);
485
                $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
486 5
487
                return $next - $prev;
488 1
            default:
489 4
                // US (NASD) 30/360, Actual/360 or European 30/360
490
                return 360 / $frequency;
491 3
        }
492 2
    }
493
494 2
    /**
495
     * COUPDAYSNC.
496 1
     *
497 1
     * Returns the number of days from the settlement date to the next coupon date.
498
     *
499 1
     * Excel Function:
500
     *        COUPDAYSNC(settlement,maturity,frequency[,basis])
501
     *
502 1
     * @category Financial Functions
503
     *
504
     * @param mixed $settlement The security's settlement date.
505
     *                                The security settlement date is the date after the issue
506
     *                                date when the security is traded to the buyer.
507
     * @param mixed $maturity The security's maturity date.
508
     *                                The maturity date is the date when the security expires.
509
     * @param mixed $frequency the number of coupon payments per year.
510
     *                                    Valid frequency values are:
511
     *                                        1    Annual
512
     *                                        2    Semi-Annual
513
     *                                        4    Quarterly
514
     * @param int $basis The type of day count to use.
515
     *                                        0 or omitted    US (NASD) 30/360
516
     *                                        1                Actual/actual
517
     *                                        2                Actual/360
518
     *                                        3                Actual/365
519
     *                                        4                European 30/360
520
     *
521
     * @return float|string
522
     */
523
    public static function COUPDAYSNC($settlement, $maturity, $frequency, $basis = 0)
524
    {
525
        $settlement = Functions::flattenSingleValue($settlement);
526
        $maturity = Functions::flattenSingleValue($maturity);
527
        $frequency = (int) Functions::flattenSingleValue($frequency);
528
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
529
530
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
531
            return Functions::VALUE();
532
        }
533
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
534
            return Functions::VALUE();
535
        }
536
537
        if (($settlement >= $maturity) ||
538
            (!self::isValidFrequency($frequency)) ||
539 5
            (($basis < 0) || ($basis > 4))) {
540
            return Functions::NAN();
541 5
        }
542 5
543 5
        $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
544 5
        $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
545
546 5
        return DateTime::YEARFRAC($settlement, $next, $basis) * $daysPerYear;
547 1
    }
548
549 4
    /**
550 1
     * COUPNCD.
551
     *
552
     * Returns the next coupon date after the settlement date.
553 3
     *
554 3
     * Excel Function:
555 3
     *        COUPNCD(settlement,maturity,frequency[,basis])
556 1
     *
557
     * @category Financial Functions
558
     *
559 2
     * @param mixed $settlement The security's settlement date.
560 2
     *                                The security settlement date is the date after the issue
561
     *                                date when the security is traded to the buyer.
562 2
     * @param mixed $maturity The security's maturity date.
563
     *                                The maturity date is the date when the security expires.
564
     * @param mixed $frequency the number of coupon payments per year.
565
     *                                    Valid frequency values are:
566
     *                                        1    Annual
567
     *                                        2    Semi-Annual
568
     *                                        4    Quarterly
569
     * @param int $basis The type of day count to use.
570
     *                                        0 or omitted    US (NASD) 30/360
571
     *                                        1                Actual/actual
572
     *                                        2                Actual/360
573
     *                                        3                Actual/365
574
     *                                        4                European 30/360
575
     *
576
     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
577
     *                        depending on the value of the ReturnDateType flag
578
     */
579
    public static function COUPNCD($settlement, $maturity, $frequency, $basis = 0)
580
    {
581
        $settlement = Functions::flattenSingleValue($settlement);
582
        $maturity = Functions::flattenSingleValue($maturity);
583
        $frequency = (int) Functions::flattenSingleValue($frequency);
584
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
585
586
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
587
            return Functions::VALUE();
588
        }
589
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
590
            return Functions::VALUE();
591
        }
592
593
        if (($settlement >= $maturity) ||
594
            (!self::isValidFrequency($frequency)) ||
595
            (($basis < 0) || ($basis > 4))) {
596
            return Functions::NAN();
597
        }
598
599 9
        return self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
600
    }
601 9
602 9
    /**
603 9
     * COUPNUM.
604 9
     *
605
     * Returns the number of coupons payable between the settlement date and maturity date,
606 9
     * rounded up to the nearest whole coupon.
607 1
     *
608
     * Excel Function:
609 8
     *        COUPNUM(settlement,maturity,frequency[,basis])
610 1
     *
611
     * @category Financial Functions
612
     *
613 7
     * @param mixed $settlement The security's settlement date.
614 7
     *                                The security settlement date is the date after the issue
615 7
     *                                date when the security is traded to the buyer.
616 2
     * @param mixed $maturity The security's maturity date.
617
     *                                The maturity date is the date when the security expires.
618
     * @param mixed $frequency the number of coupon payments per year.
619 5
     *                                    Valid frequency values are:
620
     *                                        1    Annual
621
     *                                        2    Semi-Annual
622
     *                                        4    Quarterly
623
     * @param int $basis The type of day count to use.
624
     *                                        0 or omitted    US (NASD) 30/360
625
     *                                        1                Actual/actual
626
     *                                        2                Actual/360
627
     *                                        3                Actual/365
628
     *                                        4                European 30/360
629
     *
630
     * @return int|string
631
     */
632
    public static function COUPNUM($settlement, $maturity, $frequency, $basis = 0)
633
    {
634
        $settlement = Functions::flattenSingleValue($settlement);
635
        $maturity = Functions::flattenSingleValue($maturity);
636
        $frequency = (int) Functions::flattenSingleValue($frequency);
637
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
638
639
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
640
            return Functions::VALUE();
641
        }
642
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
643
            return Functions::VALUE();
644
        }
645
646
        if (($settlement >= $maturity) ||
647
            (!self::isValidFrequency($frequency)) ||
648
            (($basis < 0) || ($basis > 4))) {
649
            return Functions::NAN();
650
        }
651
652
        $yearsBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, 0);
653
654
        return ceil($yearsBetweenSettlementAndMaturity * $frequency);
655
    }
656 10
657
    /**
658 10
     * COUPPCD.
659 10
     *
660 10
     * Returns the previous coupon date before the settlement date.
661 10
     *
662
     * Excel Function:
663 10
     *        COUPPCD(settlement,maturity,frequency[,basis])
664 1
     *
665
     * @category Financial Functions
666 9
     *
667 1
     * @param mixed $settlement The security's settlement date.
668
     *                                The security settlement date is the date after the issue
669
     *                                date when the security is traded to the buyer.
670 8
     * @param mixed $maturity The security's maturity date.
671 8
     *                                The maturity date is the date when the security expires.
672 8
     * @param mixed $frequency the number of coupon payments per year.
673 2
     *                                    Valid frequency values are:
674
     *                                        1    Annual
675
     *                                        2    Semi-Annual
676 6
     *                                        4    Quarterly
677 6
     * @param int $basis The type of day count to use.
678
     *                                        0 or omitted    US (NASD) 30/360
679
     *                                        1                Actual/actual
680 6
     *                                        2                Actual/360
681 5
     *                                        3                Actual/365
682 4
     *                                        4                European 30/360
683
     *
684
     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
685 6
     *                        depending on the value of the ReturnDateType flag
686
     */
687
    public static function COUPPCD($settlement, $maturity, $frequency, $basis = 0)
688
    {
689
        $settlement = Functions::flattenSingleValue($settlement);
690
        $maturity = Functions::flattenSingleValue($maturity);
691
        $frequency = (int) Functions::flattenSingleValue($frequency);
692
        $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
0 ignored issues
show
introduced by
The condition $basis === null is always false.
Loading history...
693
694
        if (is_string($settlement = DateTime::getDateValue($settlement))) {
695
            return Functions::VALUE();
696
        }
697
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
698
            return Functions::VALUE();
699
        }
700
701
        if (($settlement >= $maturity) ||
702
            (!self::isValidFrequency($frequency)) ||
703
            (($basis < 0) || ($basis > 4))) {
704
            return Functions::NAN();
705
        }
706
707
        return self::couponFirstPeriodDate($settlement, $maturity, $frequency, false);
708
    }
709
710
    /**
711
     * CUMIPMT.
712
     *
713
     * Returns the cumulative interest paid on a loan between the start and end periods.
714
     *
715
     * Excel Function:
716
     *        CUMIPMT(rate,nper,pv,start,end[,type])
717
     *
718
     * @category Financial Functions
719
     *
720
     * @param float $rate The Interest rate
721
     * @param int $nper The total number of payment periods
722
     * @param float $pv Present Value
723
     * @param int $start The first period in the calculation.
724
     *                            Payment periods are numbered beginning with 1.
725 9
     * @param int $end the last period in the calculation
726
     * @param int $type A number 0 or 1 and indicates when payments are due:
727 9
     *                                0 or omitted    At the end of the period.
728 9
     *                                1                At the beginning of the period.
729 9
     *
730 9
     * @return float|string
731
     */
732 9
    public static function CUMIPMT($rate, $nper, $pv, $start, $end, $type = 0)
733 1
    {
734
        $rate = Functions::flattenSingleValue($rate);
735 8
        $nper = (int) Functions::flattenSingleValue($nper);
736 1
        $pv = Functions::flattenSingleValue($pv);
737
        $start = (int) Functions::flattenSingleValue($start);
738
        $end = (int) Functions::flattenSingleValue($end);
739 7
        $type = (int) Functions::flattenSingleValue($type);
740 7
741 7
        // Validate parameters
742 2
        if ($type != 0 && $type != 1) {
743
            return Functions::NAN();
744
        }
745 5
        if ($start < 1 || $start > $end) {
746
            return Functions::VALUE();
747
        }
748
749
        // Calculate
750
        $interest = 0;
751
        for ($per = $start; $per <= $end; ++$per) {
752
            $interest += self::IPMT($rate, $per, $nper, $pv, 0, $type);
753
        }
754
755
        return $interest;
756
    }
757
758
    /**
759
     * CUMPRINC.
760
     *
761
     * Returns the cumulative principal paid on a loan between the start and end periods.
762
     *
763
     * Excel Function:
764
     *        CUMPRINC(rate,nper,pv,start,end[,type])
765
     *
766
     * @category Financial Functions
767
     *
768
     * @param float $rate The Interest rate
769
     * @param int $nper The total number of payment periods
770 9
     * @param float $pv Present Value
771
     * @param int $start The first period in the calculation.
772 9
     *                            Payment periods are numbered beginning with 1.
773 9
     * @param int $end the last period in the calculation
774 9
     * @param int $type A number 0 or 1 and indicates when payments are due:
775 9
     *                                0 or omitted    At the end of the period.
776 9
     *                                1                At the beginning of the period.
777 9
     *
778
     * @return float|string
779
     */
780 9
    public static function CUMPRINC($rate, $nper, $pv, $start, $end, $type = 0)
781 1
    {
782
        $rate = Functions::flattenSingleValue($rate);
783 8
        $nper = (int) Functions::flattenSingleValue($nper);
784 1
        $pv = Functions::flattenSingleValue($pv);
785
        $start = (int) Functions::flattenSingleValue($start);
786
        $end = (int) Functions::flattenSingleValue($end);
787
        $type = (int) Functions::flattenSingleValue($type);
788 7
789 7
        // Validate parameters
790 7
        if ($type != 0 && $type != 1) {
791
            return Functions::NAN();
792
        }
793 7
        if ($start < 1 || $start > $end) {
794
            return Functions::VALUE();
795
        }
796
797
        // Calculate
798
        $principal = 0;
799
        for ($per = $start; $per <= $end; ++$per) {
800
            $principal += self::PPMT($rate, $per, $nper, $pv, 0, $type);
801
        }
802
803
        return $principal;
804
    }
805
806
    /**
807
     * DB.
808
     *
809
     * Returns the depreciation of an asset for a specified period using the
810
     * fixed-declining balance method.
811
     * This form of depreciation is used if you want to get a higher depreciation value
812
     * at the beginning of the depreciation (as opposed to linear depreciation). The
813
     * depreciation value is reduced with every depreciation period by the depreciation
814
     * already deducted from the initial cost.
815
     *
816
     * Excel Function:
817
     *        DB(cost,salvage,life,period[,month])
818 9
     *
819
     * @category Financial Functions
820 9
     *
821 9
     * @param float $cost Initial cost of the asset
822 9
     * @param float $salvage Value at the end of the depreciation.
823 9
     *                                (Sometimes called the salvage value of the asset)
824 9
     * @param int $life Number of periods over which the asset is depreciated.
825 9
     *                                (Sometimes called the useful life of the asset)
826
     * @param int $period The period for which you want to calculate the
827
     *                                depreciation. Period must use the same units as life.
828 9
     * @param int $month Number of months in the first year. If month is omitted,
829 1
     *                                it defaults to 12.
830
     *
831 8
     * @return float|string
832 1
     */
833
    public static function DB($cost, $salvage, $life, $period, $month = 12)
834
    {
835
        $cost = Functions::flattenSingleValue($cost);
836 7
        $salvage = Functions::flattenSingleValue($salvage);
837 7
        $life = Functions::flattenSingleValue($life);
838 7
        $period = Functions::flattenSingleValue($period);
839
        $month = Functions::flattenSingleValue($month);
840
841 7
        //    Validate
842
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($month))) {
843
            $cost = (float) $cost;
844
            $salvage = (float) $salvage;
845
            $life = (int) $life;
846
            $period = (int) $period;
847
            $month = (int) $month;
848
            if ($cost == 0) {
849
                return 0.0;
850
            } elseif (($cost < 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($month < 1)) {
851
                return Functions::NAN();
852
            }
853
            //    Set Fixed Depreciation Rate
854
            $fixedDepreciationRate = 1 - pow(($salvage / $cost), (1 / $life));
855
            $fixedDepreciationRate = round($fixedDepreciationRate, 3);
856
857
            //    Loop through each period calculating the depreciation
858
            $previousDepreciation = 0;
859
            $depreciation = 0;
860
            for ($per = 1; $per <= $period; ++$per) {
861
                if ($per == 1) {
862
                    $depreciation = $cost * $fixedDepreciationRate * $month / 12;
863
                } elseif ($per == ($life + 1)) {
864
                    $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12;
865
                } else {
866
                    $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate;
867
                }
868
                $previousDepreciation += $depreciation;
869
            }
870
871 16
            return $depreciation;
872
        }
873 16
874 16
        return Functions::VALUE();
875 16
    }
876 16
877 16
    /**
878
     * DDB.
879
     *
880 16
     * Returns the depreciation of an asset for a specified period using the
881 15
     * double-declining balance method or some other method you specify.
882 15
     *
883 15
     * Excel Function:
884 15
     *        DDB(cost,salvage,life,period[,factor])
885 15
     *
886 15
     * @category Financial Functions
887 1
     *
888 14
     * @param float $cost Initial cost of the asset
889 1
     * @param float $salvage Value at the end of the depreciation.
890
     *                                (Sometimes called the salvage value of the asset)
891
     * @param int $life Number of periods over which the asset is depreciated.
892 13
     *                                (Sometimes called the useful life of the asset)
893 13
     * @param int $period The period for which you want to calculate the
894
     *                                depreciation. Period must use the same units as life.
895
     * @param float $factor The rate at which the balance declines.
896 13
     *                                If factor is omitted, it is assumed to be 2 (the
897 13
     *                                double-declining balance method).
898 13
     *
899 13
     * @return float|string
900 11
     */
901 2
    public static function DDB($cost, $salvage, $life, $period, $factor = 2.0)
902
    {
903 11
        $cost = Functions::flattenSingleValue($cost);
904
        $salvage = Functions::flattenSingleValue($salvage);
905 13
        $life = Functions::flattenSingleValue($life);
906
        $period = Functions::flattenSingleValue($period);
907 13
        $factor = Functions::flattenSingleValue($factor);
908
909
        //    Validate
910
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($factor))) {
911 13
            $cost = (float) $cost;
912
            $salvage = (float) $salvage;
913
            $life = (int) $life;
914 1
            $period = (int) $period;
915
            $factor = (float) $factor;
916
            if (($cost <= 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($factor <= 0.0) || ($period > $life)) {
917
                return Functions::NAN();
918
            }
919
            //    Set Fixed Depreciation Rate
920
            $fixedDepreciationRate = 1 - pow(($salvage / $cost), (1 / $life));
921
            $fixedDepreciationRate = round($fixedDepreciationRate, 3);
0 ignored issues
show
Unused Code introduced by
The assignment to $fixedDepreciationRate is dead and can be removed.
Loading history...
922
923
            //    Loop through each period calculating the depreciation
924
            $previousDepreciation = 0;
925
            $depreciation = 0;
926
            for ($per = 1; $per <= $period; ++$per) {
927
                $depreciation = min(($cost - $previousDepreciation) * ($factor / $life), ($cost - $salvage - $previousDepreciation));
928
                $previousDepreciation += $depreciation;
929
            }
930
931
            return $depreciation;
932
        }
933
934
        return Functions::VALUE();
935
    }
936
937
    /**
938
     * DISC.
939
     *
940
     * Returns the discount rate for a security.
941 15
     *
942
     * Excel Function:
943 15
     *        DISC(settlement,maturity,price,redemption[,basis])
944 15
     *
945 15
     * @category Financial Functions
946 15
     *
947 15
     * @param mixed $settlement The security's settlement date.
948
     *                                The security settlement date is the date after the issue
949
     *                                date when the security is traded to the buyer.
950 15
     * @param mixed $maturity The security's maturity date.
951 14
     *                                The maturity date is the date when the security expires.
952 14
     * @param int $price The security's price per $100 face value
953 14
     * @param int $redemption The security's redemption value per $100 face value
954 14
     * @param int $basis The type of day count to use.
955 14
     *                                        0 or omitted    US (NASD) 30/360
956 14
     *                                        1                Actual/actual
957 1
     *                                        2                Actual/360
958
     *                                        3                Actual/365
959
     *                                        4                European 30/360
960 13
     *
961 13
     * @return float|string
962
     */
963
    public static function DISC($settlement, $maturity, $price, $redemption, $basis = 0)
964 13
    {
965 13
        $settlement = Functions::flattenSingleValue($settlement);
966 13
        $maturity = Functions::flattenSingleValue($maturity);
967 13
        $price = Functions::flattenSingleValue($price);
968
        $redemption = Functions::flattenSingleValue($redemption);
969 13
        $basis = Functions::flattenSingleValue($basis);
970
971
        //    Validate
972
        if ((is_numeric($price)) && (is_numeric($redemption)) && (is_numeric($basis))) {
973 13
            $price = (float) $price;
974
            $redemption = (float) $redemption;
975
            $basis = (int) $basis;
976 1
            if (($price <= 0) || ($redemption <= 0)) {
977
                return Functions::NAN();
978
            }
979
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
980
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
981
                //    return date error
982
                return $daysBetweenSettlementAndMaturity;
983
            }
984
985
            return (1 - $price / $redemption) / $daysBetweenSettlementAndMaturity;
986
        }
987
988
        return Functions::VALUE();
989
    }
990
991
    /**
992
     * DOLLARDE.
993
     *
994
     * Converts a dollar price expressed as an integer part and a fraction
995
     *        part into a dollar price expressed as a decimal number.
996
     * Fractional dollar numbers are sometimes used for security prices.
997
     *
998
     * Excel Function:
999
     *        DOLLARDE(fractional_dollar,fraction)
1000
     *
1001
     * @category Financial Functions
1002
     *
1003
     * @param float $fractional_dollar Fractional Dollar
1004
     * @param int $fraction Fraction
1005 5
     *
1006
     * @return float|string
1007 5
     */
1008 5
    public static function DOLLARDE($fractional_dollar = null, $fraction = 0)
1009 5
    {
1010 5
        $fractional_dollar = Functions::flattenSingleValue($fractional_dollar);
1011 5
        $fraction = (int) Functions::flattenSingleValue($fraction);
1012
1013
        // Validate parameters
1014 5
        if ($fractional_dollar === null || $fraction < 0) {
1015 4
            return Functions::NAN();
1016 4
        }
1017 4
        if ($fraction == 0) {
1018 4
            return Functions::DIV0();
1019 1
        }
1020
1021 3
        $dollars = floor($fractional_dollar);
1022 3
        $cents = fmod($fractional_dollar, 1);
1023
        $cents /= $fraction;
1024 1
        $cents *= pow(10, ceil(log10($fraction)));
1025
1026
        return $dollars + $cents;
1027 2
    }
1028
1029
    /**
1030 1
     * DOLLARFR.
1031
     *
1032
     * Converts a dollar price expressed as a decimal number into a dollar price
1033
     *        expressed as a fraction.
1034
     * Fractional dollar numbers are sometimes used for security prices.
1035
     *
1036
     * Excel Function:
1037
     *        DOLLARFR(decimal_dollar,fraction)
1038
     *
1039
     * @category Financial Functions
1040
     *
1041
     * @param float $decimal_dollar Decimal Dollar
1042
     * @param int $fraction Fraction
1043
     *
1044
     * @return float|string
1045
     */
1046
    public static function DOLLARFR($decimal_dollar = null, $fraction = 0)
1047
    {
1048
        $decimal_dollar = Functions::flattenSingleValue($decimal_dollar);
1049
        $fraction = (int) Functions::flattenSingleValue($fraction);
1050 9
1051
        // Validate parameters
1052 9
        if ($decimal_dollar === null || $fraction < 0) {
1053 9
            return Functions::NAN();
1054
        }
1055
        if ($fraction == 0) {
1056 9
            return Functions::DIV0();
1057 1
        }
1058
1059 8
        $dollars = floor($decimal_dollar);
1060 1
        $cents = fmod($decimal_dollar, 1);
1061
        $cents *= $fraction;
1062
        $cents *= pow(10, -ceil(log10($fraction)));
1063 7
1064 7
        return $dollars + $cents;
1065 7
    }
1066 7
1067
    /**
1068 7
     * EFFECT.
1069
     *
1070
     * Returns the effective interest rate given the nominal rate and the number of
1071
     *        compounding payments per year.
1072
     *
1073
     * Excel Function:
1074
     *        EFFECT(nominal_rate,npery)
1075
     *
1076
     * @category Financial Functions
1077
     *
1078
     * @param float $nominal_rate Nominal interest rate
1079
     * @param int $npery Number of compounding payments per year
1080
     *
1081
     * @return float|string
1082
     */
1083
    public static function EFFECT($nominal_rate = 0, $npery = 0)
1084
    {
1085
        $nominal_rate = Functions::flattenSingleValue($nominal_rate);
1086
        $npery = (int) Functions::flattenSingleValue($npery);
1087
1088 9
        // Validate parameters
1089
        if ($nominal_rate <= 0 || $npery < 1) {
1090 9
            return Functions::NAN();
1091 9
        }
1092
1093
        return pow((1 + $nominal_rate / $npery), $npery) - 1;
1094 9
    }
1095 1
1096
    /**
1097 8
     * FV.
1098 1
     *
1099
     * Returns the Future Value of a cash flow with constant payments and interest rate (annuities).
1100
     *
1101 7
     * Excel Function:
1102 7
     *        FV(rate,nper,pmt[,pv[,type]])
1103 7
     *
1104 7
     * @category Financial Functions
1105
     *
1106 7
     * @param float $rate The interest rate per period
1107
     * @param int $nper Total number of payment periods in an annuity
1108
     * @param float $pmt The payment made each period: it cannot change over the
1109
     *                            life of the annuity. Typically, pmt contains principal
1110
     *                            and interest but no other fees or taxes.
1111
     * @param float $pv present Value, or the lump-sum amount that a series of
1112
     *                            future payments is worth right now
1113
     * @param int $type A number 0 or 1 and indicates when payments are due:
1114
     *                                0 or omitted    At the end of the period.
1115
     *                                1                At the beginning of the period.
1116
     *
1117
     * @return float|string
1118
     */
1119
    public static function FV($rate = 0, $nper = 0, $pmt = 0, $pv = 0, $type = 0)
1120
    {
1121
        $rate = Functions::flattenSingleValue($rate);
1122
        $nper = Functions::flattenSingleValue($nper);
1123
        $pmt = Functions::flattenSingleValue($pmt);
1124
        $pv = Functions::flattenSingleValue($pv);
1125 5
        $type = Functions::flattenSingleValue($type);
1126
1127 5
        // Validate parameters
1128 5
        if ($type != 0 && $type != 1) {
1129
            return Functions::NAN();
1130
        }
1131 5
1132 1
        // Calculate
1133
        if ($rate !== null && $rate != 0) {
1134
            return -$pv * pow(1 + $rate, $nper) - $pmt * (1 + $rate * $type) * (pow(1 + $rate, $nper) - 1) / $rate;
1135 4
        }
1136
1137
        return -$pv - $pmt * $nper;
1138
    }
1139
1140
    /**
1141
     * FVSCHEDULE.
1142
     *
1143
     * Returns the future value of an initial principal after applying a series of compound interest rates.
1144
     * Use FVSCHEDULE to calculate the future value of an investment with a variable or adjustable rate.
1145
     *
1146
     * Excel Function:
1147
     *        FVSCHEDULE(principal,schedule)
1148
     *
1149
     * @param float $principal the present value
1150
     * @param float[] $schedule an array of interest rates to apply
1151
     *
1152
     * @return float
1153
     */
1154
    public static function FVSCHEDULE($principal, $schedule)
1155
    {
1156
        $principal = Functions::flattenSingleValue($principal);
1157
        $schedule = Functions::flattenArray($schedule);
1158
1159
        foreach ($schedule as $rate) {
1160
            $principal *= 1 + $rate;
1161 8
        }
1162
1163 8
        return $principal;
1164 8
    }
1165 8
1166 8
    /**
1167 8
     * INTRATE.
1168
     *
1169
     * Returns the interest rate for a fully invested security.
1170 8
     *
1171 1
     * Excel Function:
1172
     *        INTRATE(settlement,maturity,investment,redemption[,basis])
1173
     *
1174
     * @param mixed $settlement The security's settlement date.
1175 7
     *                                The security settlement date is the date after the issue date when the security is traded to the buyer.
1176 6
     * @param mixed $maturity The security's maturity date.
1177
     *                                The maturity date is the date when the security expires.
1178
     * @param int $investment the amount invested in the security
1179 1
     * @param int $redemption the amount to be received at maturity
1180
     * @param int $basis The type of day count to use.
1181
     *                                        0 or omitted    US (NASD) 30/360
1182
     *                                        1                Actual/actual
1183
     *                                        2                Actual/360
1184
     *                                        3                Actual/365
1185
     *                                        4                European 30/360
1186
     *
1187
     * @return float|string
1188
     */
1189
    public static function INTRATE($settlement, $maturity, $investment, $redemption, $basis = 0)
1190
    {
1191
        $settlement = Functions::flattenSingleValue($settlement);
1192
        $maturity = Functions::flattenSingleValue($maturity);
1193
        $investment = Functions::flattenSingleValue($investment);
1194
        $redemption = Functions::flattenSingleValue($redemption);
1195
        $basis = Functions::flattenSingleValue($basis);
1196 3
1197
        //    Validate
1198 3
        if ((is_numeric($investment)) && (is_numeric($redemption)) && (is_numeric($basis))) {
1199 3
            $investment = (float) $investment;
1200
            $redemption = (float) $redemption;
1201 3
            $basis = (int) $basis;
1202 3
            if (($investment <= 0) || ($redemption <= 0)) {
1203
                return Functions::NAN();
1204
            }
1205 3
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1206
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
1207
                //    return date error
1208
                return $daysBetweenSettlementAndMaturity;
1209
            }
1210
1211
            return (($redemption / $investment) - 1) / ($daysBetweenSettlementAndMaturity);
1212
        }
1213
1214
        return Functions::VALUE();
1215
    }
1216
1217
    /**
1218
     * IPMT.
1219
     *
1220
     * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate.
1221
     *
1222
     * Excel Function:
1223
     *        IPMT(rate,per,nper,pv[,fv][,type])
1224
     *
1225
     * @param float $rate Interest rate per period
1226
     * @param int $per Period for which we want to find the interest
1227
     * @param int $nper Number of periods
1228
     * @param float $pv Present Value
1229
     * @param float $fv Future Value
1230
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1231 5
     *
1232
     * @return float|string
1233 5
     */
1234 5
    public static function IPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0)
1235 5
    {
1236 5
        $rate = Functions::flattenSingleValue($rate);
1237 5
        $per = (int) Functions::flattenSingleValue($per);
1238
        $nper = (int) Functions::flattenSingleValue($nper);
1239
        $pv = Functions::flattenSingleValue($pv);
1240 5
        $fv = Functions::flattenSingleValue($fv);
1241 4
        $type = (int) Functions::flattenSingleValue($type);
1242 4
1243 4
        // Validate parameters
1244 4
        if ($type != 0 && $type != 1) {
1245 1
            return Functions::NAN();
1246
        }
1247 3
        if ($per <= 0 || $per > $nper) {
1248 3
            return Functions::VALUE();
1249
        }
1250 1
1251
        // Calculate
1252
        $interestAndPrincipal = self::interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type);
1253 2
1254
        return $interestAndPrincipal[0];
1255
    }
1256 1
1257
    /**
1258
     * IRR.
1259
     *
1260
     * Returns the internal rate of return for a series of cash flows represented by the numbers in values.
1261
     * These cash flows do not have to be even, as they would be for an annuity. However, the cash flows must occur
1262
     * at regular intervals, such as monthly or annually. The internal rate of return is the interest rate received
1263
     * for an investment consisting of payments (negative values) and income (positive values) that occur at regular
1264
     * periods.
1265
     *
1266
     * Excel Function:
1267
     *        IRR(values[,guess])
1268
     *
1269
     * @param float[] $values An array or a reference to cells that contain numbers for which you want
1270
     *                                    to calculate the internal rate of return.
1271
     *                                Values must contain at least one positive value and one negative value to
1272
     *                                    calculate the internal rate of return.
1273
     * @param float $guess A number that you guess is close to the result of IRR
1274
     *
1275
     * @return float|string
1276 15
     */
1277
    public static function IRR($values, $guess = 0.1)
1278 15
    {
1279 15
        if (!is_array($values)) {
0 ignored issues
show
introduced by
The condition is_array($values) is always true.
Loading history...
1280 15
            return Functions::VALUE();
1281 15
        }
1282 15
        $values = Functions::flattenArray($values);
1283 15
        $guess = Functions::flattenSingleValue($guess);
1284
1285
        // create an initial range, with a root somewhere between 0 and guess
1286 15
        $x1 = 0.0;
1287 1
        $x2 = $guess;
1288
        $f1 = self::NPV($x1, $values);
1289 14
        $f2 = self::NPV($x2, $values);
1290 1
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
1291
            if (($f1 * $f2) < 0.0) {
1292
                break;
1293
            }
1294 13
            if (abs($f1) < abs($f2)) {
1295
                $f1 = self::NPV($x1 += 1.6 * ($x1 - $x2), $values);
1296 13
            } else {
1297
                $f2 = self::NPV($x2 += 1.6 * ($x2 - $x1), $values);
1298
            }
1299
        }
1300
        if (($f1 * $f2) > 0.0) {
1301
            return Functions::VALUE();
1302
        }
1303
1304
        $f = self::NPV($x1, $values);
1305
        if ($f < 0.0) {
1306
            $rtb = $x1;
1307
            $dx = $x2 - $x1;
1308
        } else {
1309
            $rtb = $x2;
1310
            $dx = $x1 - $x2;
1311
        }
1312
1313
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
1314
            $dx *= 0.5;
1315
            $x_mid = $rtb + $dx;
1316
            $f_mid = self::NPV($x_mid, $values);
1317
            if ($f_mid <= 0.0) {
1318
                $rtb = $x_mid;
1319 5
            }
1320
            if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
1321 5
                return $x_mid;
1322
            }
1323
        }
1324 5
1325 5
        return Functions::VALUE();
1326
    }
1327
1328 5
    /**
1329 5
     * ISPMT.
1330 5
     *
1331 5
     * Returns the interest payment for an investment based on an interest rate and a constant payment schedule.
1332 5
     *
1333 5
     * Excel Function:
1334 5
     *     =ISPMT(interest_rate, period, number_payments, PV)
1335
     *
1336 4
     * interest_rate is the interest rate for the investment
1337 3
     *
1338
     * period is the period to calculate the interest rate.  It must be betweeen 1 and number_payments.
1339 1
     *
1340
     * number_payments is the number of payments for the annuity
1341
     *
1342 5
     * PV is the loan amount or present value of the payments
1343
     */
1344
    public static function ISPMT(...$args)
1345
    {
1346 5
        // Return value
1347 5
        $returnValue = 0;
1348
1349
        // Get the parameters
1350
        $aArgs = Functions::flattenArray($args);
1351 5
        $interestRate = array_shift($aArgs);
1352 5
        $period = array_shift($aArgs);
1353
        $numberPeriods = array_shift($aArgs);
1354
        $principleRemaining = array_shift($aArgs);
1355 5
1356 5
        // Calculate
1357 5
        $principlePayment = ($principleRemaining * 1.0) / ($numberPeriods * 1.0);
1358 5
        for ($i = 0; $i <= $period; ++$i) {
1359 5
            $returnValue = $interestRate * $principleRemaining * -1;
1360 5
            $principleRemaining -= $principlePayment;
1361
            // principle needs to be 0 after the last payment, don't let floating point screw it up
1362 5
            if ($i == $numberPeriods) {
1363 5
                $returnValue = 0;
1364
            }
1365
        }
1366
1367
        return $returnValue;
1368
    }
1369
1370
    /**
1371
     * MIRR.
1372
     *
1373
     * Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both
1374
     *        the cost of the investment and the interest received on reinvestment of cash.
1375
     *
1376
     * Excel Function:
1377
     *        MIRR(values,finance_rate, reinvestment_rate)
1378
     *
1379
     * @param float[] $values An array or a reference to cells that contain a series of payments and
1380
     *                                            income occurring at regular intervals.
1381
     *                                        Payments are negative value, income is positive values.
1382
     * @param float $finance_rate The interest rate you pay on the money used in the cash flows
1383
     * @param float $reinvestment_rate The interest rate you receive on the cash flows as you reinvest them
1384
     *
1385
     * @return float|string Result, or a string containing an error
1386 4
     */
1387
    public static function MIRR($values, $finance_rate, $reinvestment_rate)
1388
    {
1389 4
        if (!is_array($values)) {
0 ignored issues
show
introduced by
The condition is_array($values) is always true.
Loading history...
1390
            return Functions::VALUE();
1391
        }
1392 4
        $values = Functions::flattenArray($values);
1393 4
        $finance_rate = Functions::flattenSingleValue($finance_rate);
1394 4
        $reinvestment_rate = Functions::flattenSingleValue($reinvestment_rate);
1395 4
        $n = count($values);
1396 4
1397
        $rr = 1.0 + $reinvestment_rate;
1398
        $fr = 1.0 + $finance_rate;
1399 4
1400 4
        $npv_pos = $npv_neg = 0.0;
1401 4
        foreach ($values as $i => $v) {
1402 4
            if ($v >= 0) {
1403
                $npv_pos += $v / pow($rr, $i);
1404 4
            } else {
1405
                $npv_neg += $v / pow($fr, $i);
1406
            }
1407
        }
1408
1409 4
        if (($npv_neg == 0) || ($npv_pos == 0) || ($reinvestment_rate <= -1)) {
0 ignored issues
show
introduced by
The condition $npv_neg == 0 is always true.
Loading history...
1410
            return Functions::VALUE();
1411
        }
1412
1413
        $mirr = pow((-$npv_pos * pow($rr, $n))
1414
                / ($npv_neg * ($rr)), (1.0 / ($n - 1))) - 1.0;
1415
1416
        return is_finite($mirr) ? $mirr : Functions::VALUE();
1417
    }
1418
1419
    /**
1420
     * NOMINAL.
1421
     *
1422
     * Returns the nominal interest rate given the effective rate and the number of compounding payments per year.
1423
     *
1424
     * @param float $effect_rate Effective interest rate
1425
     * @param int $npery Number of compounding payments per year
1426
     *
1427
     * @return float|string Result, or a string containing an error
1428
     */
1429 5
    public static function NOMINAL($effect_rate = 0, $npery = 0)
1430
    {
1431 5
        $effect_rate = Functions::flattenSingleValue($effect_rate);
1432
        $npery = (int) Functions::flattenSingleValue($npery);
1433
1434 5
        // Validate parameters
1435 5
        if ($effect_rate <= 0 || $npery < 1) {
1436 5
            return Functions::NAN();
1437 5
        }
1438
1439 5
        // Calculate
1440 5
        return $npery * (pow($effect_rate + 1, 1 / $npery) - 1);
1441
    }
1442 5
1443 5
    /**
1444 5
     * NPER.
1445 5
     *
1446
     * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate.
1447 5
     *
1448
     * @param float $rate Interest rate per period
1449
     * @param int $pmt Periodic payment (annuity)
1450
     * @param float $pv Present Value
1451 5
     * @param float $fv Future Value
1452
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1453
     *
1454
     * @return float|string Result, or a string containing an error
1455 5
     */
1456 5
    public static function NPER($rate = 0, $pmt = 0, $pv = 0, $fv = 0, $type = 0)
1457
    {
1458 5
        $rate = Functions::flattenSingleValue($rate);
1459
        $pmt = Functions::flattenSingleValue($pmt);
1460
        $pv = Functions::flattenSingleValue($pv);
1461
        $fv = Functions::flattenSingleValue($fv);
1462
        $type = Functions::flattenSingleValue($type);
1463
1464
        // Validate parameters
1465
        if ($type != 0 && $type != 1) {
1466
            return Functions::NAN();
1467
        }
1468
1469
        // Calculate
1470
        if ($rate !== null && $rate != 0) {
1471 5
            if ($pmt == 0 && $pv == 0) {
1472
                return Functions::NAN();
1473 5
            }
1474 5
1475
            return log(($pmt * (1 + $rate * $type) / $rate - $fv) / ($pv + $pmt * (1 + $rate * $type) / $rate)) / log(1 + $rate);
1476
        }
1477 5
        if ($pmt == 0) {
1478 1
            return Functions::NAN();
1479
        }
1480
1481
        return (-$pv - $fv) / $pmt;
1482 4
    }
1483
1484
    /**
1485
     * NPV.
1486
     *
1487
     * Returns the Net Present Value of a cash flow series given a discount rate.
1488
     *
1489
     * @return float
1490
     */
1491
    public static function NPV(...$args)
1492
    {
1493
        // Return value
1494
        $returnValue = 0;
1495
1496
        // Loop through arguments
1497
        $aArgs = Functions::flattenArray($args);
1498 9
1499
        // Calculate
1500 9
        $rate = array_shift($aArgs);
1501 9
        $countArgs = count($aArgs);
1502 9
        for ($i = 1; $i <= $countArgs; ++$i) {
1503 9
            // Is it a numeric value?
1504 9
            if (is_numeric($aArgs[$i - 1])) {
1505
                $returnValue += $aArgs[$i - 1] / pow(1 + $rate, $i);
1506
            }
1507 9
        }
1508 1
1509
        // Return
1510
        return $returnValue;
1511
    }
1512 8
1513 6
    /**
1514 1
     * PDURATION.
1515
     *
1516
     * Calculates the number of periods required for an investment to reach a specified value.
1517 5
     *
1518
     * @param float $rate Interest rate per period
1519 2
     * @param float $pv Present Value
1520 1
     * @param float $fv Future Value
1521
     *
1522
     * @return float|string Result, or a string containing an error
1523 1
     */
1524
    public static function PDURATION($rate = 0, $pv = 0, $fv = 0)
1525
    {
1526
        $rate = Functions::flattenSingleValue($rate);
1527
        $pv = Functions::flattenSingleValue($pv);
1528
        $fv = Functions::flattenSingleValue($fv);
1529
1530
        // Validate parameters
1531
        if (!is_numeric($rate) || !is_numeric($pv) || !is_numeric($fv)) {
1532
            return Functions::VALUE();
1533 9
        } elseif ($rate <= 0.0 || $pv <= 0.0 || $fv <= 0.0) {
1534
            return Functions::NAN();
1535
        }
1536 9
1537
        return (log($fv) - log($pv)) / log(1 + $rate);
1538
    }
1539 9
1540
    /**
1541
     * PMT.
1542 9
     *
1543 9
     * Returns the constant payment (annuity) for a cash flow with a constant interest rate.
1544 9
     *
1545
     * @param float $rate Interest rate per period
1546 9
     * @param int $nper Number of periods
1547 9
     * @param float $pv Present Value
1548
     * @param float $fv Future Value
1549
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1550
     *
1551
     * @return float|string Result, or a string containing an error
1552 9
     */
1553
    public static function PMT($rate = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0)
1554
    {
1555
        $rate = Functions::flattenSingleValue($rate);
1556
        $nper = Functions::flattenSingleValue($nper);
1557
        $pv = Functions::flattenSingleValue($pv);
1558
        $fv = Functions::flattenSingleValue($fv);
1559
        $type = Functions::flattenSingleValue($type);
1560
1561
        // Validate parameters
1562
        if ($type != 0 && $type != 1) {
1563
            return Functions::NAN();
1564
        }
1565
1566 8
        // Calculate
1567
        if ($rate !== null && $rate != 0) {
1568 8
            return (-$fv - $pv * pow(1 + $rate, $nper)) / (1 + $rate * $type) / ((pow(1 + $rate, $nper) - 1) / $rate);
1569 8
        }
1570 8
1571
        return (-$pv - $fv) / $nper;
1572
    }
1573 8
1574 1
    /**
1575 7
     * PPMT.
1576 2
     *
1577
     * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate.
1578
     *
1579 5
     * @param float $rate Interest rate per period
1580
     * @param int $per Period for which we want to find the interest
1581
     * @param int $nper Number of periods
1582
     * @param float $pv Present Value
1583
     * @param float $fv Future Value
1584
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1585
     *
1586
     * @return float|string Result, or a string containing an error
1587
     */
1588
    public static function PPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0)
1589
    {
1590
        $rate = Functions::flattenSingleValue($rate);
1591
        $per = (int) Functions::flattenSingleValue($per);
1592
        $nper = (int) Functions::flattenSingleValue($nper);
1593
        $pv = Functions::flattenSingleValue($pv);
1594
        $fv = Functions::flattenSingleValue($fv);
1595 20
        $type = (int) Functions::flattenSingleValue($type);
1596
1597 20
        // Validate parameters
1598 20
        if ($type != 0 && $type != 1) {
1599 20
            return Functions::NAN();
1600 20
        }
1601 20
        if ($per <= 0 || $per > $nper) {
1602
            return Functions::VALUE();
1603
        }
1604 20
1605
        // Calculate
1606
        $interestAndPrincipal = self::interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type);
1607
1608
        return $interestAndPrincipal[1];
1609 20
    }
1610 20
1611
    private static function validatePrice($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis)
1612
    {
1613
        if (is_string($settlement)) {
1614
            return Functions::VALUE();
1615
        }
1616
        if (is_string($maturity)) {
1617
            return Functions::VALUE();
1618
        }
1619
        if (!is_numeric($rate)) {
1620
            return Functions::VALUE();
1621
        }
1622
        if (!is_numeric($yield)) {
1623
            return Functions::VALUE();
1624
        }
1625
        if (!is_numeric($redemption)) {
1626
            return Functions::VALUE();
1627
        }
1628
        if (!is_numeric($frequency)) {
1629
            return Functions::VALUE();
1630 7
        }
1631
        if (!is_numeric($basis)) {
1632 7
            return Functions::VALUE();
1633 7
        }
1634 7
1635 7
        return '';
1636 7
    }
1637 7
1638
    public static function PRICE($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis = 0)
1639
    {
1640 7
        $settlement = Functions::flattenSingleValue($settlement);
1641
        $maturity = Functions::flattenSingleValue($maturity);
1642
        $rate = Functions::flattenSingleValue($rate);
1643 7
        $yield = Functions::flattenSingleValue($yield);
1644
        $redemption = Functions::flattenSingleValue($redemption);
1645
        $frequency = Functions::flattenSingleValue($frequency);
1646
        $basis = Functions::flattenSingleValue($basis);
1647
1648 7
        $settlement = DateTime::getDateValue($settlement);
1649
        $maturity = DateTime::getDateValue($maturity);
1650 7
        $rslt = self::validatePrice($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis);
1651
        if ($rslt) {
1652
            return $rslt;
1653
        }
1654
        $rate = (float) $rate;
1655
        $yield = (float) $yield;
1656
        $redemption = (float) $redemption;
1657
        $frequency = (int) $frequency;
1658
        $basis = (int) $basis;
1659
1660
        if (($settlement > $maturity) ||
1661
            (!self::isValidFrequency($frequency)) ||
1662
            (($basis < 0) || ($basis > 4))) {
1663
            return Functions::NAN();
1664
        }
1665
1666
        $dsc = self::COUPDAYSNC($settlement, $maturity, $frequency, $basis);
1667
        $e = self::COUPDAYS($settlement, $maturity, $frequency, $basis);
1668
        $n = self::COUPNUM($settlement, $maturity, $frequency, $basis);
1669
        $a = self::COUPDAYBS($settlement, $maturity, $frequency, $basis);
1670
1671
        $baseYF = 1.0 + ($yield / $frequency);
1672
        $rfp = 100 * ($rate / $frequency);
1673
        $de = $dsc / $e;
1674
1675
        $result = $redemption / pow($baseYF, (--$n + $de));
1676
        for ($k = 0; $k <= $n; ++$k) {
1677
            $result += $rfp / (pow($baseYF, ($k + $de)));
1678
        }
1679
        $result -= $rfp * ($a / $e);
1680
1681
        return $result;
1682
    }
1683
1684
    /**
1685
     * PRICEDISC.
1686
     *
1687
     * Returns the price per $100 face value of a discounted security.
1688
     *
1689
     * @param mixed $settlement The security's settlement date.
1690
     *                                The security settlement date is the date after the issue date when the security is traded to the buyer.
1691
     * @param mixed $maturity The security's maturity date.
1692
     *                                The maturity date is the date when the security expires.
1693
     * @param int $discount The security's discount rate
1694
     * @param int $redemption The security's redemption value per $100 face value
1695
     * @param int $basis The type of day count to use.
1696
     *                                        0 or omitted    US (NASD) 30/360
1697
     *                                        1                Actual/actual
1698
     *                                        2                Actual/360
1699
     *                                        3                Actual/365
1700
     *                                        4                European 30/360
1701
     *
1702
     * @return float|string Result, or a string containing an error
1703
     */
1704
    public static function PRICEDISC($settlement, $maturity, $discount, $redemption, $basis = 0)
1705
    {
1706
        $settlement = Functions::flattenSingleValue($settlement);
1707
        $maturity = Functions::flattenSingleValue($maturity);
1708
        $discount = (float) Functions::flattenSingleValue($discount);
1709
        $redemption = (float) Functions::flattenSingleValue($redemption);
1710
        $basis = (int) Functions::flattenSingleValue($basis);
1711
1712
        //    Validate
1713
        if ((is_numeric($discount)) && (is_numeric($redemption)) && (is_numeric($basis))) {
0 ignored issues
show
introduced by
The condition is_numeric($basis) is always true.
Loading history...
introduced by
The condition is_numeric($redemption) is always true.
Loading history...
1714 2
            if (($discount <= 0) || ($redemption <= 0)) {
1715
                return Functions::NAN();
1716 2
            }
1717 2
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1718 2
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
1719 2
                //    return date error
1720 2
                return $daysBetweenSettlementAndMaturity;
1721
            }
1722
1723 2
            return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity);
1724 2
        }
1725
1726
        return Functions::VALUE();
1727 2
    }
1728 2
1729
    /**
1730
     * PRICEMAT.
1731
     *
1732
     * Returns the price per $100 face value of a security that pays interest at maturity.
1733 2
     *
1734
     * @param mixed $settlement The security's settlement date.
1735
     *                                The security's settlement date is the date after the issue date when the security is traded to the buyer.
1736
     * @param mixed $maturity The security's maturity date.
1737
     *                                The maturity date is the date when the security expires.
1738
     * @param mixed $issue The security's issue date
1739
     * @param int $rate The security's interest rate at date of issue
1740
     * @param int $yield The security's annual yield
1741
     * @param int $basis The type of day count to use.
1742
     *                                        0 or omitted    US (NASD) 30/360
1743
     *                                        1                Actual/actual
1744
     *                                        2                Actual/360
1745
     *                                        3                Actual/365
1746
     *                                        4                European 30/360
1747
     *
1748
     * @return float|string Result, or a string containing an error
1749
     */
1750
    public static function PRICEMAT($settlement, $maturity, $issue, $rate, $yield, $basis = 0)
1751
    {
1752
        $settlement = Functions::flattenSingleValue($settlement);
1753
        $maturity = Functions::flattenSingleValue($maturity);
1754
        $issue = Functions::flattenSingleValue($issue);
1755
        $rate = Functions::flattenSingleValue($rate);
1756
        $yield = Functions::flattenSingleValue($yield);
1757
        $basis = (int) Functions::flattenSingleValue($basis);
1758
1759
        //    Validate
1760
        if (is_numeric($rate) && is_numeric($yield)) {
1761
            if (($rate <= 0) || ($yield <= 0)) {
1762
                return Functions::NAN();
1763
            }
1764
            $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
1765
            if (!is_numeric($daysPerYear)) {
1766
                return $daysPerYear;
1767
            }
1768
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
1769
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
1770
                //    return date error
1771
                return $daysBetweenIssueAndSettlement;
1772
            }
1773
            $daysBetweenIssueAndSettlement *= $daysPerYear;
1774
            $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis);
1775
            if (!is_numeric($daysBetweenIssueAndMaturity)) {
1776
                //    return date error
1777
                return $daysBetweenIssueAndMaturity;
1778
            }
1779
            $daysBetweenIssueAndMaturity *= $daysPerYear;
1780
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1781
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
1782
                //    return date error
1783
                return $daysBetweenSettlementAndMaturity;
1784
            }
1785
            $daysBetweenSettlementAndMaturity *= $daysPerYear;
1786
1787
            return (100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100)) /
1788
                   (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) -
1789
                   (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100);
1790
        }
1791
1792
        return Functions::VALUE();
1793
    }
1794
1795
    /**
1796
     * PV.
1797
     *
1798
     * Returns the Present Value of a cash flow with constant payments and interest rate (annuities).
1799
     *
1800
     * @param float $rate Interest rate per period
1801
     * @param int $nper Number of periods
1802
     * @param float $pmt Periodic payment (annuity)
1803
     * @param float $fv Future Value
1804
     * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
1805
     *
1806
     * @return float|string Result, or a string containing an error
1807
     */
1808
    public static function PV($rate = 0, $nper = 0, $pmt = 0, $fv = 0, $type = 0)
1809
    {
1810
        $rate = Functions::flattenSingleValue($rate);
1811
        $nper = Functions::flattenSingleValue($nper);
1812
        $pmt = Functions::flattenSingleValue($pmt);
1813
        $fv = Functions::flattenSingleValue($fv);
1814
        $type = Functions::flattenSingleValue($type);
1815
1816
        // Validate parameters
1817
        if ($type != 0 && $type != 1) {
1818 5
            return Functions::NAN();
1819
        }
1820 5
1821 5
        // Calculate
1822 5
        if ($rate !== null && $rate != 0) {
1823 5
            return (-$pmt * (1 + $rate * $type) * ((pow(1 + $rate, $nper) - 1) / $rate) - $fv) / pow(1 + $rate, $nper);
1824 5
        }
1825
1826
        return -$fv - $pmt * $nper;
1827 5
    }
1828
1829
    /**
1830
     * RATE.
1831
     *
1832 5
     * Returns the interest rate per period of an annuity.
1833 5
     * RATE is calculated by iteration and can have zero or more solutions.
1834
     * If the successive results of RATE do not converge to within 0.0000001 after 20 iterations,
1835
     * RATE returns the #NUM! error value.
1836
     *
1837
     * Excel Function:
1838
     *        RATE(nper,pmt,pv[,fv[,type[,guess]]])
1839
     *
1840
     * @category Financial Functions
1841
     *
1842
     * @param float $nper The total number of payment periods in an annuity
1843
     * @param float $pmt The payment made each period and cannot change over the life
1844
     *                                    of the annuity.
1845
     *                                Typically, pmt includes principal and interest but no other
1846
     *                                    fees or taxes.
1847
     * @param float $pv The present value - the total amount that a series of future
1848
     *                                    payments is worth now
1849
     * @param float $fv The future value, or a cash balance you want to attain after
1850
     *                                    the last payment is made. If fv is omitted, it is assumed
1851
     *                                    to be 0 (the future value of a loan, for example, is 0).
1852
     * @param int $type A number 0 or 1 and indicates when payments are due:
1853
     *                                        0 or omitted    At the end of the period.
1854
     *                                        1                At the beginning of the period.
1855
     * @param float $guess Your guess for what the rate will be.
1856
     *                                    If you omit guess, it is assumed to be 10 percent.
1857
     *
1858
     * @return float|string
1859
     */
1860
    public static function RATE($nper, $pmt, $pv, $fv = 0.0, $type = 0, $guess = 0.1)
1861
    {
1862
        $nper = (int) Functions::flattenSingleValue($nper);
1863
        $pmt = Functions::flattenSingleValue($pmt);
1864
        $pv = Functions::flattenSingleValue($pv);
1865
        $fv = ($fv === null) ? 0.0 : Functions::flattenSingleValue($fv);
0 ignored issues
show
introduced by
The condition $fv === null is always false.
Loading history...
1866
        $type = ($type === null) ? 0 : (int) Functions::flattenSingleValue($type);
0 ignored issues
show
introduced by
The condition $type === null is always false.
Loading history...
1867
        $guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess);
0 ignored issues
show
introduced by
The condition $guess === null is always false.
Loading history...
1868
1869
        $rate = $guess;
1870
        // rest of code adapted from python/numpy
1871
        $close = false;
1872
        $iter = 0;
1873
        while (!$close && $iter < self::FINANCIAL_MAX_ITERATIONS) {
1874
            $nextdiff = self::rateNextGuess($rate, $nper, $pmt, $pv, $fv, $type);
1875
            if (!is_numeric($nextdiff)) {
1876
                break;
1877
            }
1878
            $rate1 = $rate - $nextdiff;
1879
            $close = abs($rate1 - $rate) < self::FINANCIAL_PRECISION;
1880
            ++$iter;
1881
            $rate = $rate1;
1882
        }
1883
1884
        return $close ? $rate : Functions::NAN();
1885
    }
1886
1887
    private static function rateNextGuess($rate, $nper, $pmt, $pv, $fv, $type)
1888
    {
1889
        if ($rate == 0) {
1890
            return Functions::NAN();
1891
        }
1892
        $tt1 = pow($rate + 1, $nper);
1893
        $tt2 = pow($rate + 1, $nper - 1);
1894
        $numerator = $fv + $tt1 * $pv + $pmt * ($tt1 - 1) * ($rate * $type + 1) / $rate;
1895
        $denominator = $nper * $tt2 * $pv - $pmt * ($tt1 - 1) * ($rate * $type + 1) / ($rate * $rate)
1896
             + $nper * $pmt * $tt2 * ($rate * $type + 1) / $rate
1897
             + $pmt * ($tt1 - 1) * $type / $rate;
1898
        if ($denominator == 0) {
1899
            return Functions::NAN();
1900
        }
1901
1902
        return $numerator / $denominator;
1903
    }
1904
1905
    /**
1906
     * RECEIVED.
1907
     *
1908
     * Returns the price per $100 face value of a discounted security.
1909
     *
1910
     * @param mixed $settlement The security's settlement date.
1911
     *                                The security settlement date is the date after the issue date when the security is traded to the buyer.
1912
     * @param mixed $maturity The security's maturity date.
1913
     *                                The maturity date is the date when the security expires.
1914
     * @param int $investment The amount invested in the security
1915
     * @param int $discount The security's discount rate
1916
     * @param int $basis The type of day count to use.
1917
     *                                        0 or omitted    US (NASD) 30/360
1918
     *                                        1                Actual/actual
1919
     *                                        2                Actual/360
1920
     *                                        3                Actual/365
1921
     *                                        4                European 30/360
1922
     *
1923
     * @return float|string Result, or a string containing an error
1924
     */
1925
    public static function RECEIVED($settlement, $maturity, $investment, $discount, $basis = 0)
1926
    {
1927
        $settlement = Functions::flattenSingleValue($settlement);
1928
        $maturity = Functions::flattenSingleValue($maturity);
1929
        $investment = (float) Functions::flattenSingleValue($investment);
1930
        $discount = (float) Functions::flattenSingleValue($discount);
1931
        $basis = (int) Functions::flattenSingleValue($basis);
1932
1933
        //    Validate
1934
        if ((is_numeric($investment)) && (is_numeric($discount)) && (is_numeric($basis))) {
0 ignored issues
show
introduced by
The condition is_numeric($basis) is always true.
Loading history...
introduced by
The condition is_numeric($discount) is always true.
Loading history...
1935
            if (($investment <= 0) || ($discount <= 0)) {
1936
                return Functions::NAN();
1937
            }
1938
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
1939
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
1940
                //    return date error
1941
                return $daysBetweenSettlementAndMaturity;
1942
            }
1943
1944
            return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity));
1945
        }
1946
1947
        return Functions::VALUE();
1948
    }
1949
1950
    /**
1951
     * RRI.
1952
     *
1953
     * Calculates the interest rate required for an investment to grow to a specified future value .
1954
     *
1955
     * @param float $nper The number of periods over which the investment is made
1956
     * @param float $pv Present Value
1957
     * @param float $fv Future Value
1958
     *
1959
     * @return float|string Result, or a string containing an error
1960
     */
1961
    public static function RRI($nper = 0, $pv = 0, $fv = 0)
1962
    {
1963
        $nper = Functions::flattenSingleValue($nper);
1964
        $pv = Functions::flattenSingleValue($pv);
1965
        $fv = Functions::flattenSingleValue($fv);
1966
1967
        // Validate parameters
1968
        if (!is_numeric($nper) || !is_numeric($pv) || !is_numeric($fv)) {
1969
            return Functions::VALUE();
1970 6
        } elseif ($nper <= 0.0 || $pv <= 0.0 || $fv < 0.0) {
1971
            return Functions::NAN();
1972 6
        }
1973 6
1974 6
        return pow($fv / $pv, 1 / $nper) - 1;
1975
    }
1976
1977 6
    /**
1978 1
     * SLN.
1979 5
     *
1980 2
     * Returns the straight-line depreciation of an asset for one period
1981
     *
1982
     * @param mixed $cost Initial cost of the asset
1983 3
     * @param mixed $salvage Value at the end of the depreciation
1984
     * @param mixed $life Number of periods over which the asset is depreciated
1985
     *
1986
     * @return float|string Result, or a string containing an error
1987
     */
1988
    public static function SLN($cost, $salvage, $life)
1989
    {
1990
        $cost = Functions::flattenSingleValue($cost);
1991
        $salvage = Functions::flattenSingleValue($salvage);
1992
        $life = Functions::flattenSingleValue($life);
1993
1994
        // Calculate
1995
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life))) {
1996
            if ($life < 0) {
1997 9
                return Functions::NAN();
1998
            }
1999 9
2000 9
            return ($cost - $salvage) / $life;
2001 9
        }
2002
2003
        return Functions::VALUE();
2004 9
    }
2005 8
2006 1
    /**
2007
     * SYD.
2008
     *
2009 7
     * Returns the sum-of-years' digits depreciation of an asset for a specified period.
2010
     *
2011
     * @param mixed $cost Initial cost of the asset
2012 1
     * @param mixed $salvage Value at the end of the depreciation
2013
     * @param mixed $life Number of periods over which the asset is depreciated
2014
     * @param mixed $period Period
2015
     *
2016
     * @return float|string Result, or a string containing an error
2017
     */
2018
    public static function SYD($cost, $salvage, $life, $period)
2019
    {
2020
        $cost = Functions::flattenSingleValue($cost);
2021
        $salvage = Functions::flattenSingleValue($salvage);
2022
        $life = Functions::flattenSingleValue($life);
2023
        $period = Functions::flattenSingleValue($period);
2024
2025
        // Calculate
2026
        if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period))) {
2027 10
            if (($life < 1) || ($period > $life)) {
2028
                return Functions::NAN();
2029 10
            }
2030 10
2031 10
            return (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
2032 10
        }
2033
2034
        return Functions::VALUE();
2035 10
    }
2036 9
2037 1
    /**
2038
     * TBILLEQ.
2039
     *
2040 8
     * Returns the bond-equivalent yield for a Treasury bill.
2041
     *
2042
     * @param mixed $settlement The Treasury bill's settlement date.
2043 1
     *                                The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
2044
     * @param mixed $maturity The Treasury bill's maturity date.
2045
     *                                The maturity date is the date when the Treasury bill expires.
2046
     * @param int $discount The Treasury bill's discount rate
2047
     *
2048
     * @return float|string Result, or a string containing an error
2049
     */
2050
    public static function TBILLEQ($settlement, $maturity, $discount)
2051
    {
2052
        $settlement = Functions::flattenSingleValue($settlement);
2053
        $maturity = Functions::flattenSingleValue($maturity);
2054
        $discount = Functions::flattenSingleValue($discount);
2055
2056
        //    Use TBILLPRICE for validation
2057
        $testValue = self::TBILLPRICE($settlement, $maturity, $discount);
2058
        if (is_string($testValue)) {
0 ignored issues
show
introduced by
The condition is_string($testValue) is always true.
Loading history...
2059
            return $testValue;
2060
        }
2061
2062
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
2063
            return Functions::VALUE();
2064
        }
2065
2066
        if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
2067
            ++$maturity;
2068
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
2069
        } else {
2070
            $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
2071
        }
2072
2073
        return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity);
2074
    }
2075
2076
    /**
2077
     * TBILLPRICE.
2078
     *
2079
     * Returns the yield for a Treasury bill.
2080
     *
2081
     * @param mixed $settlement The Treasury bill's settlement date.
2082
     *                                The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
2083
     * @param mixed $maturity The Treasury bill's maturity date.
2084
     *                                The maturity date is the date when the Treasury bill expires.
2085
     * @param int $discount The Treasury bill's discount rate
2086
     *
2087
     * @return float|string Result, or a string containing an error
2088
     */
2089
    public static function TBILLPRICE($settlement, $maturity, $discount)
2090
    {
2091
        $settlement = Functions::flattenSingleValue($settlement);
2092
        $maturity = Functions::flattenSingleValue($maturity);
2093
        $discount = Functions::flattenSingleValue($discount);
2094
2095
        if (is_string($maturity = DateTime::getDateValue($maturity))) {
2096
            return Functions::VALUE();
2097
        }
2098
2099
        //    Validate
2100
        if (is_numeric($discount)) {
2101
            if ($discount <= 0) {
2102
                return Functions::NAN();
2103
            }
2104
2105
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
2106
                ++$maturity;
2107
                $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
2108
                if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenSettlementAndMaturity) is always true.
Loading history...
2109
                    //    return date error
2110
                    return $daysBetweenSettlementAndMaturity;
2111
                }
2112
            } else {
2113
                $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
2114
            }
2115
2116
            if ($daysBetweenSettlementAndMaturity > 360) {
2117
                return Functions::NAN();
2118
            }
2119
2120
            $price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360));
2121
            if ($price <= 0) {
2122
                return Functions::NAN();
2123
            }
2124
2125
            return $price;
2126
        }
2127
2128
        return Functions::VALUE();
2129
    }
2130
2131
    /**
2132
     * TBILLYIELD.
2133
     *
2134
     * Returns the yield for a Treasury bill.
2135
     *
2136
     * @param mixed $settlement The Treasury bill's settlement date.
2137
     *                                The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
2138
     * @param mixed $maturity The Treasury bill's maturity date.
2139
     *                                The maturity date is the date when the Treasury bill expires.
2140
     * @param int $price The Treasury bill's price per $100 face value
2141
     *
2142
     * @return float|mixed|string
2143
     */
2144
    public static function TBILLYIELD($settlement, $maturity, $price)
2145
    {
2146
        $settlement = Functions::flattenSingleValue($settlement);
2147
        $maturity = Functions::flattenSingleValue($maturity);
2148
        $price = Functions::flattenSingleValue($price);
2149
2150
        //    Validate
2151
        if (is_numeric($price)) {
2152
            if ($price <= 0) {
2153
                return Functions::NAN();
2154
            }
2155
2156
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
2157
                ++$maturity;
2158
                $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
2159
                if (!is_numeric($daysBetweenSettlementAndMaturity)) {
0 ignored issues
show
introduced by
The condition is_numeric($daysBetweenSettlementAndMaturity) is always true.
Loading history...
2160
                    //    return date error
2161
                    return $daysBetweenSettlementAndMaturity;
2162
                }
2163
            } else {
2164
                $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
2165
            }
2166
2167
            if ($daysBetweenSettlementAndMaturity > 360) {
2168
                return Functions::NAN();
2169
            }
2170
2171
            return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity);
2172
        }
2173
2174
        return Functions::VALUE();
2175
    }
2176
2177
    private static function bothNegAndPos($neg, $pos)
2178
    {
2179
        return $neg && $pos;
2180
    }
2181
2182
    private static function xirrPart2(&$values)
2183
    {
2184
        $valCount = count($values);
2185
        $foundpos = false;
2186
        $foundneg = false;
2187
        for ($i = 0; $i < $valCount; ++$i) {
2188
            $fld = $values[$i];
2189
            if (!is_numeric($fld)) {
2190
                return Functions::VALUE();
2191
            } elseif ($fld > 0) {
2192
                $foundpos = true;
2193
            } elseif ($fld < 0) {
2194
                $foundneg = true;
2195
            }
2196
        }
2197
        if (!self::bothNegAndPos($foundneg, $foundpos)) {
2198
            return Functions::NAN();
2199
        }
2200
2201
        return '';
2202
    }
2203 8
2204
    private static function xirrPart1(&$values, &$dates)
2205 8
    {
2206
        if ((!is_array($values)) && (!is_array($dates))) {
2207
            return Functions::NA();
2208 8
        }
2209 8
        $values = Functions::flattenArray($values);
2210 8
        $dates = Functions::flattenArray($dates);
2211 8
        if (count($values) != count($dates)) {
2212 1
            return Functions::NAN();
2213
        }
2214
2215 7
        $datesCount = count($dates);
2216 7
        for ($i = 0; $i < $datesCount; ++$i) {
2217 7
            $dates[$i] = DateTime::getDateValue($dates[$i]);
2218 7
            if (!is_numeric($dates[$i])) {
2219 1
                return Functions::VALUE();
2220
            }
2221
        }
2222 6
2223 1
        return self::xirrPart2($values);
2224
    }
2225
2226
    private static function xirrPart3($values, $dates, $x1, $x2)
2227 5
    {
2228 5
        $f = self::xnpvOrdered($x1, $values, $dates, false);
2229 5
        if ($f < 0.0) {
2230 5
            $rtb = $x1;
2231 2
            $dx = $x2 - $x1;
2232
        } else {
2233 3
            $rtb = $x2;
2234 3
            $dx = $x1 - $x2;
2235
        }
2236
2237 3
        $rslt = Functions::VALUE();
2238 3
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
2239 2
            $dx *= 0.5;
2240 2
            $x_mid = $rtb + $dx;
2241
            $f_mid = self::xnpvOrdered($x_mid, $values, $dates, false);
2242
            if ($f_mid <= 0.0) {
2243 2
                $rtb = $x_mid;
2244
            }
2245
            if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
2246 3
                $rslt = $x_mid;
2247 1
2248
                break;
2249
            }
2250 2
        }
2251 2
2252 2
        return $rslt;
2253 2
    }
2254
2255
    /**
2256
     * XIRR.
2257
     *
2258
     * Returns the internal rate of return for a schedule of cash flows that is not necessarily periodic.
2259 2
     *
2260 2
     * Excel Function:
2261 2
     *        =XIRR(values,dates,guess)
2262 2
     *
2263 2
     * @param float[] $values     A series of cash flow payments
2264 2
     *                                The series of values must contain at least one positive value & one negative value
2265
     * @param mixed[] $dates      A series of payment dates
2266 2
     *                                The first payment date indicates the beginning of the schedule of payments
2267 2
     *                                All other dates must be later than this date, but they may occur in any order
2268
     * @param float $guess        An optional guess at the expected answer
2269
     *
2270
     * @return float|mixed|string
2271
     */
2272
    public static function XIRR($values, $dates, $guess = 0.1)
2273
    {
2274
        $rslt = self::xirrPart1($values, $dates);
2275
        if ($rslt) {
2276
            return $rslt;
2277
        }
2278
2279
        // create an initial range, with a root somewhere between 0 and guess
2280
        $guess = Functions::flattenSingleValue($guess);
2281
        $x1 = 0.0;
2282
        $x2 = $guess ? $guess : 0.1;
2283
        $f1 = self::xnpvOrdered($x1, $values, $dates, false);
2284
        $f2 = self::xnpvOrdered($x2, $values, $dates, false);
2285
        $found = false;
2286
        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
2287
            if (!is_numeric($f1) || !is_numeric($f2)) {
2288
                break;
2289
            }
2290
            if (($f1 * $f2) < 0.0) {
2291
                $found = true;
2292
2293
                break;
2294 5
            } elseif (abs($f1) < abs($f2)) {
2295
                $f1 = self::xnpvOrdered($x1 += 1.6 * ($x1 - $x2), $values, $dates, false);
2296 5
            } else {
2297 5
                $f2 = self::xnpvOrdered($x2 += 1.6 * ($x2 - $x1), $values, $dates, false);
2298
            }
2299
        }
2300 5
        if (!$found) {
2301
            return Functions::NAN();
2302
        }
2303 5
2304 5
        return self::xirrPart3($values, $dates, $x1, $x2);
2305 5
    }
2306 5
2307
    /**
2308
     * XNPV.
2309 5
     *
2310 2
     * Returns the net present value for a schedule of cash flows that is not necessarily periodic.
2311
     * To calculate the net present value for a series of cash flows that is periodic, use the NPV function.
2312
     *
2313 3
     * Excel Function:
2314 3
     *        =XNPV(rate,values,dates)
2315 3
     *
2316
     * @param float $rate the discount rate to apply to the cash flows
2317
     * @param float[] $values     A series of cash flows that corresponds to a schedule of payments in dates.
2318 3
     *                                         The first payment is optional and corresponds to a cost or payment that occurs at the beginning of the investment.
2319
     *                                         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.
2320
     *                                         The series of values must contain at least one positive value and one negative value.
2321 3
     * @param mixed[] $dates      A schedule of payment dates that corresponds to the cash flow payments.
2322
     *                                         The first payment date indicates the beginning of the schedule of payments.
2323
     *                                         All other dates must be later than this date, but they may occur in any order.
2324
     *
2325
     * @return float|mixed|string
2326
     */
2327
    public static function XNPV($rate, $values, $dates)
2328
    {
2329
        return self::xnpvOrdered($rate, $values, $dates, true);
2330
    }
2331
2332
    private static function validateXnpv($rate, $values, $dates)
2333
    {
2334
        if (!is_numeric($rate)) {
2335
            return Functions::VALUE();
2336
        }
2337
        $valCount = count($values);
2338
        if ($valCount != count($dates)) {
2339
            return Functions::NAN();
2340
        }
2341
        if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) {
2342
            return Functions::NAN();
2343
        }
2344
        $date0 = DateTime::getDateValue($dates[0]);
2345
        if (is_string($date0)) {
2346
            return Functions::VALUE();
2347
        }
2348
2349
        return '';
2350
    }
2351
2352
    private static function xnpvOrdered($rate, $values, $dates, $ordered = true)
2353
    {
2354
        $rate = Functions::flattenSingleValue($rate);
2355
        $values = Functions::flattenArray($values);
2356
        $dates = Functions::flattenArray($dates);
2357
        $valCount = count($values);
2358
        $date0 = DateTime::getDateValue($dates[0]);
2359
        $rslt = self::validateXnpv($rate, $values, $dates);
2360
        if ($rslt) {
2361
            return $rslt;
2362
        }
2363
        $xnpv = 0.0;
2364
        for ($i = 0; $i < $valCount; ++$i) {
2365
            if (!is_numeric($values[$i])) {
2366
                return Functions::VALUE();
2367
            }
2368
            $datei = DateTime::getDateValue($dates[$i]);
2369
            if (is_string($datei)) {
2370
                return Functions::VALUE();
2371
            }
2372
            if ($date0 > $datei) {
2373
                $dif = $ordered ? Functions::NAN() : -DateTime::DATEDIF($datei, $date0, 'd');
2374
            } else {
2375
                $dif = DateTime::DATEDIF($date0, $datei, 'd');
2376
            }
2377
            if (!is_numeric($dif)) {
2378
                return $dif;
2379
            }
2380
            $xnpv += $values[$i] / pow(1 + $rate, $dif / 365);
2381
        }
2382
2383
        return is_finite($xnpv) ? $xnpv : Functions::VALUE();
2384
    }
2385
2386
    /**
2387
     * YIELDDISC.
2388
     *
2389
     * Returns the annual yield of a security that pays interest at maturity.
2390
     *
2391
     * @param mixed $settlement The security's settlement date.
2392
     *                                    The security's settlement date is the date after the issue date when the security is traded to the buyer.
2393
     * @param mixed $maturity The security's maturity date.
2394
     *                                    The maturity date is the date when the security expires.
2395
     * @param int $price The security's price per $100 face value
2396
     * @param int $redemption The security's redemption value per $100 face value
2397
     * @param int $basis The type of day count to use.
2398
     *                                        0 or omitted    US (NASD) 30/360
2399
     *                                        1                Actual/actual
2400
     *                                        2                Actual/360
2401
     *                                        3                Actual/365
2402
     *                                        4                European 30/360
2403
     *
2404
     * @return float|string Result, or a string containing an error
2405
     */
2406
    public static function YIELDDISC($settlement, $maturity, $price, $redemption, $basis = 0)
2407
    {
2408
        $settlement = Functions::flattenSingleValue($settlement);
2409
        $maturity = Functions::flattenSingleValue($maturity);
2410
        $price = Functions::flattenSingleValue($price);
2411
        $redemption = Functions::flattenSingleValue($redemption);
2412
        $basis = (int) Functions::flattenSingleValue($basis);
2413
2414
        //    Validate
2415
        if (is_numeric($price) && is_numeric($redemption)) {
2416
            if (($price <= 0) || ($redemption <= 0)) {
2417
                return Functions::NAN();
2418
            }
2419
            $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
2420
            if (!is_numeric($daysPerYear)) {
2421
                return $daysPerYear;
2422
            }
2423
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
2424
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
2425
                //    return date error
2426
                return $daysBetweenSettlementAndMaturity;
2427
            }
2428
            $daysBetweenSettlementAndMaturity *= $daysPerYear;
2429
2430
            return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity);
2431
        }
2432
2433
        return Functions::VALUE();
2434
    }
2435
2436
    /**
2437
     * YIELDMAT.
2438
     *
2439
     * Returns the annual yield of a security that pays interest at maturity.
2440
     *
2441
     * @param mixed $settlement The security's settlement date.
2442
     *                                   The security's settlement date is the date after the issue date when the security is traded to the buyer.
2443
     * @param mixed $maturity The security's maturity date.
2444
     *                                   The maturity date is the date when the security expires.
2445
     * @param mixed $issue The security's issue date
2446
     * @param int $rate The security's interest rate at date of issue
2447
     * @param int $price The security's price per $100 face value
2448
     * @param int $basis The type of day count to use.
2449
     *                                        0 or omitted    US (NASD) 30/360
2450
     *                                        1                Actual/actual
2451
     *                                        2                Actual/360
2452
     *                                        3                Actual/365
2453
     *                                        4                European 30/360
2454
     *
2455
     * @return float|string Result, or a string containing an error
2456
     */
2457
    public static function YIELDMAT($settlement, $maturity, $issue, $rate, $price, $basis = 0)
2458
    {
2459
        $settlement = Functions::flattenSingleValue($settlement);
2460
        $maturity = Functions::flattenSingleValue($maturity);
2461
        $issue = Functions::flattenSingleValue($issue);
2462
        $rate = Functions::flattenSingleValue($rate);
2463
        $price = Functions::flattenSingleValue($price);
2464
        $basis = (int) Functions::flattenSingleValue($basis);
2465
2466
        //    Validate
2467
        if (is_numeric($rate) && is_numeric($price)) {
2468
            if (($rate <= 0) || ($price <= 0)) {
2469
                return Functions::NAN();
2470
            }
2471
            $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
2472
            if (!is_numeric($daysPerYear)) {
2473
                return $daysPerYear;
2474
            }
2475
            $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
2476
            if (!is_numeric($daysBetweenIssueAndSettlement)) {
2477
                //    return date error
2478
                return $daysBetweenIssueAndSettlement;
2479
            }
2480
            $daysBetweenIssueAndSettlement *= $daysPerYear;
2481
            $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis);
2482
            if (!is_numeric($daysBetweenIssueAndMaturity)) {
2483
                //    return date error
2484
                return $daysBetweenIssueAndMaturity;
2485
            }
2486
            $daysBetweenIssueAndMaturity *= $daysPerYear;
2487
            $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis);
2488
            if (!is_numeric($daysBetweenSettlementAndMaturity)) {
2489
                //    return date error
2490
                return $daysBetweenSettlementAndMaturity;
2491
            }
2492
            $daysBetweenSettlementAndMaturity *= $daysPerYear;
2493
2494
            return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) /
2495
                   (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) *
2496
                   ($daysPerYear / $daysBetweenSettlementAndMaturity);
2497
        }
2498
2499
        return Functions::VALUE();
2500
    }
2501
}
2502