Coverage for rivapy/instruments/bond_specifications.py: 90%
247 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-05 14:27 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-05 14:27 +0000
1from abc import abstractmethod as _abstractmethod
2from typing import List as _List, Union as _Union, Tuple
3import numpy as np
4from datetime import datetime, date, timedelta
5from holidays import HolidayBase as _HolidayBase, ECB as _ECB
6from rivapy.tools.datetools import Period, Schedule, _date_to_datetime, _datetime_to_date_list, _term_to_period
7from rivapy.tools.enums import DayCounterType, RollConvention, SecuritizationLevel, Currency, Rating
8from rivapy.tools._validators import _check_positivity, _check_start_before_end, _string_to_calendar, _is_ascending_date_list
9import rivapy.tools.interfaces as interfaces
10from rivapy.tools.datetools import Period, Schedule
13class BondBaseSpecification(interfaces.FactoryObject):
15 def __init__(self,
16 obj_id: str,
17 issue_date: _Union[date, datetime],
18 maturity_date: _Union[date, datetime],
19 currency: _Union[Currency, str] = 'EUR',
20 notional: float = 100.0,
21 issuer: str = None,
22 securitization_level: _Union[SecuritizationLevel, str] = SecuritizationLevel.NONE,
23 rating: _Union[Rating, str] = Rating.NONE):
24 """Base bond specification.
26 Args:
27 obj_id (str): (Preferably) Unique label of the bond, e.g. ISIN.
28 issue_date (_Union[date, datetime]): Date of bond issuance.
29 maturity_date (_Union[date, datetime]): Bond's maturity/expiry date. Must lie after the issue_date.
30 currency (str, optional): Currency as alphabetic, Defaults to 'EUR'.
31 notional (float, optional): Bond's notional/face value. Must be positive. Defaults to 100.0.
32 issuer (str, optional): Name/id of issuer. Defaults to None.
33 securitization_level (_Union[SecuritizationLevel, str], optional): Securitization level. Defaults to None.
34 rating (_Union[Rating, str]): Paper rating.
35 """
36 self.obj_id = obj_id
37 if issuer is not None:
38 self.issuer = issuer
39 if securitization_level is not None:
40 self.securitization_level = securitization_level
41 self.issue_date = issue_date
42 self.maturity_date = maturity_date
43 self.currency = currency
44 self.notional = notional
45 self.rating = Rating.to_string(rating)
46 # validate dates
47 self._validate_derived_issued_instrument()
49 @staticmethod
50 def _create_sample(n_samples: int, seed: int = None, ref_date = None,
51 issuers: _List[str]= None,
52 sec_levels: _List[str]=None,
53 currencies: _List[str]= None)->_List[dict]:
54 if seed is not None:
55 np.random.seed(seed)
56 if ref_date is None:
57 ref_date = datetime.now()
58 else:
59 ref_date = _date_to_datetime(ref_date)
60 if issuers is None:
61 issuers = ['Issuer_'+str(i) for i in range(int(n_samples/2))]
62 result = []
63 if currencies is None:
64 currencies = list(Currency)
65 if sec_levels is None:
66 sec_levels = list(SecuritizationLevel)
67 for _ in range(n_samples):
68 days = int(15.0*365.0*np.random.beta(2.0,2.0)) + 1
69 issue_date = ref_date + timedelta(days=np.random.randint(low=-365, high=0))
70 result.append(
71 {
72 'issue_date': issue_date,
73 'maturity_date': ref_date + timedelta(days=days),
74 'currency':np.random.choice(currencies),
75 'notional': np.random.choice([100.0, 1000.0, 10_000.0, 100_0000.0]),
76 'issuer': np.random.choice(issuers),
77 'securitization_level': np.random.choice(sec_levels)
78 }
79 )
80 return result
82 def _validate_derived_issued_instrument(self):
83 self.__issue_date, self.__maturity_date = _check_start_before_end(self.__issue_date, self.__maturity_date)
85 def _to_dict(self)->dict:
86 result = {
87 'obj_id': self.obj_id, 'issuer':self.issuer,
88 'securitization_level': self.securitization_level,
89 'issue_date': self.issue_date, 'maturity_date':self.maturity_date,
90 'currency': self.currency, 'notional': self.notional,
91 'rating': self.rating
92 }
93 return result
95 #region properties
97 @property
98 def issuer(self) -> str:
99 """
100 Getter for instrument's issuer.
102 Returns:
103 str: Instrument's issuer.
104 """
105 return self.__issuer
107 @issuer.setter
108 def issuer(self, issuer: str):
109 """
110 Setter for instrument's issuer.
112 Args:
113 issuer(str): Issuer of the instrument.
114 """
115 self.__issuer = issuer
117 @property
118 def rating(self)->str:
119 return self.__rating
121 @rating.setter
122 def rating(self, rating:_Union[Rating, str])->str:
123 self.__rating = Rating.to_string(rating)
125 @property
126 def securitization_level(self) -> str:
127 """
128 Getter for instrument's securitisation level.
130 Returns:
131 str: Instrument's securitisation level.
132 """
133 return self.__securitization_level
135 @securitization_level.setter
136 def securitization_level(self, securitisation_level: _Union[SecuritizationLevel, str]):
137 self.__securitization_level = SecuritizationLevel.to_string(securitisation_level)
139 @property
140 def issue_date(self) -> date:
141 """
142 Getter for bond's issue date.
144 Returns:
145 date: Bond's issue date.
146 """
147 return self.__issue_date
149 @issue_date.setter
150 def issue_date(self, issue_date: _Union[datetime, date]):
151 """
152 Setter for bond's issue date.
154 Args:
155 issue_date (Union[datetime, date]): Bond's issue date.
156 """
157 self.__issue_date = _date_to_datetime(issue_date)
159 @property
160 def maturity_date(self) -> date:
161 """
162 Getter for bond's maturity date.
164 Returns:
165 date: Bond's maturity date.
166 """
167 return self.__maturity_date
169 @maturity_date.setter
170 def maturity_date(self, maturity_date: _Union[datetime, date]):
171 """
172 Setter for bond's maturity date.
174 Args:
175 maturity_date (Union[datetime, date]): Bond's maturity date.
176 """
177 self.__maturity_date = _date_to_datetime(maturity_date)
179 @property
180 def currency(self) -> str:
181 """
182 Getter for bond's currency.
184 Returns:
185 str: Bond's ISO 4217 currency code
186 """
187 return self.__currency
189 @currency.setter
190 def currency(self, currency:str):
191 self.__currency = Currency.to_string(currency)
193 @property
194 def notional(self) -> float:
195 """
196 Getter for bond's face value.
198 Returns:
199 float: Bond's face value.
200 """
201 return self.__notional
203 @notional.setter
204 def notional(self, notional):
205 self.__notional = _check_positivity(notional)
207 #endregion
209class ZeroCouponBondSpecification(BondBaseSpecification):
210 def __init__(self,
211 obj_id: str,
212 issue_date: _Union[date, datetime],
213 maturity_date: _Union[date, datetime],
214 currency: str = 'EUR',
215 notional: float = 100.0,
216 issuer: str = None,
217 securitization_level: _Union[SecuritizationLevel, str] = None,
218 rating: _Union[Rating, str] = Rating.NONE):
219 """Zero coupon bond specification.
221 Args:
222 obj_id (str): (Preferably) Unique label of the bond, e.g. ISIN.
223 issue_date (_Union[date, datetime]): Date of bond issuance.
224 maturity_date (_Union[date, datetime]): Bond's maturity/expiry date. Must lie after the issue_date.
225 currency (str, optional): Currency as alphabetic, Defaults to 'EUR'.
226 notional (float, optional): Bond's notional/face value. Must be positive. Defaults to 100.0.
227 issuer (str, optional): Name/id of issuer. Defaults to None.
228 securitization_level (_Union[SecuritizationLevel, str], optional): Securitization level. Defaults to None.
229 rating (_Union[Rating, str]): Paper rating.
230 """
231 super().__init__(obj_id,
232 issue_date,
233 maturity_date,
234 currency,
235 notional,
236 issuer,
237 securitization_level)
239 @staticmethod
240 def _create_sample(n_samples: int, seed: int = None, ref_date = None,
241 issuers: _List[str]= None, sec_levels: _List[str]=None,
242 currencies: _List[str]= None):
243 specs = BondBaseSpecification._create_sample(**locals())
244 result = []
245 for i, b in enumerate(specs):
246 result.append(ZeroCouponBondSpecification('ZC_BND_'+str(i), **b))
247 return result
249 def _validate_derived_bond(self):
250 pass
252 def _validate_derived_issued_instrument(self):
253 pass
255 def expected_cashflows(self)->_List[Tuple[datetime, float]]:
256 """Return a list of all expected cashflows (here only the final notional) together with their payment date.
258 Returns:
259 _List[Tuple[datetime, float]]: The resulting list of all cashflows.
260 """
261 return [(self.maturity_date, self.notional)]
265class PlainVanillaCouponBondSpecification(BondBaseSpecification):
266 def __init__(self,
267 obj_id: str,
268 issue_date: _Union[date, datetime],
269 maturity_date: _Union[date, datetime],
270 accrual_start: _Union[date, datetime],
271 coupon_freq: str,
272 coupon: float,
273 currency: str = 'EUR',
274 notional: float = 100.0,
275 issuer: str = None,
276 securitization_level: _Union[SecuritizationLevel, str] = None,
277 stub: bool = True,
278 rating: _Union[Rating, str] = Rating.NONE):
279 """PlainVanillaCouponBond specification.
281 Args:
282 obj_id (str): (Preferably) Unique label of the bond, e.g. ISIN.
283 issue_date (_Union[date, datetime]): Date of bond issuance.
284 maturity_date (_Union[date, datetime]): Bond's maturity/expiry date. Must lie after the issue_date.
285 first_coupondate (_Union[date, datetime]): The first coupon date.
286 coupon_freq (str): Frequency of coupons. Defaults to '1Y' for yearly. Internally, the method :func:`rivapy.tools.Period.from_string` is used, see the definition of valid strings there.
287 coupon (float): Coupon as relative number (multiplied internaly by notional to get absolute cashflow).
288 currency (str, optional): Currency as alphabetic, Defaults to 'EUR'.
289 notional (float, optional): Bond's notional/face value. Must be positive. Defaults to 100.0.
290 issuer (str, optional): Name/id of issuer. Defaults to None.
291 securitization_level (_Union[SecuritizationLevel, str], optional): Securitization level. Defaults to None.
292 rating ( _Union[Rating, str]): Paper rating.
293 """
294 super().__init__(obj_id,
295 issue_date,
296 maturity_date,
297 currency,
298 notional,
299 issuer,
300 securitization_level,
301 rating)
303 self.accrual_start = accrual_start
304 self.coupon_freq = coupon_freq
305 self.coupon = coupon
306 self.stub = stub
308 def expected_cashflows(self)->_List[Tuple[datetime, float]]:
309 """Return a list of all expected cashflows (final notional and coupons) together with their payment date.
311 Returns:
312 _List[Tuple[datetime, float]]: The resulting list of all cashflows.
313 """
314 #if self.coupon_freq != 'Y':
315 # raise Exception('Cannot calc cashflows for other than yearly coupons. Missing transformation from yearly coupon to .... ')
316 period = Period.from_string(self.coupon_freq)
317 coupon_multiplier = 1.0
318 if period.years > 0:
319 coupon_multiplier = period.years
320 elif period.months > 0:
321 coupon_multiplier = period.months/12.0
322 elif period.days > 0:
323 coupon_multiplier = period.days/365.0
324 schedule = Schedule(self.accrual_start, self.maturity_date, period, stub=self.stub).generate_dates(ends_only=True)
325 result = [(d, self.coupon*coupon_multiplier*self.notional) for d in schedule]
326 result.insert(0, (self.accrual_start, 0.0))# the first entry of this schedule is the accrual start which has a cashflow of zero and is just used for accrual calculation
327 result.append((self.maturity_date, self.notional))
328 return result
330 def _to_dict(self) -> dict:
331 result = {
332 'accrual_start': self.accrual_start,
333 'coupon_freq' : self.coupon_freq,
334 'coupon' : self.coupon,
335 }
336 result.update(super(PlainVanillaCouponBondSpecification, self)._to_dict())
337 return result
339 @staticmethod
340 def _create_sample(n_samples: int, seed: int = None, ref_date = None,
341 issuers: _List[str]= None, sec_levels: _List[str]=None,
342 currencies: _List[str]= None):
343 specs = BondBaseSpecification._create_sample(**locals())
344 result = []
345 coupons = np.arange(0.0, 0.09, 0.0025)
346 for i, b in enumerate(specs):
347 b['coupon_freq'] = np.random.choice(['3M', '6M', '9M', '1Y'], p=[0.1,0.4,0.1,0.4])
348 issue_date = b['issue_date']
349 b['accrual_start'] = issue_date + timedelta(days=np.random.randint(low=0, high=10))
350 b['coupon'] = np.random.choice(coupons)
351 result.append(PlainVanillaCouponBondSpecification('BND_PV_'+str(i), **b))
352 return result
354class FixedRateBondSpecification(BondBaseSpecification):
355 def __init__(self,
356 obj_id: str,
357 issue_date: _Union[date, datetime],
358 maturity_date: _Union[date, datetime],
359 coupon_payment_dates: _List[_Union[date, datetime]],
360 coupons: _List[float],
361 currency: str = 'EUR',
362 notional: float = 100.0,
363 issuer: str = None,
364 securitization_level: _Union[SecuritizationLevel, str] = None,
365 rating: _Union[Rating, str] = Rating.NONE):
366 """
367 Fixed rate bond specification by providing coupons and coupon payment dates directly.
369 Args:
370 coupon_payment_dates (List[Union[date, datetime]]): List of annualised coupon payment dates.
371 coupons (List[float]): List of annualised coupon amounts as fraction of notional.
372 """
373 super().__init__(obj_id, issue_date, maturity_date, currency, notional, issuer, securitization_level, rating)
374 self.__coupon_payment_dates = coupon_payment_dates
375 self.__coupons = coupons
376 # validation of dates' consistency
377 if not _is_ascending_date_list(issue_date, coupon_payment_dates, maturity_date):
378 raise Exception("Inconsistent combination of issue date '" + str(issue_date)
379 + "', payment dates '" + str(coupon_payment_dates)
380 + "', and maturity date '" + str(maturity_date) + "'.")
381 # TODO: Clarify if inconsistency should be shown explicitly.
382 if len(coupon_payment_dates) == len(coupons):
383 self.__coupons = coupons
384 else:
385 raise Exception('Number of coupons ' + str(coupons) +
386 ' is not equal to number of coupon payment dates ' + str(coupon_payment_dates))
389 @staticmethod
390 def _create_sample(n_samples: int, seed: int = None, ref_date = None, issuers: _List[str]= None):
391 specs = BondBaseSpecification._create_sample(**locals())
392 result = []
393 coupons = np.arange(0.01, 0.09, 0.005)
394 for i, b in enumerate(specs):
395 issue_date = b['issue_date']
396 n_coupons = np.random.randint(low=1, high=20)
397 days_coupon_period = np.random.choice([90.0,180.0,365.0], p=[0.2,0.2,0.6])
398 b['coupon_payment_dates'] = [issue_date+timedelta(days=(i+1)*days_coupon_period) for i in range(n_coupons)]
399 coupon = np.random.choice(coupons)
400 b['coupons'] = [coupon]*n_coupons
401 b['maturity_date'] = b['coupon_payment_dates'][-1]
402 result.append(FixedRateBondSpecification('BND_FR_'+str(i), **b))
403 return result
405 def _validate_derived_bond(self):
406 self.__coupon_payment_dates = _datetime_to_date_list(self.__coupon_payment_dates)
407 # validation of dates' consistency
408 if not _is_ascending_date_list(self.__issue_date, self.__coupon_payment_dates, self.__maturity_date):
409 raise Exception("Inconsistent combination of issue date '" + str(self.__issue_date)
410 + "', payment dates '" + str(self.__coupon_payment_dates)
411 + "', and maturity date '" + str(self.__maturity_date) + "'.")
412 # TODO: Clarify if inconsistency should be shown explicitly.
413 if len(self.__coupon_payment_dates) != len(self.__coupons):
414 raise Exception('Number of coupons ' + str(self.__coupons) +
415 ' is not equal to number of coupon payment dates ' + str(self.__coupon_payment_dates))
417 def _validate_derived_issued_instrument(self):
418 pass
421 def _to_dict(self)->dict:
422 result = {
423 'coupon_payment_dates': self.__coupon_payment_dates,
424 'coupons' : self.__coupons
425 }
426 result.update(super(FixedRateBondSpecification, self)._to_dict())
427 return result
429 @classmethod
430 def from_master_data(cls,
431 obj_id: str,
432 issue_date: _Union[date, datetime],
433 maturity_date: _Union[date, datetime],
434 coupon: float,
435 tenor: _Union[Period, str],
436 backwards: bool = True,
437 stub: bool = False,
438 business_day_convention: _Union[RollConvention, str] = RollConvention.FOLLOWING,
439 calendar: _Union[_HolidayBase, str] = None,
440 currency: str = 'EUR',
441 notional: float = 100.0,
442 issuer: str = None,
443 securitisation_level: _Union[SecuritizationLevel, str] = None):
444 """
445 Fixed rate bond specification based on bond's master data.
447 Args:
448 # TODO: How can we avoid repeating ourselves here?
449 obj_id (str): (Preferably) Unique label of the bond, e.g. ISIN.
450 issue_date (Union[date, datetime]): Date of bond issuance.
451 maturity_date (Union[date, datetime]): Bond's maturity/expiry date. Must lie after the issue_date.
453 coupon (float): Annualised coupon amount as fraction of notional, e.g. 0.0125 for fixed rate coupon of
454 1.25%.
455 tenor: (Union[period, str]): Time distance between two coupon payment dates.
456 backwards (bool, optional): Defines direction for rolling out the schedule. True means the schedule will be
457 rolled out (backwards) from maturity date to issue date. Defaults to True.
458 stub (bool, optional): Defines if the first/last period is accepted (True), even though it is shorter than
459 the others, or if it remaining days are added to the neighbouring period (False).
460 Defaults to True.
461 business_day_convention (Union[RollConvention, str], optional): Set of rules defining the adjustment of
462 days to ensure each date being a business
463 day with respect to a given holiday
464 calendar. Defaults to
465 RollConvention.FOLLOWING
466 calendar (Union[HolidayBase, str], optional): Holiday calendar defining the bank holidays of a country or
467 province (but not all non-business days as for example
468 Saturdays and Sundays).
469 Defaults (through constructor) to holidays.ECB
470 (= Target2 calendar) between start_day and end_day.
471 # TODO: How can we avoid repeating ourselves here?
472 currency (str, optional): Currency as alphabetic according to iso
473 currency code ISO 4217
474 (cf. https://www.iso.org/iso-4217-currency-codes.html).
475 Defaults to 'EUR'.
476 notional (float, optional): Bond's notional/face value. Must be positive. Defaults to 100.0.
477 issuer (str, optional): Issuer of the instrument. Defaults to None.
478 securitisation_level (Union[SecuritizationLevel, str], optional): Securitisation level of the instrument.
479 Defaults to None.
481 Returns:
482 FixedRateBond: Corresponding fixed rate bond with already generated schedule for coupon payments.
483 """
484 coupon = _check_positivity(coupon)
485 tenor = _term_to_period(tenor)
486 business_day_convention = RollConvention.to_string(business_day_convention)
487 if calendar is None:
488 calendar = _ECB(years=range(issue_date.year, maturity_date.year + 1))
489 else:
490 calendar = _string_to_calendar(calendar)
491 schedule = Schedule(issue_date, maturity_date, tenor, backwards, stub, business_day_convention, calendar)
492 coupon_payment_dates = schedule.generate_dates(True)
493 coupons = [coupon] * len(coupon_payment_dates)
494 securitisation_level = SecuritizationLevel.to_string(securitisation_level)
495 return FixedRateBondSpecification(obj_id, issue_date, maturity_date, coupon_payment_dates, coupons, currency, notional,
496 issuer, securitisation_level)
498 @property
499 def coupon_payment_dates(self) -> _List[date]:
500 """
501 Getter for payment dates for fixed coupons.
503 Returns:
504 List[date]: List of dates for fixed coupon payments.
505 """
506 return self.__coupon_payment_dates
508 @property
509 def coupons(self) -> _List[float]:
510 """
511 Getter for fixed coupon payments.
513 Returns:
514 List[float]: List of coupon amounts expressed as annualised fractions of bond's face value.
515 """
516 return self.__coupons
518class FloatingRateNoteSpecification(BondBaseSpecification):
519 def __init__(self,
520 obj_id: str,
521 issue_date: _Union[date, datetime],
522 maturity_date: _Union[date, datetime],
523 coupon_period_dates: _List[_Union[date, datetime]],
524 day_count_convention: _Union[DayCounterType, str] = DayCounterType.ThirtyU360,
525 spreads: _List[float] = None,
526 reference_index: str = 'dummy_curve',
527 currency: str = 'EUR',
528 notional: float = 100.0,
529 issuer: str = None,
530 securitisation_level: _Union[SecuritizationLevel, str] = None):
531 """
532 Floating rate note specification by providing coupon periods directly.
534 Args:
535 coupon_period_dates (List[_Union[date, datetime]): Floating rate note's coupon periods, i.e. beginning and
536 ends of the accrual periods for the floating rate coupon
537 payments.
538 day_count_convention (Union[DayCounter, str], optional): Day count convention for determining period
539 length. Defaults to DayCounter.ThirtyU360.
540 spreads (List[float], optional): List of spreads added to the floating rates derived from fixing the
541 reference curve as fraction of notional. Defaults to None.
542 reference_index (str, optional): Floating rate note underlying reference curve used for fixing the floating
543 rate coupon amounts. Defaults to 'dummy_curve'.
544 Note: A reference curve could also be provided later at the pricing stage.
545 """
546 #super().__init__(obj_id, issue_date, maturity_date, currency, notional, issuer, securitisation_level)
547 BondBaseSpecification.__init__(self, obj_id, issue_date, maturity_date, currency, notional, issuer, securitisation_level)
548 self.__coupon_period_dates = _datetime_to_date_list(coupon_period_dates)
549 # validation of dates' consistency
550 if not _is_ascending_date_list(issue_date, coupon_period_dates, maturity_date, False):
551 raise Exception("Inconsistent combination of issue date '" + str(issue_date)
552 + "', payment dates '" + str(coupon_period_dates)
553 + "', and maturity date '" + str(maturity_date) + "'.")
554 # TODO: Clarify if inconsistency should be shown explicitly.
555 self.__day_count_convention = DayCounterType.to_string(day_count_convention)
556 if spreads is None:
557 self.__spreads = [0.0] * (len(coupon_period_dates) - 1)
558 elif len(spreads) == len(coupon_period_dates) - 1:
559 self.__spreads = spreads
560 else:
561 raise Exception('Number of spreads ' + str(spreads) +
562 ' does not fit to number of coupon periods ' + str(coupon_period_dates))
563 if reference_index == '':
564 # do not leave reference curve empty as this causes pricer to ignore floating rate coupons!
565 self.__reference_index = 'dummy_curve'
566 else:
567 self.__reference_index = reference_index
569 @classmethod
570 def from_master_data(cls,
571 obj_id: str,
572 issue_date: _Union[date, datetime],
573 maturity_date: _Union[date, datetime],
574 tenor: _Union[Period, str],
575 backwards: bool = True,
576 stub: bool = False,
577 business_day_convention: _Union[RollConvention, str] = RollConvention.FOLLOWING,
578 calendar: _Union[_HolidayBase, str] = None,
579 day_count_convention: _Union[DayCounterType, str] = DayCounterType.ThirtyU360,
580 spread: float = 0.0,
581 reference_index: str = 'dummy_curve',
582 currency: str = 'EUR',
583 notional: float = 100.0,
584 issuer: str = None,
585 securitisation_level: _Union[SecuritizationLevel, str] = None):
586 """
587 Floating rate note specification based on master data.
589 Args:
590 # TODO: How can we avoid repeating ourselves here?
591 obj_id (str): (Preferably) Unique label of the bond, e.g. ISIN.
592 issue_date (Union[date, datetime]): Date of bond issuance.
593 maturity_date (Union[date, datetime]): Bond's maturity/expiry date. Must lie after the issue_date.
595 tenor: (Union[period, str]): Time distance between two coupon payment dates.
596 backwards (bool, optional): Defines direction for rolling out the schedule. True means the schedule will be
597 rolled out (backwards) from maturity date to issue date. Defaults to True.
598 stub (bool, optional): Defines if the first/last period is accepted (True), even though it is shorter than
599 the others, or if it remaining days are added to the neighbouring period (False).
600 Defaults to True.
601 business_day_convention (Union[RollConvention, str], optional): Set of rules defining the adjustment of
602 days to ensure each date being a business
603 day with respect to a given holiday
604 calendar. Defaults to
605 RollConvention.FOLLOWING
606 calendar (Union[HolidayBase, str], optional): Holiday calendar defining the bank holidays of a country or
607 province (but not all non-business days as for example
608 Saturdays and Sundays).
609 Defaults (through constructor) to holidays.ECB
610 (= Target2 calendar) between start_day and end_day.
611 # TODO: How can we avoid repeating ourselves here?
612 day_count_convention (Union[DayCounter, str], optional): Day count convention for determining period
613 length. Defaults to DayCounter.ThirtyU360.
614 spread (float, optional): Spread added to floating rate derived from fixing the reference curve as fraction
615 of notional, i.e. 0.0025 for 25 basis points. Defaults to 0.0.
616 reference_index (str, optional): Floating rate note underlying reference curve used for fixing the floating
617 rate coupon amounts. Defaults to 'dummy_curve'.
618 Note: A reference curve could also be provided later at the pricing stage.
619 currency (str, optional): Currency as alphabetic code according to iso
620 currency code ISO 4217
621 (cf. https://www.iso.org/iso-4217-currency-codes.html).
622 Defaults to 'EUR'.
623 notional (float, optional): Bond's notional/face value. Must be positive. Defaults to 100.0.
624 issuer (str, optional): Issuer of the instrument. Defaults to None.
625 securitisation_level (Union[SecuritizationLevel, str], optional): Securitisation level of the instrument.
626 Defaults to None.
627 Returns:
628 FloatingRateNote: Corresponding floating rate note with already generated schedule for coupon payments.
629 """
630 tenor = _term_to_period(tenor)
631 business_day_convention = RollConvention.to_string(business_day_convention)
632 if calendar is None:
633 calendar = _ECB(years=range(issue_date.year, maturity_date.year + 1))
634 else:
635 calendar = _string_to_calendar(calendar)
636 schedule = Schedule(issue_date, maturity_date, tenor, backwards, stub, business_day_convention, calendar)
637 coupon_period_dates = schedule.generate_dates(False)
638 spreads = [spread] * (len(coupon_period_dates) - 1)
639 return FloatingRateNoteSpecification(obj_id, issue_date, maturity_date, coupon_period_dates, day_count_convention, spreads,
640 reference_index, currency, notional, issuer, securitisation_level)
642 @property
643 def coupon_period_dates(self) -> _List[date]:
644 """
645 Getter for accrual periods for floating rate coupons.
647 Returns:
648 List[date]: List of accrual periods for floating rate coupons.
649 """
650 return self.__coupon_period_dates
652 @property
653 def daycount_convention(self) -> str:
654 """
655 Getter for bond's day count convention.
657 Returns:
658 str: Bond's day count convention.
659 """
660 return self.__day_count_convention
662 @daycount_convention.setter
663 def daycount_convention(self, day_count_convention: _Union[DayCounterType, str])-> str:
664 self.__day_count_convention = DayCounterType.to_string(day_count_convention)
666 @property
667 def spreads(self) -> _List[float]:
668 """
669 Getter for spreads added to the floating rates determined by fixing of reference index.
671 Returns:
672 List[float]: List of spreads added to the floating rates determined by fixing of reference index.
673 """
674 return self.__spreads
676 @property
677 def reference_index(self) -> str:
678 """
679 Getter for reference index for fixing floating rates.
681 Returns:
682 str: Reference index for fixing floating rates.
683 """
684 return self.__reference_index
687class FixedToFloatingRateNoteSpecification(FixedRateBondSpecification, FloatingRateNoteSpecification):
688 def __init__(self,
689 obj_id: str,
690 issue_date: _Union[date, datetime],
691 maturity_date: _Union[date, datetime],
692 coupon_payment_dates: _List[_Union[date, datetime]],
693 coupons: _List[float],
694 coupon_period_dates: _List[_Union[date, datetime]],
695 day_count_convention: _Union[DayCounterType, str] = DayCounterType.ThirtyU360,
696 spreads: _List[float] = None,
697 reference_index: str = 'dummy_curve',
698 currency: str = 'EUR',
699 notional: float = 100.0,
700 issuer: str = None,
701 securitisation_level: _Union[SecuritizationLevel, str] = None):
702 """
703 Fixed-to-floating rate note specification by providing fixed rate coupons and fixed rate coupon payment dates
704 as well as floating rate coupon periods directly.
705 """
706 # TODO FIX THIS CLASS!!!!!!!!!!!!!!!!
707 raise Exception('Not working properly, @Stefan: Please fix me!!!!')
708 FixedRateBondSpecification.__init__(self, obj_id, issue_date, maturity_date, coupon_payment_dates, coupons,
709 currency, notional, issuer, securitisation_level)
711 FloatingRateNoteSpecification.__init__(self, obj_id, issue_date, maturity_date, coupon_period_dates,
712 day_count_convention, spreads, reference_index, currency, notional, issuer,
713 securitisation_level)
715 @classmethod
716 def from_master_data(cls, obj_id: str,
717 issue_date: _Union[date, datetime],
718 fixed_to_float_date: _Union[date, datetime],
719 maturity_date: _Union[date, datetime],
720 coupon: float,
721 tenor_fixed: _Union[Period, str],
722 tenor_float: _Union[Period, str],
723 backwards_fixed: bool = True,
724 backwards_float: bool = True,
725 stub_fixed: bool = False,
726 stub_float: bool = False,
727 business_day_convention_fixed: _Union[RollConvention, str] = RollConvention.FOLLOWING,
728 business_day_convention_float: _Union[RollConvention, str] = RollConvention.FOLLOWING,
729 calendar_fixed: _Union[_HolidayBase, str] = None,
730 calendar_float: _Union[_HolidayBase, str] = None,
731 day_count_convention: _Union[DayCounterType, str] = DayCounterType.ThirtyU360,
732 spread: float = 0.0,
733 reference_index: str = 'dummy_curve',
734 currency: _Union[str, int] = 'EUR',
735 notional: float = 100.0,
736 issuer: str = None,
737 securitisation_level: _Union[SecuritizationLevel, str] = None):
738 """
739 Fixed-to-floating rate note specification based on master data.
741 Args:
742 # TODO: How can we avoid repeating ourselves here?
743 obj_id (str): (Preferably) Unique label of the bond, e.g. ISIN.
744 issue_date (_Union[date, datetime]): Date of bond issuance.
745 fixed_to_float_date (_Union[date, datetime]): Date where fixed schedule changes into floating one.
746 maturity_date (_Union[date, datetime]): Bond's maturity/expiry date. Must lie after the issue_date.
747 coupon (float): Annualised coupon amount as fraction of notional, e.g. 0.0125 for fixed rate coupon of
748 1.25%.
749 tenor_fixed (_Union[period, str]): Time distance between two fixed rate coupon payment dates.
750 tenor_float (_Union[period, str]): Time distance between two floating rate coupon payment dates.
751 backwards_fixed (bool, optional): Defines direction for rolling out the schedule for the fixed rate part.
752 True means the schedule will be rolled out (backwards) from maturity date
753 to issue date. Defaults to True.
754 backwards_float (bool, optional): Defines direction for rolling out the schedule for the floating rate part.
755 True means the schedule will be rolled out (backwards) from maturity date
756 to issue date. Defaults to True.
757 stub_fixed (bool, optional): Defines if the first/last period is accepted (True) in the fixed rate schedule,
758 even though it is shorter than the others, or if it remaining days are added to
759 the neighbouring period (False). Defaults to True.
760 stub_float (bool, optional): Defines if the first/last period is accepted (True) in the float rate schedule,
761 even though it is shorter than the others, or if it remaining days are added to
762 the neighbouring period (False). Defaults to True.
763 business_day_convention_fixed (_Union[RollConvention, str], optional): Set of rules defining the adjustment
764 of days to ensure each date in the
765 fixed rate schedule being a business
766 day with respect to a given holiday
767 calendar. Defaults to
768 RollConvention.FOLLOWING
769 business_day_convention_float (_Union[RollConvention, str], optional): Set of rules defining the adjustment
770 of days to ensure each date in the
771 float rate schedule being a business
772 day with respect to a given holiday
773 calendar. Defaults to
774 RollConvention.FOLLOWING
775 calendar_fixed (_Union[__HolidayBase, str], optional): Holiday calendar defining the bank holidays of a
776 country or province (but not all non-business days as
777 for example Saturdays and Sundays).
778 Defaults (through constructor) to holidays.ECB
779 (= Target2 calendar) between start_day and end_day.
780 calendar_float (_Union[__HolidayBase, str], optional): Holiday calendar defining the bank holidays of a
781 country or province (but not all non-business days as
782 for example Saturdays and Sundays).
783 Defaults (through constructor) to holidays.ECB
784 (= Target2 calendar) between start_day and end_day.
785 day_count_convention (_Union[DayCounter, str], optional): Day count convention for determining period
786 length.Defaults to DayCounter.ThirtyU360.
787 spread (float, optional): Spread added to floating rate derived from fixing the reference curve as fraction
788 of notional, i.e. 0.0025 for 25 basis points. Defaults to 0.0.
789 reference_index (str, optional): Floating rate note underlying reference curve used for fixing the floating
790 rate coupon amounts. Defaults to 'dummy_curve'.
791 Note: A reference curve could also be provided later at the pricing stage.
792 currency (str, optional): Currency as alphabeticcode according to iso currency code
793 ISO 4217 (cf. https://www.iso.org/iso-4217-currency-codes.html).
794 Defaults to 'EUR'.
795 notional (float, optional): Bond's notional/face value. Must be positive. Defaults to 100.0.
796 issuer (str, optional): Issuer of the instrument. Defaults to None.
797 securitisation_level (_Union[SecuritizationLevel, str], optional): Securitisation level of the instrument.
798 Defaults to None.
800 Returns:
801 FixedToFloatingRateNote: Corresponding fixed-to-floating rate note with already generated schedules for
802 fixed rate and floating rate coupon payments.
803 """
804 fixed_rate_part = FixedRateBondSpecification.from_master_data(obj_id, issue_date, fixed_to_float_date, coupon, tenor_fixed,
805 backwards_fixed, stub_fixed, business_day_convention_fixed,
806 calendar_fixed, currency, notional, issuer,
807 securitisation_level)
808 floating_rate_part = FloatingRateNoteSpecification.from_master_data(obj_id, fixed_to_float_date, maturity_date, tenor_float,
809 backwards_float, stub_float,
810 business_day_convention_float, calendar_float,
811 day_count_convention, spread, reference_index, currency,
812 notional, issuer, securitisation_level)
813 return FixedToFloatingRateNoteSpecification(obj_id, issue_date, maturity_date, fixed_rate_part.coupon_payment_dates,
814 fixed_rate_part.coupons, floating_rate_part.coupon_period_dates,
815 day_count_convention, floating_rate_part.spreads, reference_index, currency,
816 notional, issuer, securitisation_level)