How to leverage Contingent Claims in custom instrument implementations

They Payoff Modeling tutorial introduces the Contingent Claims modeling framework in the context of the Generic Instrument. In this chapter, we will see how the library can instead be used to lifecycle custom instrument implementations. The Bond and Swap instruments, for example, leverage Contingent Claims behind the scenes to calculate pending coupon payments.

Let us explore in detail how the fixed rate bond instrument is implemented in Daml Finance. The goal is for you to learn how to implement and lifecycle your own instrument template, should you need an instrument type that is not already implemented in the library.

To follow the code snippets used in this tutorial in Daml Studio, you can clone the Daml Finance repository and take a look at how the Bond Instrument template is implemented. In order to see how lifecycling is performed, you can run the script in the Instrument/Bond/Test/FixedRate.daml file.

Template Definition

We start by defining a new template for the instrument. Here are the fields used for 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.
    notional : Decimal
      -- ^ The notional of the bond. This is the face value corresponding to one unit of the bond
      --   instrument. For example, if one bond unit corresponds to 1000 USD, this should be 1000.0.
    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 economic terms of a fixed rate bond.

The Claims Interface

We now need to map the template variables to a Contingent Claims tree, the internal representation we wish to use for lifecycling. Note that the Contingent Claims tree is not a part of the template above, instead it will be created dynamically upon request.

In order do that, we implement the Claims interface. This interface provides access to a generic mechanism to process coupon payments and redemptions. It will work in a similar way for the majority of instrument types, regardless of their specific 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
      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
          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 calculate the coupon payment dates by rolling out a periodic coupon schedule.
  • The payment dates are then used to build claim sub-tree for the coupon payments.
  • A claim sub-tree for the final redemption is also created.
  • Finally, the coupon and redemption sub-trees are joined. Together, they yield the desired economic terms for the bond.

How to define the redemption claim

The the redemption claim depends on the currency and the maturity date of the bond.

-- | 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
  cashInstrument valueDate =
  let
    fxLegClaimAmount = when (TimeGte valueDate)
                       $ scale (Const fxRateMultiplier)
                       $ scale (Const notional)
                       $ one cashInstrument
    fxLegClaim = if ownerReceives then fxLegClaimAmount else give fxLegClaimAmount
  in
    prepareAndTagClaims dateToTime [fxLegClaim] "Principal payment"

Keywords like when, scale, one and give should be familiar from the Payoff Modeling tutorial. TimeGte is just a synonym for at.

The ownerReceives flag is used to indicate whether the owner of a holding on the bond is meant to receive the redemption payment. When this is set to false, the holding custodian will be entitled to the payment.

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.

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

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

Evolving the Instrument over time

The bond instrument gives the holder the right to receive future coupons and the redemption amount. At issuance, all coupons are due. However, after the first coupon is paid, the holder of the instrument is no longer entitled to receive it again. the lastEventTimestamp field in our template is used to keep track of the latest executed coupon payment.

Evolution of the instrument over time (and calculation of the corresponding lifecycle effects) can be performed using the Lifecycle.Rule template provided in the Daml.Finance.Claims package. This rule is very generic and can be used for all instruments that implement the Claims interface.

Let us break its implementation apart to describe what happens in more detail:

  • First, we retrieve the claim tree corresponding to the initial state of the instrument. We do so by fetching the Claims interface we defined for the template.

      claimInstrument <- fetchInterfaceByKey @BaseInstrument.R instrument
    
  • By using the lastEventTimestamp (in our case: the last time a coupon was paid), we can now “fast forward” the claim tree to the current instrument state.

      -- Recover claims tree as of the lastEventTimestamp. For a bond, this just requires
      -- lifecycling as of the lastEventTimestamp.
      dynInstrView <- BaseInstrument.exerciseInterfaceByKey @DynamicInstrument.I instrument lifecycler
        DynamicInstrument.GetView with viewer = lifecycler
      let
        prevElections = map (\e ->
          case e.election of
            None -> error "election missing"
            Some (electorIsOwner, tag) -> electionEvent e.t electorIsOwner tag)
          dynInstrView.prevElections
        prevEvents = prevElections <> [timeEvent dynInstrView.lastEventTimestamp]
    
      -- fast-forward the claims tree to the current version by replaying the previous events
      -- (the previous elections + the lastEventTimestamp)
      claims <- fst <$> lifecycle lifecycler observableCids claimInstrument prevEvents
    
  • Finally, we lifecycle the current instrument state to calculate pending cashflows. If there are such cashflows (for example when a coupon payment is due), we create a Lifecycle Effect for it, which can then be claimed and settled.

          evolve Lifecycle.Evolve{eventCid; observableCids; instrument} = do
            v <- view <$> fetch eventCid
    
            -- Fast-forward the instrument from inception to the timestamp of the last event.
            -- Then, perform a time-based lifecycling according to the current event.
            (remaining, pending, claims, claimInstrument, dynInstrView) <-
              fastForwardAndLifecycle instrument observableCids v.eventTime lifecycler
    
            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 $ mconcat [show v.eventTime, show remaining]
                producedInstrument = if isZero' remaining then None else Some newKey
              tryCreateNewInstrument lifecycler dynInstrView.prevElections v.eventTime None instrument
                newKey
              effectCid <- toInterfaceContractId <$> create Effect with
                providers = singleton currentKey.issuer
                id = v.id
                description = v.description
                targetInstrument = currentKey
                producedInstrument
                otherConsumed
                otherProduced
                settlementTime = Some v.eventTime
                observers = (.observers) . view $ toInterface @Disclosure.I claimInstrument
              pure (Some newKey, [effectCid])
    

Including market 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 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 for the coupon payments, we can then use ObserveAt 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 cashInstrument referenceRateId =
  let
    couponClaimAmounts = andList $ map (\p ->
        when (TimeGte p.adjustedEndDate)
        $ scale (
          (ObserveAt referenceRateId p.adjustedStartDate + Const floatingRateSpread) *
          (Const (calcPeriodDcf dayCountConvention p useAdjustedDatesForDcf
                    periodicSchedule.terminationDate periodicSchedule.frequency)
          ))
        $ scale (Const notional)
        $ one cashInstrument
      ) schedule
    couponClaims = if ownerReceives then couponClaimAmounts else give couponClaimAmounts
  in prepareAndTagClaims dateToTime [couponClaims] "Floating rate payment"

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

Different ways to create and store the Contingent Claims tree

To summarize what we have seen so far, there are two different ways of using Contingent Claims in a Daml Finance instrument.

When using the Generic Instrument, we create the claim tree at instrument inception and store this representation explicitly on the ledger. After a lifecycle event, for example a coupon payment, a new version of the instrument (with a different claim tree) supersedes the previous version.

In contrast, the approach used in this tutorial only stores the key economic terms of the bond on the ledger. The claim tree is not stored on-ledger, but it is created “on-the-fly” when needed (for example, when lifecycling).

Which approach is preferred?

The latter approach has the advantage that the claim tree can adapt to changes in reference data. A change to e.g. a holiday calendar would automatically impact the claim tree the next time it is dynamically created. This is not the case for the first approach, where the tree is static.

Also, if the economic terms of the instrument result in a very large claim tree, it could be desirable not to store it on the ledger for performance reasons.

Finally, the “dynamic” approach allows for the terms of the template to be very descriptive to anyone familiar with the payoff at hand.

On the other hand, if you need to quickly create a one-off instrument, the on-ledger approach allows you to create the claims directly from a script, without first having to define a dedicated template.

Also, if you need to explicitly access Contingent Claims representations of older versions of the instrument on the ledger, for example for auditing reasons, that would be achieved out of the box with the first approach.