Source code for finpricing.utils.payment_schedule
# from math import log, exp, ceil
from ..utils.date import Date
from ..utils.bus_day_adj import BusDayAdjustTypes
from ..utils.holiday import CalendarTypes
from ..utils.calendar import Calendar, DateGenRuleTypes
from ..utils.frequency import FrequencyTypes
from ..utils.error import NotSupportedError
[docs]
class FixedBondPaymentSchedule:
def __init__(self, anchor_date, date_gen_rule_type, freq_type, maturity_date):
pass
[docs]
class PaymentSchedule:
"""Generate a list of adjusted payment dates according to the given parameters.
Aims to provide most naive implementation, independent of any instrument.
"""
def __init__(self,
start_date: Date,
maturity_date_or_tenor: tuple((Date, str)),
freq_type: FrequencyTypes = FrequencyTypes.QUARTERLY,
calendar_type: CalendarTypes = CalendarTypes.WEEKEND,
bus_day_adj_type: BusDayAdjustTypes = BusDayAdjustTypes.FOLLOWING,
date_gen_rule_type: DateGenRuleTypes = DateGenRuleTypes.BACKWARD,
adjust_maturity_date: bool = False,
extended: bool = False):
"""
NOTE
1. when generating raw payment schedule (using backward or forward), the maturity date is not adjusted. This will be adjusted to business day after the raw schedule is generated.
2. If the first payment date is the start date, it is not included in the payment schedule.
Args:
extended: If True, In a FORWARD schedule, the start date and NCD after maturity date are included. In a BACKWARD schedule, the PCD before start date is included.
"""
self._start_date = start_date
if isinstance(maturity_date_or_tenor, Date):
self._maturity_date = maturity_date_or_tenor
elif isinstance(maturity_date_or_tenor, str):
self._maturity_date = start_date.add_tenor(maturity_date_or_tenor)
else:
raise NotSupportedError("maturity_date_or_tenor must be either Date or str")
self._freq_type = freq_type
self._calendar = Calendar(calendar_type)
self._bus_day_adj_type = bus_day_adj_type
self._date_gen_rule_type = date_gen_rule_type
self._extended = extended
# adjust the maturity date before generating payment dates?
if adjust_maturity_date:
self._maturity_date = self._calendar.adjust(self._maturity_date, self._bus_day_adj_type)
# declare other attributes
self._payment_dates = []
# generate payment dates
self._generate_adj_payment_dates()
@property
def payment_dates(self) -> list:
"""Return the payment dates"""
return self._payment_dates
[docs]
def _generate_unadj_payment_dates(self) -> list:
"""Generate unadjusted payment dates"""
if self._date_gen_rule_type == DateGenRuleTypes.FORWARD:
payment_dates = []
next_date = self._start_date
interval = int(12 / self._freq_type.value)
while next_date <= self._maturity_date:
payment_dates.append(next_date)
next_date = next_date.add_months(interval)
# add the next coupon date after maturity date
payment_dates.append(next_date)
return payment_dates if self._extended else payment_dates[1:-1]
elif self._date_gen_rule_type == DateGenRuleTypes.BACKWARD:
payment_dates = []
next_date = self._maturity_date
interval = int(12 / self._freq_type.value)
while next_date >= self._start_date:
payment_dates.append(next_date)
next_date = next_date.add_months(-interval)
# add the previous coupon date
payment_dates.append(next_date)
return payment_dates[::-1] if self._extended else payment_dates[::-1][1:]
else:
raise NotSupportedError(
"Generate payment dates failed as DateGenRuleTypes is not supported")
[docs]
def _generate_adj_payment_dates(self) -> list:
"""Return a list of adjusted payment dates"""
unadj_payment_dates = self._generate_unadj_payment_dates()
adj_payment_dates = [self._calendar.adjust(date, self._bus_day_adj_type) for date in unadj_payment_dates]
# remove the first payment date if it is the start date, there is no cashflow on the start date
if adj_payment_dates[0] == self._start_date:
adj_payment_dates = adj_payment_dates[1:]
self._payment_dates = adj_payment_dates
return adj_payment_dates
[docs]
def __repr__(self) -> str:
"""Return the string representation of the object"""
return str(self.payment_dates)