How to Implement a Contingent Claims-Based Instrument

In this chapter we will look at how to create a strongly typed instrument, which leverages the Contingent Claims library. As an example, we will see how the fixed rate bond instrument is implemented in Daml Finance. The goal is that you will learn how to implement your own instrument template, if you need an instrument type that is not already implemented in Daml Fincance.

To follow the script used in this tutorial, you can clone the Daml Finance repository. In particular, the file src/test/daml/Daml/Finance/Instrument/Bond/Test/FixedRate.daml is the starting point of this tutorial. It also refers to some utility functions in src/test/daml/Daml/Finance/Instrument/Bond/Test/Util.daml.

Template Definition

We start by defining a new template for the instrument. Here are the first few lines of the fixed rate instrument:

-- | This template models a fixed rate bond.
-- It pays a fixed coupon rate at the end of every coupon period.
template Instrument
  with
    depository : Party
      -- ^ The depository of the instrument.
    issuer : Party
      -- ^ The issuer of the instrument.
    id : Id
      -- ^ The identifier of the instrument.
    version : Text
      -- ^ The instrument's version.
    description : Text
      -- ^ A description of the instrument.
    couponRate : Decimal
      -- ^ The fixed coupon rate, per annum. For example, in case of a "3.5% p.a coupon" this should
      --   be 0.035.
    periodicSchedule : PeriodicSchedule
      -- ^ The schedule for the periodic coupon payments.
    holidayCalendarIds : [Text]
      -- ^ The identifiers of the holiday calendars to be used for the coupon schedule.
    calendarDataProvider : Party
      -- ^ The reference data provider to use for the holiday calendar.
    dayCountConvention : DayCountConventionEnum
      -- ^ The day count convention used to calculate day count fractions. For example: Act360.
    currency : InstrumentKey
      -- ^ The currency of the bond. For example, if the bond pays in USD this should be a USD cash
      --   instrument.
    observers : PartiesMap
      -- ^ The observers of the instrument.
    lastEventTimestamp : Time
      -- ^ (Market) time of the last recorded lifecycle event. If no event has occurred yet, the
      --   time of creation should be used.

These template variables describe the payoff of the fixed rate bond. They will be used to create a Contingent Claims tree, which is the internal representation used for modelling and lifecycling in Daml Finance. Note that this tree is not part of the template above. Instead, it will be created dynamically, as described in the next sections.

The Claims Interface

In order for the instrument to work with the general Daml Finance lifecycling framework, we will implement the Claims interface. This provides a generic mechanism to process coupon payments and the redemption amount. It will work in a similar way for all instrument types, regardless of their economic terms.

Here is a high level implementation of the Claims interface:

    interface instance Claim.I for Instrument where
      view = Claim.View with acquisitionTime = dateToDateClockTime (daysSinceEpochToDate 0)
      asBaseInstrument = toInterface @BaseInstrument.I this
      getClaims Claim.GetClaims{actor} = do
        -- get the initial claims tree (as of the bond's acquisition time)
        let getCalendars = getHolidayCalendars actor calendarDataProvider
        schedule <- rollSchedule getCalendars periodicSchedule holidayCalendarIds
        let
          useAdjustedDatesForDcf = True
          notional = 1.0
          ownerReceives = True
          fxAdjustment = 1.0
          couponClaims =
            createFixRatePaymentClaims dateToDateClockTime schedule periodicSchedule
              useAdjustedDatesForDcf couponRate ownerReceives dayCountConvention notional currency
          redemptionClaim =
            createFxAdjustedPrincipalClaim dateToDateClockTime ownerReceives fxAdjustment notional
              currency periodicSchedule.terminationDate
        pure $ [couponClaims, redemptionClaim]

The getClaims function is where we define the payoff of the instrument. First, we create a coupon schedule, which depends on the coupon dates and a holiday calendar. This is then used to create the actual coupon claims. The redemption claim is also created. Finally, the coupon claims and the redemption claim are joined. Together, they define the economic terms of the instrument.

How to Define the Redemption Claim

In the above example, we see that the redemption claim depends on the currency and the maturity date.

We will now create the actual redemption claim:

-- | Create an FX adjusted principal claim.
-- This can be used for both FX swaps (using the appropriate FX rate) and single currency bonds
-- (setting the FX rate to 1.0).
createFxAdjustedPrincipalClaim : (Date -> Time) -> Bool -> Decimal -> Decimal -> Deliverable ->
  Date -> TaggedClaim
createFxAdjustedPrincipalClaim dateToTime ownerReceives fxRateMultiplier notional
  cashInstrumentCid valueDate =
  let
    fxLegClaimAmount = when (TimeGte valueDate)
                       $ scale (Const fxRateMultiplier)
                       $ scale (Const notional)
                       $ one cashInstrumentCid
    fxLegClaim = if ownerReceives then fxLegClaimAmount else give fxLegClaimAmount
  in
    prepareAndTagClaims dateToTime [fxLegClaim] "Principal payment"

Keywords like when, TimeGte, scale, one and give are defined in the Contingent Claims documentation.

How to Define the Coupon Claims

The coupon claims are a bit more complicated to define. We need to take a schedule of adjusted coupon dates and the day count convention into account.

Here is how we create the coupon claims:

createFixRatePaymentClaimsList : Schedule -> PeriodicSchedule -> Bool -> Decimal -> Bool ->
  DayCountConventionEnum -> Decimal -> Deliverable -> [Claim Date Decimal Deliverable Observable]
createFixRatePaymentClaimsList schedule periodicSchedule useAdjustedDatesForDcf couponRate
  ownerReceives dayCountConvention notional cashInstrumentCid =
    let
      couponDatesAdjusted = map (.adjustedEndDate) schedule
      couponAmounts = map (\p ->
          couponRate *
          (calcPeriodDcf dayCountConvention p useAdjustedDatesForDcf
            periodicSchedule.terminationDate periodicSchedule.frequency)
        ) schedule
      couponClaimAmounts = mconcat $
        zipWith
          (\d a ->
            when (TimeGte d) $ scale (Const a) $ scale (Const notional) $ one cashInstrumentCid
          ) couponDatesAdjusted couponAmounts
    in
      [if ownerReceives then couponClaimAmounts else give couponClaimAmounts]

For each coupon period, we calculate the adjusted end date and the amount of the coupon. We then create each coupon claim in a way similar to the redemption claim above.

How the Instrument Evolves Over Time

The bond instrument gives the holder the right to receive future coupons and the redemption amount. At issuance, this means all the coupons, since they are all in the future. However, when the first coupon is paid, the holder of the instrument is no longer entitled to receive this coupon again. In other words, the claims representation of the instrument changes. It evolves over time.

In our implementation of the fixed rate bond, we want a simple and reliable mechanism for evolving the instrument. Luckily for us, when the lifecycle function returns a coupon to be paid today, it also returns the remaining claims of the instrument (excluding today’s and any previous coupons). Hence, we can use this to evolve our instrument, in a way that is guaranteed to be consistent with the lifecycle mechanism.

This is all done in the Lifecycle.Rule. We will now break it apart to describe the steps in more detail:

      evolve Lifecycle.Evolve{eventCid; observableCids; instrument} = do
        claimInstrument <- fetchInterfaceByKey @BaseInstrument.R instrument

First, we retrieve the inital claims of the instrument. This represents the bond as of inception. By keeping track of lastEventTimestamp (in our case: the last time a coupon was paid), we can “fast forward” to the remaining claims of the instrument:

        -- Recover claims tree as of the lastEventTimestamp. For a bond, this just requires
        -- lifecycling as of the lastEventTimestamp.
        nv <- BaseInstrument.exerciseInterfaceByKey @DynamicInstrument.I instrument
          lifecycler DynamicInstrument.GetView with viewer = lifecycler
        claims <- Prelude.fst <$>
          lifecycle lifecycler observableCids claimInstrument [timeEvent nv.lastEventTimestamp]

Finally, we can lifecycle the instrument as of the current time. If there is a lifecycle effect (for example a coupon), we will create an Effect for it, which can then be settled.

        -- Lifecycle
        let
          acquisitionTime = Claim.getAcquisitionTime claimInstrument
        v <- view <$> fetch eventCid
        (remaining, pending) <-
          lifecycleClaims observableCids acquisitionTime claims [timeEvent v.eventTime]

        let
          pendingAfterNetting = netOnTag pending
          (otherConsumed, otherProduced) = splitPending pendingAfterNetting
        if remaining == claims && null pendingAfterNetting then
          pure (None, [])
        else do
          let
            currentKey = BaseInstrument.getKey $ toInterface claimInstrument
            newKey = currentKey with version = sha256 $ show remaining
            producedInstrument = if isZero' remaining then None else Some newKey

          newInstrumentCid <- BaseInstrument.exerciseInterfaceByKey @DynamicInstrument.I instrument
            lifecycler DynamicInstrument.CreateNewVersion with
              lastEventTimestamp = v.eventTime; version = newKey.version

          effectCid <- toInterfaceContractId <$> create Effect with
            providers = fromList [currentKey.issuer]
            id = v.id
            description = v.description
            targetInstrument = currentKey
            producedInstrument
            otherConsumed
            otherProduced
            settlementTime = Some v.eventTime
            observers = Disclosure.flattenObservers . (.observers) . view $
              toInterface @Disclosure.I claimInstrument
          pure (producedInstrument, [effectCid])

Observables

In our fixed rate bond example above, the coupon amount is pre-determined at the inception of the instrument. In contrast, a floating rate coupon is defined by the value of a reference rate during the lifetime of the bond. Since we do not know this value when the instrument is created, we need to define the coupon based on a future observation of the reference rate. In order to do this we introduce the concept of a numeric observation.

In the instrument definition, we need an identifier for the reference rate:

-- | This template models a floating rate bond.
-- It pays a floating coupon rate at the end of every coupon period.
-- This consists of a reference rate (observed at the beginning of the coupon period) plus a coupon
-- spread. For example, 3M Euribor + 0.5%.
template Instrument
  with
    depository : Party
      -- ^ The depository of the instrument.
    issuer : Party
      -- ^ The issuer of the instrument.
    id : Id
      -- ^ An identifier of the instrument.
    version : Text
      -- ^ The instrument's version.
    description : Text
      -- ^ A description of the instrument.
    referenceRateId : Text
      -- ^ The floating rate reference ID. For example, in case of "3M Euribor + 0.5%" this should
      --   be a valid reference to the "3M Euribor" reference rate.

When we create the claims, we can then use Observe to refer to the value of the reference rate:

-- | Calculate a floating rate amount for each payment date and create claims.
-- The floating rate is always observed on the first day of each payment period and used for the
-- corresponding payment on the last day of that payment period. This means that the calculation
-- agent needs to provide such an Observable, irrespective of the kind of reference rate used (e.g.
-- a forward looking LIBOR or a backward looking SOFR-COMPOUND).
createFloatingRatePaymentClaims : (Date -> Time) -> Schedule -> PeriodicSchedule -> Bool ->
  Decimal -> Bool -> DayCountConventionEnum -> Decimal -> Deliverable -> Observable -> TaggedClaim
createFloatingRatePaymentClaims dateToTime schedule periodicSchedule useAdjustedDatesForDcf
  floatingRateSpread ownerReceives dayCountConvention notional cashInstrumentCid referenceRateId =
  let
    couponClaimAmounts = mconcat $ map (\p ->
        when (TimeGte p.adjustedStartDate)
        $ scale (
          (Observe referenceRateId + Const floatingRateSpread) *
          (Const (calcPeriodDcf dayCountConvention p useAdjustedDatesForDcf
                    periodicSchedule.terminationDate periodicSchedule.frequency)
          ))
        $ when (TimeGte p.adjustedEndDate)
        $ scale (Const notional)
        $ one cashInstrumentCid
      ) schedule
    couponClaims = if ownerReceives then couponClaimAmounts else give couponClaimAmounts
  in prepareAndTagClaims dateToTime [couponClaims] "Floating rate payment"

In this example, the observable is an interest reference rate. Other instrument types can require other types of observables, for example an FX rate or a stock price.