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]; |
|
|
|
|
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); |
|
|
|
|
145
|
|
|
$settlement = Functions::flattenSingleValue($settlement); |
146
|
|
|
$rate = Functions::flattenSingleValue($rate); |
147
|
|
|
$par = ($par === null) ? 1000 : Functions::flattenSingleValue($par); |
|
|
|
|
148
|
|
|
$frequency = ($frequency === null) ? 1 : Functions::flattenSingleValue($frequency); |
|
|
|
|
149
|
7 |
|
$basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis); |
|
|
|
|
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); |
|
|
|
|
200
|
|
|
$basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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)) { |
|
|
|
|
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)) { |
|
|
|
|
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)) { |
|
|
|
|
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))) { |
|
|
|
|
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); |
|
|
|
|
1866
|
|
|
$type = ($type === null) ? 0 : (int) Functions::flattenSingleValue($type); |
|
|
|
|
1867
|
|
|
$guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess); |
|
|
|
|
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))) { |
|
|
|
|
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)) { |
|
|
|
|
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)) { |
|
|
|
|
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)) { |
|
|
|
|
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
|
|
|
|