Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
 
3
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
4
 
5
use DateTime;
6
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
7
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
8
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
9
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
10
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
11
use PhpOffice\PhpSpreadsheet\Shared\Date;
12
 
13
class Coupons
14
{
15
    private const PERIOD_DATE_PREVIOUS = false;
16
    private const PERIOD_DATE_NEXT = true;
17
 
18
    /**
19
     * COUPDAYBS.
20
     *
21
     * Returns the number of days from the beginning of the coupon period to the settlement date.
22
     *
23
     * Excel Function:
24
     *        COUPDAYBS(settlement,maturity,frequency[,basis])
25
     *
26
     * @param mixed $settlement The security's settlement date.
27
     *                              The security settlement date is the date after the issue
28
     *                                  date when the security is traded to the buyer.
29
     * @param mixed $maturity The security's maturity date.
30
     *                            The maturity date is the date when the security expires.
31
     * @param mixed $frequency The number of coupon payments per year (int).
32
     *                             Valid frequency values are:
33
     *                               1    Annual
34
     *                               2    Semi-Annual
35
     *                               4    Quarterly
36
     * @param mixed $basis The type of day count to use (int).
37
     *                         0 or omitted    US (NASD) 30/360
38
     *                         1               Actual/actual
39
     *                         2               Actual/360
40
     *                         3               Actual/365
41
     *                         4               European 30/360
42
     */
43
    public static function COUPDAYBS(
44
        mixed $settlement,
45
        mixed $maturity,
46
        mixed $frequency,
47
        mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
48
    ): string|int|float {
49
        $settlement = Functions::flattenSingleValue($settlement);
50
        $maturity = Functions::flattenSingleValue($maturity);
51
        $frequency = Functions::flattenSingleValue($frequency);
52
        $basis = ($basis === null)
53
            ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
54
            : Functions::flattenSingleValue($basis);
55
 
56
        try {
57
            $settlement = FinancialValidations::validateSettlementDate($settlement);
58
            $maturity = FinancialValidations::validateMaturityDate($maturity);
59
            self::validateCouponPeriod($settlement, $maturity);
60
            $frequency = FinancialValidations::validateFrequency($frequency);
61
            $basis = FinancialValidations::validateBasis($basis);
62
        } catch (Exception $e) {
63
            return $e->getMessage();
64
        }
65
 
66
        $daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
67
        if (is_string($daysPerYear)) {
68
            return ExcelError::VALUE();
69
        }
70
        $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
71
 
72
        if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL) {
73
            return abs((float) DateTimeExcel\Days::between($prev, $settlement));
74
        }
75
 
76
        return (float) DateTimeExcel\YearFrac::fraction($prev, $settlement, $basis) * $daysPerYear;
77
    }
78
 
79
    /**
80
     * COUPDAYS.
81
     *
82
     * Returns the number of days in the coupon period that contains the settlement date.
83
     *
84
     * Excel Function:
85
     *        COUPDAYS(settlement,maturity,frequency[,basis])
86
     *
87
     * @param mixed $settlement The security's settlement date.
88
     *                              The security settlement date is the date after the issue
89
     *                                  date when the security is traded to the buyer.
90
     * @param mixed $maturity The security's maturity date.
91
     *                            The maturity date is the date when the security expires.
92
     * @param mixed $frequency The number of coupon payments per year.
93
     *                             Valid frequency values are:
94
     *                               1    Annual
95
     *                               2    Semi-Annual
96
     *                               4    Quarterly
97
     * @param mixed $basis The type of day count to use (int).
98
     *                         0 or omitted    US (NASD) 30/360
99
     *                         1               Actual/actual
100
     *                         2               Actual/360
101
     *                         3               Actual/365
102
     *                         4               European 30/360
103
     */
104
    public static function COUPDAYS(
105
        mixed $settlement,
106
        mixed $maturity,
107
        mixed $frequency,
108
        mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
109
    ): string|int|float {
110
        $settlement = Functions::flattenSingleValue($settlement);
111
        $maturity = Functions::flattenSingleValue($maturity);
112
        $frequency = Functions::flattenSingleValue($frequency);
113
        $basis = ($basis === null)
114
            ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
115
            : Functions::flattenSingleValue($basis);
116
 
117
        try {
118
            $settlement = FinancialValidations::validateSettlementDate($settlement);
119
            $maturity = FinancialValidations::validateMaturityDate($maturity);
120
            self::validateCouponPeriod($settlement, $maturity);
121
            $frequency = FinancialValidations::validateFrequency($frequency);
122
            $basis = FinancialValidations::validateBasis($basis);
123
        } catch (Exception $e) {
124
            return $e->getMessage();
125
        }
126
 
127
        switch ($basis) {
128
            case FinancialConstants::BASIS_DAYS_PER_YEAR_365:
129
                // Actual/365
130
                return 365 / $frequency;
131
            case FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL:
132
                // Actual/actual
133
                if ($frequency == FinancialConstants::FREQUENCY_ANNUAL) {
134
                    $daysPerYear = (int) Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
135
 
136
                    return $daysPerYear / $frequency;
137
                }
138
                $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
139
                $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
140
 
141
                return $next - $prev;
142
            default:
143
                // US (NASD) 30/360, Actual/360 or European 30/360
144
                return 360 / $frequency;
145
        }
146
    }
147
 
148
    /**
149
     * COUPDAYSNC.
150
     *
151
     * Returns the number of days from the settlement date to the next coupon date.
152
     *
153
     * Excel Function:
154
     *        COUPDAYSNC(settlement,maturity,frequency[,basis])
155
     *
156
     * @param mixed $settlement The security's settlement date.
157
     *                              The security settlement date is the date after the issue
158
     *                                  date when the security is traded to the buyer.
159
     * @param mixed $maturity The security's maturity date.
160
     *                            The maturity date is the date when the security expires.
161
     * @param mixed $frequency The number of coupon payments per year.
162
     *                             Valid frequency values are:
163
     *                               1    Annual
164
     *                               2    Semi-Annual
165
     *                               4    Quarterly
166
     * @param mixed $basis The type of day count to use (int) .
167
     *                         0 or omitted    US (NASD) 30/360
168
     *                         1               Actual/actual
169
     *                         2               Actual/360
170
     *                         3               Actual/365
171
     *                         4               European 30/360
172
     */
173
    public static function COUPDAYSNC(
174
        mixed $settlement,
175
        mixed $maturity,
176
        mixed $frequency,
177
        mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
178
    ): string|float {
179
        $settlement = Functions::flattenSingleValue($settlement);
180
        $maturity = Functions::flattenSingleValue($maturity);
181
        $frequency = Functions::flattenSingleValue($frequency);
182
        $basis = ($basis === null)
183
            ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
184
            : Functions::flattenSingleValue($basis);
185
 
186
        try {
187
            $settlement = FinancialValidations::validateSettlementDate($settlement);
188
            $maturity = FinancialValidations::validateMaturityDate($maturity);
189
            self::validateCouponPeriod($settlement, $maturity);
190
            $frequency = FinancialValidations::validateFrequency($frequency);
191
            $basis = FinancialValidations::validateBasis($basis);
192
        } catch (Exception $e) {
193
            return $e->getMessage();
194
        }
195
 
196
        /** @var int $daysPerYear */
197
        $daysPerYear = Helpers::daysPerYear(Functions::Scalar(DateTimeExcel\DateParts::year($settlement)), $basis);
198
        $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
199
 
200
        if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_NASD) {
201
            $settlementDate = Date::excelToDateTimeObject($settlement);
202
            $settlementEoM = Helpers::isLastDayOfMonth($settlementDate);
203
            if ($settlementEoM) {
204
                ++$settlement;
205
            }
206
        }
207
 
208
        return (float) DateTimeExcel\YearFrac::fraction($settlement, $next, $basis) * $daysPerYear;
209
    }
210
 
211
    /**
212
     * COUPNCD.
213
     *
214
     * Returns the next coupon date after the settlement date.
215
     *
216
     * Excel Function:
217
     *        COUPNCD(settlement,maturity,frequency[,basis])
218
     *
219
     * @param mixed $settlement The security's settlement date.
220
     *                              The security settlement date is the date after the issue
221
     *                                  date when the security is traded to the buyer.
222
     * @param mixed $maturity The security's maturity date.
223
     *                            The maturity date is the date when the security expires.
224
     * @param mixed $frequency The number of coupon payments per year.
225
     *                             Valid frequency values are:
226
     *                               1    Annual
227
     *                               2    Semi-Annual
228
     *                               4    Quarterly
229
     * @param mixed $basis The type of day count to use (int).
230
     *                         0 or omitted    US (NASD) 30/360
231
     *                         1               Actual/actual
232
     *                         2               Actual/360
233
     *                         3               Actual/365
234
     *                         4               European 30/360
235
     *
236
     * @return float|string Excel date/time serial value or error message
237
     */
238
    public static function COUPNCD(
239
        mixed $settlement,
240
        mixed $maturity,
241
        mixed $frequency,
242
        mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
243
    ): string|float {
244
        $settlement = Functions::flattenSingleValue($settlement);
245
        $maturity = Functions::flattenSingleValue($maturity);
246
        $frequency = Functions::flattenSingleValue($frequency);
247
        $basis = ($basis === null)
248
            ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
249
            : Functions::flattenSingleValue($basis);
250
 
251
        try {
252
            $settlement = FinancialValidations::validateSettlementDate($settlement);
253
            $maturity = FinancialValidations::validateMaturityDate($maturity);
254
            self::validateCouponPeriod($settlement, $maturity);
255
            $frequency = FinancialValidations::validateFrequency($frequency);
256
            FinancialValidations::validateBasis($basis);
257
        } catch (Exception $e) {
258
            return $e->getMessage();
259
        }
260
 
261
        return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
262
    }
263
 
264
    /**
265
     * COUPNUM.
266
     *
267
     * Returns the number of coupons payable between the settlement date and maturity date,
268
     * rounded up to the nearest whole coupon.
269
     *
270
     * Excel Function:
271
     *        COUPNUM(settlement,maturity,frequency[,basis])
272
     *
273
     * @param mixed $settlement The security's settlement date.
274
     *                              The security settlement date is the date after the issue
275
     *                                  date when the security is traded to the buyer.
276
     * @param mixed $maturity The security's maturity date.
277
     *                            The maturity date is the date when the security expires.
278
     * @param mixed $frequency The number of coupon payments per year.
279
     *                             Valid frequency values are:
280
     *                               1    Annual
281
     *                               2    Semi-Annual
282
     *                               4    Quarterly
283
     * @param mixed $basis The type of day count to use (int).
284
     *                         0 or omitted    US (NASD) 30/360
285
     *                         1               Actual/actual
286
     *                         2               Actual/360
287
     *                         3               Actual/365
288
     *                         4               European 30/360
289
     */
290
    public static function COUPNUM(
291
        mixed $settlement,
292
        mixed $maturity,
293
        mixed $frequency,
294
        mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
295
    ): string|int {
296
        $settlement = Functions::flattenSingleValue($settlement);
297
        $maturity = Functions::flattenSingleValue($maturity);
298
        $frequency = Functions::flattenSingleValue($frequency);
299
        $basis = ($basis === null)
300
            ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
301
            : Functions::flattenSingleValue($basis);
302
 
303
        try {
304
            $settlement = FinancialValidations::validateSettlementDate($settlement);
305
            $maturity = FinancialValidations::validateMaturityDate($maturity);
306
            self::validateCouponPeriod($settlement, $maturity);
307
            $frequency = FinancialValidations::validateFrequency($frequency);
308
            FinancialValidations::validateBasis($basis);
309
        } catch (Exception $e) {
310
            return $e->getMessage();
311
        }
312
 
313
        $yearsBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction(
314
            $settlement,
315
            $maturity,
316
            FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
317
        );
318
 
319
        return (int) ceil((float) $yearsBetweenSettlementAndMaturity * $frequency);
320
    }
321
 
322
    /**
323
     * COUPPCD.
324
     *
325
     * Returns the previous coupon date before the settlement date.
326
     *
327
     * Excel Function:
328
     *        COUPPCD(settlement,maturity,frequency[,basis])
329
     *
330
     * @param mixed $settlement The security's settlement date.
331
     *                              The security settlement date is the date after the issue
332
     *                              date when the security is traded to the buyer.
333
     * @param mixed $maturity The security's maturity date.
334
     *                            The maturity date is the date when the security expires.
335
     * @param mixed $frequency The number of coupon payments per year.
336
     *                             Valid frequency values are:
337
     *                               1    Annual
338
     *                               2    Semi-Annual
339
     *                               4    Quarterly
340
     * @param mixed $basis The type of day count to use (int).
341
     *                         0 or omitted    US (NASD) 30/360
342
     *                         1               Actual/actual
343
     *                         2               Actual/360
344
     *                         3               Actual/365
345
     *                         4               European 30/360
346
     *
347
     * @return float|string Excel date/time serial value or error message
348
     */
349
    public static function COUPPCD(
350
        mixed $settlement,
351
        mixed $maturity,
352
        mixed $frequency,
353
        mixed $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
354
    ): string|float {
355
        $settlement = Functions::flattenSingleValue($settlement);
356
        $maturity = Functions::flattenSingleValue($maturity);
357
        $frequency = Functions::flattenSingleValue($frequency);
358
        $basis = ($basis === null)
359
            ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
360
            : Functions::flattenSingleValue($basis);
361
 
362
        try {
363
            $settlement = FinancialValidations::validateSettlementDate($settlement);
364
            $maturity = FinancialValidations::validateMaturityDate($maturity);
365
            self::validateCouponPeriod($settlement, $maturity);
366
            $frequency = FinancialValidations::validateFrequency($frequency);
367
            FinancialValidations::validateBasis($basis);
368
        } catch (Exception $e) {
369
            return $e->getMessage();
370
        }
371
 
372
        return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
373
    }
374
 
375
    private static function monthsDiff(DateTime $result, int $months, string $plusOrMinus, int $day, bool $lastDayFlag): void
376
    {
377
        $result->setDate((int) $result->format('Y'), (int) $result->format('m'), 1);
378
        $result->modify("$plusOrMinus $months months");
379
        $daysInMonth = (int) $result->format('t');
380
        $result->setDate((int) $result->format('Y'), (int) $result->format('m'), $lastDayFlag ? $daysInMonth : min($day, $daysInMonth));
381
    }
382
 
383
    private static function couponFirstPeriodDate(float $settlement, float $maturity, int $frequency, bool $next): float
384
    {
385
        $months = 12 / $frequency;
386
 
387
        $result = Date::excelToDateTimeObject($maturity);
388
        $day = (int) $result->format('d');
389
        $lastDayFlag = Helpers::isLastDayOfMonth($result);
390
 
391
        while ($settlement < Date::PHPToExcel($result)) {
392
            self::monthsDiff($result, $months, '-', $day, $lastDayFlag);
393
        }
394
        if ($next === true) {
395
            self::monthsDiff($result, $months, '+', $day, $lastDayFlag);
396
        }
397
 
398
        return (float) Date::PHPToExcel($result);
399
    }
400
 
401
    private static function validateCouponPeriod(float $settlement, float $maturity): void
402
    {
403
        if ($settlement >= $maturity) {
404
            throw new Exception(ExcelError::NAN());
405
        }
406
    }
407
}