Date Utility Functions: Calendar, Schedule, and Day Count¶
The Daml Finance library contains date-related utility functions for implementing industry-standard conventions. These functions are used internally in instruments like bonds and swaps, but they can also be helpful in your own custom use cases. For example, you can use these functions to:
- understand and validate the implementation of existing Daml Finance instruments
- compose existing Daml Finance instruments; for example, define a series of zero-coupon bonds according to a specific schedule of maturity dates
- develop your own instruments that depend on business day shifts, schedules, or day count conventions
- work with non-financial use cases
Calendar¶
The Calendar module contains various utility functions related to business days and date adjustments.
To distinguish business days and non-business days, set a holiday calendar. The HolidayCalendarData type defines the set of non-business days by specifying the days of the week that belong to the weekend, as well as the dates of designated holidays. The following example sets Saturday and Sunday as weekend days and specifies some additional holidays:
calendar = HolidayCalendarData with
id = "USNY"
weekend = [Saturday, Sunday]
holidays = [date 2018 Jan 02, date 2018 Jan 31, date 2018 Feb 1]
To check whether a given date is a business day, use the isBusinessDay
function:
isBusinessDay calendar (date 2018 Jan 02) === False
isBusinessDay calendar (date 2018 Jan 03) === True
You can also get the previous and next business day:
previousBusinessDay calendar (date 2018 Jan 03) === date 2018 Jan 01
nextBusinessDay calendar (date 2018 Jan 01) === date 2018 Jan 03
To find the date that corresponds to a specific number of business
days in the future, use addBusinessDays
:
addBusinessDays calendar 5 (date 2018 Jan 01) === date 2018 Jan 09
You can also get the date that corresponds to a number of business days in the past, by passing in a negative offset:
addBusinessDays calendar (-5) (date 2018 Feb 05) === date 2018 Jan 25
You can adjust a date according to a business day convention:
adjustDate calendar NoAdjustment (date 2018 Mar 31) === (date 2018 Mar 31)
adjustDate calendar Following (date 2018 Mar 31) === (date 2018 Apr 02)
adjustDate calendar ModifiedFollowing (date 2018 Mar 31) === (date 2018 Mar 30)
adjustDate calendar Preceding (date 2018 Apr 01) === (date 2018 Mar 30)
adjustDate calendar ModifiedPreceding (date 2018 Apr 01) === (date 2018 Apr 02)
For the full list of available conventions, see the BusinessDayConventionEnum reference. Use them to specify how non-business days are adjusted, including when the next or previous business day is around the end of the month.
RollConvention¶
The RollConvention module provides utility functions to add a period to a given date.
For example, you can add a day, week, month, or year to a date:
addPeriod (D.date 2018 Oct 01) (Period with periodMultiplier = 1; period = D) ===
D.date 2018 Oct 02
addPeriod (D.date 2018 Oct 01) (Period with periodMultiplier = 1; period = W) ===
D.date 2018 Oct 08
addPeriod (D.date 2018 Oct 01) (Period with periodMultiplier = 1; period = M) ===
D.date 2018 Nov 01
addPeriod (D.date 2018 Oct 01) (Period with periodMultiplier = 1; period = Y) ===
D.date 2019 Oct 01
This function can also handle edge cases. For example, when you add a period of one month to a date, the function makes adjustments if the resulting month has fewer days than the starting month:
addPeriod (D.date 2018 Oct 31) (Period with periodMultiplier = 1; period = M) ===
D.date 2018 Nov 30
To subtract a period of time, use a negative offset:
addPeriod (D.date 2018 Nov 30) (Period with periodMultiplier = -1; period = M) ===
D.date 2018 Oct 30
addPeriod (D.date 2018 Jan 30) (Period with periodMultiplier = -1; period = M) ===
D.date 2017 Dec 30
You might need to find the start date of the next period according to a specific market convention, as described in the RollConventionEnum reference. The Roll convention specifies when the date is rolled.
For example, you can define an end-of-month roll:
next (D.date 2018 Oct 31) (Period with periodMultiplier = 1; period = M) EOM ===
D.date 2018 Nov 30
You can also set the roll to a specific day of the month:
next (D.date 2018 Oct 01) (Period with periodMultiplier = 1; period = M) (DOM 1) ===
D.date 2018 Nov 01
If the resulting month does not have enough days, the last day of the month is used instead:
next (D.date 2018 Oct 31) (Period with periodMultiplier = 1; period = M) (DOM 31) ===
D.date 2018 Nov 30
This function also takes leap days into account:
next (D.date 2019 Aug 31) (Period with periodMultiplier = 6; period = M) (DOM 31) ===
D.date 2020 Feb 29
The following example shows you how to roll to the previous period:
previous (D.date 2020 Feb 29) (Period with periodMultiplier = 6; period = M) (DOM 31) ===
D.date 2019 Aug 31
The RollConvention functions are often used in the context of rolling out a schedule, as explained in the next section.
Schedule¶
The Schedule module contains functions for creating a series of time periods. A schedule can be used to represent different aspects of a financial instrument, for example:
- payment periods, which define when a bond coupon or a swap payment should be paid
- calculation periods, which define how the interest amount is calculated
The following example defines a periodic 3M (3-month) schedule that rolls on the 30th of the month:
periodicSchedule = PeriodicSchedule with
businessDayAdjustment =
BusinessDayAdjustment with calendarIds = ["USNY"]; convention = ModifiedFollowing
effectiveDateBusinessDayAdjustment = None
terminationDateBusinessDayAdjustment = None
frequency = Periodic Frequency with
rollConvention = DOM 30; period = Period with period = M; periodMultiplier = 3
effectiveDate = D.date 2018 Nov 30
firstRegularPeriodStartDate = None
lastRegularPeriodEndDate = None
stubPeriodType = None
terminationDate = D.date 2019 Nov 30
Because there is one year between the start date and the end date, this periodic schedule should correspond to four periods of 3M each:
expectedResult =
[ SchedulePeriod with
unadjustedStartDate = D.date 2018 Nov 30
unadjustedEndDate = D.date 2019 Feb 28
adjustedStartDate = D.date 2018 Nov 30
adjustedEndDate = D.date 2019 Feb 28
stubType = None
, SchedulePeriod with
unadjustedStartDate = D.date 2019 Feb 28
unadjustedEndDate = D.date 2019 May 30
adjustedStartDate = D.date 2019 Feb 28
adjustedEndDate = D.date 2019 May 30
stubType = None
, SchedulePeriod with
unadjustedStartDate = D.date 2019 May 30
unadjustedEndDate = D.date 2019 Aug 30
adjustedStartDate = D.date 2019 May 30
adjustedEndDate = D.date 2019 Aug 30
stubType = None
, SchedulePeriod with
unadjustedStartDate = D.date 2019 Aug 30
unadjustedEndDate = D.date 2019 Nov 30
adjustedStartDate = D.date 2019 Aug 30
adjustedEndDate = D.date 2019 Nov 29
stubType = None
]
Note the distinction between adjusted and unadjusted dates. The unadjusted date is the result of adding a specified time period to the start date of the previous schedule period. This date might fall on a non-business day. Apply a BusinessDayAdjustment, to get an adjusted date that falls on a business day.
Now you can roll out the schedule to get the specific start and end date for each period, and compare the schedule to the expected result:
createSchedule cals periodicSchedule === expectedResult
Schedules often use the concept of stub periods. The above schedule had only regular 3M periods. The following example reveals what happens if the schedule starts one month later:
periodicScheduleWithStub = PeriodicSchedule with
businessDayAdjustment =
BusinessDayAdjustment with calendarIds = ["USNY"]; convention = ModifiedFollowing
effectiveDateBusinessDayAdjustment = None
terminationDateBusinessDayAdjustment = None
frequency = Periodic Frequency with
rollConvention = DOM 30; period = Period with period = M; periodMultiplier = 3
effectiveDate = D.date 2018 Dec 30
firstRegularPeriodStartDate = Some $ D.date 2019 Feb 28
lastRegularPeriodEndDate = None
stubPeriodType = None
terminationDate = D.date 2019 Nov 30
This schedule has only 11 months in it, which does not correspond to regular 3M periods (neither 3 nor 4 periods). To fix this, define a stub period. Using the preceding example, you could define a start date for the first regular period. (This implies a short initial stub period, in this case 2 months.) You would expect the following schedule:
expectedResultWithStub =
[ SchedulePeriod with
unadjustedStartDate = D.date 2018 Dec 30
unadjustedEndDate = D.date 2019 Feb 28
adjustedStartDate = D.date 2018 Dec 31
adjustedEndDate = D.date 2019 Feb 28
stubType = Some ShortInitial
, SchedulePeriod with
unadjustedStartDate = D.date 2019 Feb 28
unadjustedEndDate = D.date 2019 May 30
adjustedStartDate = D.date 2019 Feb 28
adjustedEndDate = D.date 2019 May 30
stubType = None
, SchedulePeriod with
unadjustedStartDate = D.date 2019 May 30
unadjustedEndDate = D.date 2019 Aug 30
adjustedStartDate = D.date 2019 May 30
adjustedEndDate = D.date 2019 Aug 30
stubType = None
, SchedulePeriod with
unadjustedStartDate = D.date 2019 Aug 30
unadjustedEndDate = D.date 2019 Nov 30
adjustedStartDate = D.date 2019 Aug 30
adjustedEndDate = D.date 2019 Nov 29
stubType = None
]
To configure different stub types, use the StubPeriodTypeEnum data type. You can configure whether the stub period should be at the beginning or end of the schedule, as well as whether the stub period should be longer or shorter than a regular period.
Roll out this schedule, which includes a stub period, and compare it to the expected result:
createSchedule cals periodicScheduleWithStub === expectedResultWithStub
DayCount¶
Many instruments that pay interest (often expressed as an annualized rate) require an exact definition of how many days of interest belong to each payment period. The DayCount module provides functions to support such requirements. In particular, you can calculate a day count fraction (dcf) between two dates. This indicates the fraction of a full year between the dates.
For example, consider a one-year bond that pays a quarterly coupon according to a given schedule, like the one described in the previous section.
This bond does not have the same number of days in each period, which normally results in different coupon amounts for each period. Various market conventions address this and are provided in the DayCountConventionEnum.
Consider the schedule with a short initial stub in the previous section. The following example uses
the Act360
day count convention:
calcDcf Act360 (D.date 2018 Dec 31) (D.date 2019 Feb 28) === 0.1638888889
This corresponds to the 2M short initial stub period. The day count fraction can be used to calculate a bond coupon or a swap payment for the given period, by multiplying it with the yearly interest rate of the instrument (such as 4% per annum).
You can compare this initial day count fraction with those of the second and third periods of this schedule, which are both regular (3M):
calcDcf Act360 (D.date 2019 Feb 28) (D.date 2019 May 30) === 0.2527777778
calcDcf Act360 (D.date 2019 May 30) (D.date 2019 Aug 30) === 0.2555555556
These day count fractions are clearly greater than the one of the first period, since they correspond to 3M instead of 2M. Note that they differ slightly: they do not contain the exact same number of days.
In addition to Act360
, the
DayCountConventionEnum
supports several other day count conventions. You can compute some of them with
calcDcf, using only a start and an
end date as an input as shown above.
Others, such as ActActISDA
, are a bit more complicated because they require additional
information.
You can calculate them using the
calcPeriodDcf function
instead. This function takes a schedule period (containing stub information) as input, as well as
the schedule end date and the payment frequency:
period = SchedulePeriod with
unadjustedStartDate = D.date 1999 Nov 30
unadjustedEndDate = D.date 2000 Apr 30
adjustedStartDate = D.date 1999 Nov 30
adjustedEndDate = D.date 2000 Apr 30
stubType = Some LongFinal
maturityDate = D.date 2000 Apr 30
useAdjustedDatesForDcf = True
frequency = Periodic Frequency with
rollConvention = EOM; period = Period with period = M; periodMultiplier = 3
calcPeriodDcf ActActISDA period useAdjustedDatesForDcf maturityDate frequency === 0.4155400854
Summary¶
You have learned about the date-related utility functions in the Daml Finance library. Here are some pointers regarding next steps, depending on what you want to do:
- understand and validate the implementation of existing Daml Finance instruments: the implementation of the Bond package could be a good starting point. It is explained and linked in the How to leverage Contingent Claims in custom instrument implementations tutorial.
- develop your own instruments that depend on business day shifts, schedules, or day count conventions: the Payoff Modelling tutorials describe the Contingent Claims framework step by step. You can then create the dates for your instruments using the functions described in this tutorial.