from datetime import date
from .coupon import Coupon
from ..timehandles.daycounter import DayCounter
from ..timehandles.utils import TimeUnit, BusinessDayConvention
from ..index.curverateindex import IborIndex
import pandas as pd
from pandas import DataFrame
from tensorflow import Tensor
[docs]
class FloatingCoupon(Coupon):
"""
A concrete implementation of the `Coupon` class representing a floating-rate coupon.
This class models a coupon with a floating interest rate, which is typically linked
to an index (e.g., LIBOR). The floating rate is adjusted by a spread and may include
gearing, and it can account for whether the rate is set in advance or in arrears.
"""
def __init__(
self,
payment_date: date,
nominal: float,
accrual_start_date: date,
accrual_end_date: date,
index: IborIndex,
gearing: float,
spread: float,
ref_period_start: date,
ref_period_end: date,
daycounter: DayCounter,
is_in_arrears: bool = False,
fixing_days: int = None,
) -> None:
"""
Initializes a FloatingCoupon instance with the specified attributes.
Parameters:
-------
payment_date: date
The date on which the coupon payment is made.
nominal: float
The nominal (face value) amount of the coupon.
accrual_start_date: date
The start date of the accrual period.
accrual_end_date: date
The end date of the accrual period.
index: IborIndex
The index (e.g., LIBOR) to which the floating rate is tied.
gearing: float
The multiplicative coefficient applied to the index rate.
spread: float
The fixed spread added to the index rate.
ref_period_start: date
The start date of the reference period.
ref_period_end: date
The end date of the reference period.
daycounter: DayCounter
The day count convention used to calculate accrued interest.
is_in_arrears: bool, optional
Indicates whether the rate is set in arrears (default is False).
fixing_days: int, optional
The number of days before the fixing date; if None, uses the index's fixing days.
"""
super().__init__(
payment_date,
nominal,
daycounter,
accrual_start_date,
accrual_end_date,
ref_period_start,
ref_period_end,
)
self._day_counter = daycounter
self._fixing_days = fixing_days
self._index = index
self._gearing = gearing
self._spread = spread
self._is_in_arrears = is_in_arrears
self._rate = None
self._amount = None
self._convexity_adj = None
@property
def day_counter(self) -> DayCounter:
"""
Returns the day count convention used by the coupon.
Returns:
-------
DayCounter
The day count convention used for calculating accrued interest.
"""
return self._day_counter
@property
def fixing_days(self) -> int:
"""
Returns the number of days before the fixing date.
If fixing days were not provided during initialization, this property will return
the default fixing days of the index. If the index is not provided, it defaults to 0.
Returns:
-------
int
The number of days before the fixing date.
"""
if self._fixing_days is None:
if self.index is not None:
return self.index.fixing_days
else:
return 0
@property
def index(self) -> IborIndex:
"""
Returns the index to which the floating rate is tied.
Returns:
-------
IborIndex
The index associated with the floating rate.
"""
return self._index
@property
def is_in_arrears(self) -> bool:
"""
Indicates whether the rate is set in arrears.
Returns:
-------
bool
True if the rate is set in arrears, False otherwise.
"""
return self._is_in_arrears
@property
def fixing_date(self) -> date:
"""
Returns the fixing date for the floating rate.
If the rate is in arrears, the fixing date is based on the accrual end date;
otherwise, it is based on the accrual start date.
Returns:
-------
date
The date on which the rate is fixed.
"""
if self.is_in_arrears:
ref_date = self.accrual_end_date
else:
ref_date = self.accrual_start_date
return self._index.fixing_calendar.advance(
ref_date,
-self._index.fixing_days,
TimeUnit.Days,
BusinessDayConvention.Preceding,
)
@property
def accrual_period(self) -> float:
"""
Returns the fraction of the year that represents the accrual period.
Uses the `DayCounter` to calculate the year fraction between the accrual start and
end dates.
Returns:
-------
float
The fraction of the year that represents the accrual period.
"""
return self._daycounter.year_fraction(
self.accrual_start_date, self.accrual_end_date
)
[docs]
def display(self) -> DataFrame:
"""
Displays a summary of the floating coupon's details in a DataFrame format.
The DataFrame includes the start and end of the reference period, payment date,
nominal amount, fixing date, fixing days, index, accrual period, arrears status,
gearing, spread, and day count convention.
Returns:
-------
pandas.DataFrame
A DataFrame summarizing the floating coupon's details.
"""
if isinstance(self._rate, Tensor):
r = self._rate.numpy()
else:
r = None
if isinstance(self._amount, Tensor):
a = self._amount.numpy()
else:
a = None
coupon_display = pd.DataFrame(
[
self.accrual_start_date,
self.accrual_end_date,
self.ref_period_start,
self.ref_period_end,
self.day_counter,
self.accrual_period,
self.fixing_date,
self.date,
self._nominal,
self._index.name,
r,
self._spread,
self._gearing,
a,
self._convexity_adj,
]
).T
coupon_display.columns = [
"accr_start",
"accr_end",
"start_period",
"end_period",
"dc",
"accrual",
"fixing_date",
"pay_date",
"notional",
"index",
"rate",
"spread",
"gearing",
"amount",
"convexity",
]
return coupon_display
@property
def amount(self) -> float:
"""
Returns the total amount of the floating coupon.
The amount is pre-calculated and stored in the `_amount` attribute.
Returns:
-------
float
The total amount of the floating coupon payment.
"""
return self._amount
@property
def rate(self) -> float:
"""
Returns the interest rate applied to the floating coupon.
The rate is pre-calculated and stored in the `_rate` attribute.
Returns:
-------
float
The interest rate of the floating coupon.
"""
return self._rate
[docs]
def accrued_amount(self, d: date) -> float:
"""
Calculates and returns the accrued amount of the floating coupon up to the specified date.
The accrued amount is calculated as the nominal value multiplied by the compound factor
of the rate over the period from the accrual start date to the minimum of the given date
and the accrual end date.
Parameters:
-------
d: date
The date up to which the accrued amount is calculated.
Returns:
-------
float
The accrued amount up to the specified date. Returns 0 if the date is outside
the accrual period.
"""
if d <= self.accrual_start_date or d > self._payment_date:
return 0
else:
return self.nominal * (
self._rate.compound_factor(
self.accrual_start_date,
min(d, self.accrual_end_date),
self.ref_period_start,
self.ref_period_end,
)
- 1
)
[docs]
class FloatingRateLeg:
"""
A class representing a leg of floating-rate coupon payments.
This class models a series of floating-rate coupon payments over multiple periods,
constructing a sequence of `FloatingCoupon` objects based on the specified payment dates,
notionals, gearings, spreads, and other parameters.
"""
def __init__(
self,
payment_dates: list[date],
period_start_dates: list[date],
period_end_dates: list[date],
notionals: list[float],
gearings: list[float],
spreads: list[float],
index: IborIndex,
daycounter: DayCounter,
is_in_arrears: bool = False,
) -> None:
"""
Initializes a FloatingRateLeg instance with the specified attributes.
Parameters:
-------
payment_dates: list[date]
A list of dates on which the coupon payments are made.
period_start_dates: list[date]
A list of start dates for each accrual period.
period_end_dates: list[date]
A list of end dates for each accrual period.
notionals: list[float]
A list of nominal (face value) amounts for each coupon.
gearings: list[float]
A list of gearing coefficients (multiplicative factors) for each coupon.
spreads: list[float]
A list of fixed spreads added to the index rate for each coupon.
index: IborIndex
The index (e.g., LIBOR) to which the floating rate is tied.
daycounter: DayCounter
The day count convention used to calculate accrued interest.
"""
self._notionals = notionals
self._gearings = gearings
self._spreads = spreads
self._index = index
self._daycounter = daycounter
self._is_in_arrears = is_in_arrears
self.leg_flows = []
for i in range(len(payment_dates)):
self.leg_flows.append(
FloatingCoupon(
payment_dates[i],
self._notionals[i],
period_start_dates[i],
period_end_dates[i],
self._index,
self._gearings[i],
self._spreads[i],
period_start_dates[i],
period_end_dates[i],
self._daycounter,
)
)
[docs]
def display_flows(self) -> DataFrame:
"""
Displays a summary of the cash flows for the entire floating-rate leg in a DataFrame format.
This method concatenates the display data of each `FloatingCoupon` in the leg into a
single DataFrame.
Returns:
-------
pandas.DataFrame
A DataFrame summarizing the cash flows of the floating-rate leg.
"""
flows = self.leg_flows
leg_display = pd.DataFrame()
for i in range(len(flows)):
coupon_flow = flows[i].display()
leg_display = pd.concat([leg_display, coupon_flow], axis=0)
return leg_display
@property
def index(self) -> IborIndex:
return self._index
@property
def price(self) -> float:
"""
Get the price associated with the Leg.
Returns:
float: The price of the Leg.
"""
if self._price is None:
raise ValueError("you must define a pricer")
return self._price
@price.setter
def price(self, value):
self._price = value