How to use the Bond Instrument package

To follow the code snippets used in this page in Daml Studio, you can clone the Daml Finance repository and run the scripts included in the Instrument/Bond/Test folder.

How to use a Bond Instrument in your application

As explained in the Getting Started section and on the Architecture page, your app should only depend on the interface layer of Daml Finance. For bonds this means that you should only include the bond interface package.

Your initialization scripts are an exception, since they are only run once when your app is initialized. These are used to create the necessary instrument factories. Your app can then create bond instruments through these factory contracts.

How to Create a Bond Instrument

There are different types of bonds, which mainly differ in the way the coupon is defined. In order to create a bond instrument you first have to decide what type of bond you need. The bond instrument package currently supports the following bond types:

Fixed Rate

Fixed rate bonds pay a constant coupon rate at the end of each coupon period. The coupon is quoted on a yearly basis (per annum, p.a.), but it could be paid more frequently (e.g. quarterly). For example, a bond could have a 2% p.a. coupon and a 6M coupon period. That would mean a 1% coupon is paid twice a year.

As an example we will create a bond instrument paying a 1.1% p.a. coupon with a 12M coupon period. This example is taken from Instrument/Bond/Test/FixedRate.daml , where all the details are available.

We start by defining the terms:

  let
    issueDate = date 2019 Jan 16
    firstCouponDate = date 2019 May 15
    maturityDate = date 2020 May 15
    notional = 1.0
    couponRate = 0.011
    couponPeriod = M
    couponPeriodMultiplier = 12
    dayCountConvention = Act365Fixed
    businessDayConvention = Following

The day count convention is used to determine how many days, i.e., what fraction of a full year, each coupon period has. This will determine the exact coupon amount that will be paid each period.

The business day convention determines how a coupon date is adjusted if it falls on a non-business day.

We also need holiday calendars, which determine when to adjust dates.

We can use these variables to create a PeriodicSchedule:

    let
      (y, m, d) = toGregorian firstCouponDate
      periodicSchedule = PeriodicSchedule with
        businessDayAdjustment =
          BusinessDayAdjustment with
            calendarIds = holidayCalendarIds
            convention = businessDayConvention
        effectiveDateBusinessDayAdjustment = None
        terminationDateBusinessDayAdjustment = None
        frequency =
          Periodic Frequency with
            rollConvention = DOM d
            period = Period with
              period = couponPeriod
              periodMultiplier = couponPeriodMultiplier
        effectiveDate = issueDate
        firstRegularPeriodStartDate = Some firstCouponDate
        lastRegularPeriodEndDate = Some maturityDate
        stubPeriodType = None
        terminationDate = maturityDate

This is used to determine the periods that are used to calculate the coupon. There are a few things to note here:

  • The RollConventionEnum defines whether dates are rolled at the end of the month or on a given date of the month. In our example above, we went for the latter option.
  • The StubPeriodTypeEnum allows you to explicitly specify what kind of stub period the bond should have. This is optional and not used in the example above. Instead, we defined the stub implicitly by specifying a firstRegularPeriodStartDate: since the time between the issue date and the first regular period start date is less than 12M (our regular coupon period), this implies a short initial stub period.

For more information on calendar, schedule, and day count functions, see the date utility functions tutorial.

Now that we have defined the terms we can create the bond instrument:

    let
      instrument = InstrumentKey with
        issuer
        depository
        id = Id label
        version = "0"
        holdingStandard

    cid <- submitMulti [issuer] [publicParty] do
      exerciseCmd fixedRateBondFactoryCid FixedRateBondFactory.Create with
        fixedRate = FixedRate with
          instrument
          description
          couponRate
          periodicSchedule
          holidayCalendarIds
          calendarDataProvider
          dayCountConvention
          currency
          notional
          lastEventTimestamp
        observers = fromList observers

Once this is done, you can create a holding on it using Account.Credit.

Floating Rate

Floating rate bonds pay a coupon which is determined by a reference rate. There is also a rate spread, which is paid in addition to the reference rate.

Here is an example of a bond paying Euribor 3M + 1.1% p.a. with a 3M coupon period:

  let
    issueDate = date 2019 Jan 16
    firstCouponDate = date 2019 Feb 15
    maturityDate = date 2019 May 15
    referenceRateId = "EUR/EURIBOR/3M"
    floatingRate = FloatingRate with
      referenceRateId
      referenceRateType = SingleFixing CalculationPeriodStartDate
      fixingDates = DateOffset with
        periodMultiplier = 0
        period = D
        dayType = Some Business
        businessDayConvention = NoAdjustment
        businessCenters = ["EUR"]
    notional = 1.0
    couponSpread = 0.011
    couponPeriod = M
    couponPeriodMultiplier = 3
    dayCountConvention = Act365Fixed
    businessDayConvention = Following

The instrument supports two types of reference rates, which are configurable using the ReferenceRateTypeEnum:

  • Libor/Euribor style rates with a single fixing
  • SOFR style reference rates (using a compounded index)

Using these terms we can create the floating rate bond instrument:

    let
      instrument = InstrumentKey with
        issuer
        depository
        id = Id label
        version = "0"
        holdingStandard

    cid <- submitMulti [issuer] [publicParty] do
      exerciseCmd floatingRateBondFactoryCid FloatingRateBondFactory.Create with
        floatingRate = FloatingRateBond.FloatingRate with
          instrument
          description
          floatingRate
          couponSpread
          periodicSchedule
          holidayCalendarIds
          calendarDataProvider
          dayCountConvention
          currency
          notional
          lastEventTimestamp
        observers = fromList observers

The reference rate (Euribor 3M) is observed once at the beginning of each coupon period and used for the coupon payment at the end of that period.

Callable

Callable bonds are similar to the bonds above, but in addition they can be redeemed by the issuer before maturity. The callability is restricted to some (or all) of the coupon dates. In other words, these bonds have a Bermudan style embedded call option.

Both fixed and floating rate coupons are supported by this instrument. In case of a floating rate, there is often a fixed spread as well. This can be represented by a fixed rate coupon, which is shown in the following example. Here is a bond paying Libor 3M + 0.1% p.a. with a 3M coupon period:

  -- Libor + 0.1% coupon every 3M (with a 0% floor and a 1.5% cap)
  let
    rollDay = 15
    issueDate = date 2022 Jan rollDay
    firstCouponDate = date 2022 Apr rollDay
    maturityDate = date 2024 Jan rollDay
    notional = 1.0
    couponRate = 0.001
    capRate = Some 0.015
    floorRate = Some $ 0.0
    couponPeriod = M
    couponPeriodMultiplier = 3
    dayCountConvention = Act360
    useAdjustedDatesForDcf = True
    businessDayConvention = Following
    referenceRateId = "USD/LIBOR/3M"
    floatingRate = Some FloatingRate with
      referenceRateId
      referenceRateType = SingleFixing CalculationPeriodStartDate
      fixingDates = DateOffset with
        periodMultiplier = -2
        period = D
        dayType = Some Business
        businessDayConvention = NoAdjustment
        businessCenters = ["USD"]

The coupon rate in this example also has a 0% floor and a 1.5% cap. This is configurable, just set the cap or floor to None if it does not apply.

The fixed rate is fairly simple to define, but the floating rate requires more inputs. A FloatingRate data type is used to specify which reference rate should be used and on which date the reference rate is fixed for each coupon period.

The above variables can be used to create a couponSchedule:

    couponSchedule = createPaymentPeriodicSchedule firstCouponDate holidayCalendarIds
      businessDayConvention couponPeriod couponPeriodMultiplier issueDate maturityDate

This couponSchedule is used to determine the coupon payment dates, where the businessDayConvention specifies how dates are adjusted. Also, useAdjustedDatesForDcf determines whether adjusted or unadjusted dates should be used for day count fractions (to determine the coupon amount).

In addition to the Libor/Euribor style reference rates, compounded SOFR and similar reference rates are also supported. In order to optimize performance, these compounded rates are calculated via a (pre-computed) continuously compounded index, as described in the ReferenceRateTypeEnum. For example, here is how daily compounded SOFR can be specified using the SOFR Index:

    referenceRateId = "SOFR/INDEX"
    floatingRate = Some FloatingRate with
      referenceRateId
      referenceRateType = CompoundedIndex Act360

This instrument also allows you to configure on which coupon dates the bond is callable. This is done by specifying a separate callSchedule. The bond is callable on the last date of each schedule period. For example, if the bond is callable on every coupon date, simply set callSchedule = couponSchedule. Alternatively, if the bond is only callable every six months, this can be configured by specifying a different schedule:

    -- Define a schedule for callability. The bond is callable on the *last* date of each schedule
    -- period.
    -- In this example, it is possible to call the bond every 6M (every second coupon date).
    callScheduleStartDate = issueDate
    callScheduleEndDate = maturityDate
    callPeriod = couponPeriod
    callPeriodMultiplier = 6
    callScheduleFirstRegular = None -- Only used in case of an irregular schedule
    callSchedule = createPeriodicSchedule callScheduleFirstRegular holidayCalendarIds
      businessDayConvention callPeriod callPeriodMultiplier callScheduleStartDate
      callScheduleEndDate rollDay
    noticeDays = 5

The noticeDays field defines how many business days notice is required to call the bond. The election whether or not to call the bond must be done on this date.

Using these terms we can create the callable bond instrument:

    let
      instrument = InstrumentKey with
        issuer
        depository
        id = Id label
        version = "0"
        holdingStandard

    cid <- submitMulti [issuer] [publicParty] do
      exerciseCmd callableBondFactoryCid CallableBondFactory.Create with
        callable = Callable with
          instrument
          description
          floatingRate
          couponRate
          capRate
          floorRate
          couponSchedule
          noticeDays
          callSchedule
          holidayCalendarIds
          calendarDataProvider
          dayCountConvention
          useAdjustedDatesForDcf
          currency
          notional
          lastEventTimestamp
          prevEvents = []
        observers = fromList observers

Unlike regular fixed and floating bonds, which are lifecycled based on the passage of time, this callable bond instrument contains an embedded option that is not automatically exercised. Instead, the custodian of the bond holding must manually decide whether or not to call the bond. This is done by making an Election.

This callable bond example is taken from Instrument/Bond/Test/Callable.daml , where all the details are available. Also, check out the Election based lifecycling tutorial for more details on how to define and process an Election in practice. Note that the sample bond above, which is callable only on some of the coupon dates, will require two types of lifecycling:

Inflation-Linked

There are different types of inflation-linked bonds in the marketplace. The Inflation-linked bonds currently supported in Daml Finance pay a fixed coupon rate at the end of every coupon period. This corresponds to the payoff of e.g. Treasury Inflation-Protected Securities (TIPS) that are issued by the U.S. Treasury. The coupon is calculated based on a principal that is adjusted according to an inflation index, for example the Consumer Price Index (CPI) in the U.S.

Here is an example of a bond paying 1.1% p.a. (on a CPI adjusted principal) with a 3M coupon period:

  let
    issueDate = date 2019 Jan 16
    firstCouponDate = date 2019 Feb 15
    maturityDate = date 2019 May 15
    inflationIndexId = "CPI"
    notional = 1.0
    couponRate = 0.011
    couponPeriod = M
    couponPeriodMultiplier = 3
    dayCountConvention = Act365Fixed
    businessDayConvention = Following

Based on these terms we can create the inflation linked bond instrument:

    let
      instrument = InstrumentKey with
        issuer
        depository
        id = Id label
        version = "0"
        holdingStandard

    cid <- submitMulti [issuer] [publicParty] do
      exerciseCmd inflationLinkedBondFactoryCid InflationLinkedBondFactory.Create with
        inflationLinked = InflationLinked with
          instrument
          description
          periodicSchedule
          holidayCalendarIds
          calendarDataProvider
          dayCountConvention
          couponRate
          inflationIndexId
          inflationIndexBaseValue
          currency
          notional
          lastEventTimestamp
        observers = fromList observers

At maturity, the greater of the adjusted principal and the original principal is redeemed. For clarity, this only applies to the redemption amount. The coupons are always calculated based on the adjusted principal. This means that in the case of deflation, the coupons would be lower than the specified coupon rate but the original principal would still be redeemed at maturity.

Zero Coupon

A zero coupon bond does not pay any coupons at all. It only pays the redemption amount at maturity.

Here is an example of a zero coupon bond:

  let
    issueDate = date 2019 Jan 16
    maturityDate = date 2020 May 15
    notional = 1000.0

Based on this we create the zero coupon bond instrument:

    let
      instrument = InstrumentKey with
        issuer
        depository
        id = Id label
        version = "0"
        holdingStandard

    cid <- submitMulti [issuer] [publicParty] do
      exerciseCmd zeroCouponBondFactoryCid ZeroCouponBondFactory.Create with
        zeroCoupon = ZeroCoupon with
          instrument
          description
          currency
          issueDate
          maturityDate
          notional
          lastEventTimestamp
        observers = fromList observers

Frequently Asked Questions

How do I transfer or trade a bond?

When you have created a holding on a bond instrument this can be transferred to another party. This is described in the Getting Started: Transfer tutorial.

In order to trade a bond (transfer it in exchange for cash) you can also initiate a delivery versus payment with atomic settlement. This is described in the Getting Started: Settlement tutorial.

How do I process coupon payments for a bond?

On the coupon payment date, the issuer will need to lifecycle the bond. This will result in a lifecycle effect for the coupon, which can be cash settled. This is described in detail in the Lifecycling and the Intermediated Lifecycling tutorials.

How do I redeem a bond?

On the redemption date, both the last coupon and the redemption amount will be paid. This is processed in the same way as a single coupon payment described above.

How do I view the terms of a bond instrument?

There are several ways to access the data of a contract, as explained in the Patterns section.