Source code for tensorquant.timehandles.tqcalendar

from abc import ABC, abstractmethod
from datetime import date, timedelta
from dateutil.relativedelta import relativedelta
from calendar import monthrange

from .utils import TimeUnit, BusinessDayConvention


[docs] class Calendar(ABC): """ The abstract class for calendar implementations. """
[docs] def advance( self, start_date: date, period: int, time_unit: TimeUnit, convention: BusinessDayConvention, end_of_month: bool = False, ): """ Advance a given start date by a specified period using the given time unit and business day convention. Parameters: ------- start_date (date): The starting date. period (int): The number of time units to advance the start date by. time_unit (TimeUnit): The unit of time to use for advancing the start date. convention (BusinessDayConvention): The business day convention to use for adjusting the dates. end_of_month (bool, optional): Whether to adjust to the end of the month if the original start date is at the end of the month. Defaults to False. Returns: ------- date: The advanced date. """ if start_date is None: raise ValueError("null date") if period == 0: return self.adjust(start_date, convention) d1 = start_date if time_unit == TimeUnit.Days: while period > 0: d1 += timedelta(days=1) while self.is_holiday(d1): d1 += timedelta(days=1) period -= 1 while period < 0: d1 -= timedelta(days=1) while self.is_holiday(d1): d1 -= timedelta(days=1) period += 1 elif time_unit == TimeUnit.Weeks: d1 += relativedelta(weeks=period) d1 = self.adjust(d1, convention) elif time_unit == TimeUnit.Months: d1 += relativedelta(months=period) d1 = self.adjust(d1, convention) if end_of_month and self.is_end_of_month(start_date): return date(d1.year, d1.month, self.end_of_month(d1)) else: # Months or Years d1 += relativedelta(years=period) d1 = self.adjust(d1, convention) if end_of_month and self.is_end_of_month(start_date): return date(d1.year, d1.month, self.end_of_month(d1)) return d1
[docs] def adjust(self, d: date, c: BusinessDayConvention): """ Adjust date based on the business day convention. Parameters: ------- d (date): The date to adjust. c (BusinessDayConvention): The business day convention. Returns: ------- date: The adjusted date. """ if d is None: raise ValueError("null date") if c == BusinessDayConvention.Unadjusted: return d d1 = d if c in [ BusinessDayConvention.Following, BusinessDayConvention.ModifiedFollowing, BusinessDayConvention.HalfMonthModifiedFollowing, ]: while self.is_holiday(d1): d1 += timedelta(days=1) if c in [ BusinessDayConvention.ModifiedFollowing, BusinessDayConvention.HalfMonthModifiedFollowing, ]: if d1.month != d.month: return self.adjust(d, BusinessDayConvention.Preceding) if ( c == BusinessDayConvention.HalfMonthModifiedFollowing and d.day <= 15 and d1.day > 15 ): return self.adjust(d, BusinessDayConvention.Preceding) elif c in [ BusinessDayConvention.Preceding, BusinessDayConvention.ModifiedPreceding, ]: while self.is_holiday(d1): d1 -= timedelta(days=1) if c == BusinessDayConvention.ModifiedPreceding and d1.month != d.month: return self.adjust(d, BusinessDayConvention.Following) elif c == BusinessDayConvention.Nearest: d2 = d while self.is_holiday(d1) and self.is_holiday(d2): d1 += timedelta(days=1) d2 -= timedelta(days=1) if self.is_holiday(d1): return d2 else: return d1 else: raise ValueError("unknown business-day convention") return d1
[docs] def is_end_of_month(self, d: date) -> bool: """ Check if a given date is the end of the month. Parameters: ------- d (date): The date to check. Returns: ------- bool: True if the given date is the end of the month, False otherwise. """ return ( d.month != self.adjust( (d + timedelta(1)), BusinessDayConvention.ModifiedFollowing ).month )
[docs] def is_weekend(self, d: date) -> bool: """ Check if a given date is a weekend. Parameters: ------- d (date): The date to check. Returns: ------- bool: True if the given date is a weekend, False otherwise. """ return d.weekday() in [5, 6]
[docs] def end_of_month(self, d: date) -> int: """ Get the last day of the month for a given date. Parameters: ------- d (date): The date for which to find the last day of the month. Returns: ------- int: The last day of the month. """ _, last_day = monthrange(d.year, d.month) return last_day
[docs] def is_holiday(self, d: date) -> bool: """ Check if a given date is a holiday. Parameters: ------- d (date): The date to check. Returns: ------- bool: True if the given date is a holiday, False otherwise. """ return not self.is_business_day(d)
[docs] @abstractmethod def is_business_day(self, d: date): """ Check if a given date is a business day. Parameters: ------- d (date): The date to check. Returns: ------- bool: True if the given date is a business day, False otherwise. """ pass