How to use the Generic Instrument packages

The Generic Instrument provides a flexible framework to model and lifecycle custom payoffs in Daml Finance. It encapsulates the Contingent Claims library, which gives us the tools to model the economic terms of the payoff.

To follow the code snippets used in this page in Daml Studio, you can clone the Daml Finance repository and run the scripts in the Instrument/Generic/Test/Intermediated/BondCoupon.daml and Instrument/Generic/Test/EuropeanOption.daml files.

The Generic Instrument and the Contingent Claims library are introduced in the Payoff Modeling tutorial, which we encourage you to check out.

How to create a Generic Instrument

Define the Claim of a Bond

Consider a fixed rate bond which pays a 4% coupon per annum with a coupon period of 6 months. Assume that there are two coupons remaining until maturity: one to be paid today and one to be paid in 180 days. This can be modeled in the following way:

    let
      today = toDateUTC now
      expiry = addDays today 180
      bondLabel = "ABC.DE 4% p.a. " <> show expiry <> " Corp"
      claims = mapClaimToUTCTime $ andList
        [ when (at today) $ scale (Const 0.02) $ one cashInstrument
        , when (at expiry) $ scale (Const 0.02) $ one cashInstrument
        , when (at expiry) $ scale (Const 1.0) $ one cashInstrument
        ]

Now that we have specified the economic terms of the payoff we can create a generic instrument:

    instrument <- originateGeneric csd issuer bondLabel "Bond" now claims pp now

On every coupon payment date, the issuer will need to lifecycle the instrument. This will result in a lifecycle effect for the coupon, which can be then be claimed and settled. This process is described in detail in Getting Started: Lifecycling.

Define the Claim of a European Option

Alternatively, if you want to model a European Option instead, you can define the claim as follows

  let
    exerciseClaim = scale (Observe spot - Const strike) $ one ccy
    option = european maturity exerciseClaim

This uses the european builder function, which is included in Contingent Claims.

Compared to the bond, where the passage of time results in a coupon payment being due, the option instrument requires a manual Election: the holder of the instrument holding needs to choose whether or not to exercise the option. How this is done is described in the next section.

How to lifecycle a Generic Instrument

Election based lifecycling of Contingent Claims based instruments

We describe how to lifecycle an option instrument (which can be Exercised or Expired), but the same concepts apply to other Election based instruments (for example, a callable bond that can be Called or NotCalled). A similar workflow is also applicable to some of the instruments available in the Bond, Swap, and Option packages.

First, an Election factory is created:

  -- Create election offers to allow holders to create elections
  electionFactoryCid <- submit broker do
    toInterfaceContractId <$> createCmd Election.Factory with
      provider = broker
      observers = M.fromList pp

Then, election offers are created for the different election choices that are available. Specifically, for option instruments, an election offer to exercise is created:

  exerciseElectionFactoryCid <- submit broker do
    createCmd ElectionOffer with
      provider = broker
      id = Id "EXERCISE"
      description = "OPTION-AAPL - Exercise"
      claim = "EXERCISE"
      observers = S.singleton publicParty
      instrument = genericInstrument
      factoryCid = electionFactoryCid

Similarly, an election offer to expire the option is also created:

  expireElectionFactoryCid <- submit broker do
    createCmd ElectionOffer with
      provider = broker
      id = Id "EXPIRE"
      description = "OPTION-AAPL - Expire"
      claim = "EXPIRE"
      observers = S.singleton publicParty
      instrument = genericInstrument
      factoryCid = electionFactoryCid

Assuming the investor wants to exercise the option, an election candidate contract is created. In order to do this, the investor presents a holding for which an election should be made, and also specifies the amount that this election applies to. This amount cannot exceed the quantity of the holding:

  -- One cannot exercise for more units than they own
  submitMultiMustFail [investor1] [publicParty] do
    exerciseCmd exerciseElectionFactoryCid CreateElectionCandidate with
      elector = investor1
      electionTime = dateToDateClockTime maturity
      holdingCid = investor1GenericHoldingCid
      amount = 5000.0

Instead, the elected amount must be the same as the holding quantity, or lower:

  -- Create election
  exerciseOptionProposalCid <- submitMulti [investor1] [publicParty] do
    exerciseCmd exerciseElectionFactoryCid CreateElectionCandidate with
      elector = investor1
      electionTime = dateToDateClockTime maturity
      holdingCid = investor1GenericHoldingCid
      amount = 500.0

A time event is also required to indicate when the election is made:

  currentTimeCid <- createDateClock (S.singleton broker) maturity S.empty

It is now possible to create the Election:

  exerciseOptionCid <- submit broker do
    exerciseCmd exerciseOptionProposalCid ValidateElectionCandidate with
      currentTimeCid

Note: these templates (election offer and election candidate) are not considered a core part of the Daml Finance library. There can be different processes to create the Election, so this is rather application specific. Still, in order to showcase one way how this could be done, this workflow is included here for convenience.

The Election has a flag electorIsOwner, which indicates whether the election is on behalf of the owner of the holding. This is typically the case for options, where the option holder has the right, but not the obligation, to exercise the option. On the other hand, for callable bonds it is not the holding owner (the bond holder) who gets to decide whether the bond is redeemed early. Instead, it is the counterparty. In this case, electorIsOwner would be false.

A lifecycle rule is required to specify how to process the Election:

  -- Apply election to generate new instrument version + effects
  lifecycleRuleCid <- toInterfaceContractId <$> submit bank do
    createCmd Lifecycle.Rule with
      providers = S.singleton bank
      observers= M.empty
      lifecycler = broker
      id = Id "LifecycleRule"
      description = "Rule to lifecycle a generic instrument"

This is similar to time-based lifecycling.

Finally, it is possible to apply the Election according to the lifecycle rule provided:

  (Some exercisedOption, [effectCid]) <- submit broker do
    exerciseCmd exerciseOptionCid Election.Apply with
      observableCids = [observableCid]
      exercisableCid = lifecycleRuleCid

This creates lifecycle effects, which can be claimed and settled in the usual way (as described in Getting Started: Lifecycling). However, the holding contract used to claim the effect must be compatible with the election that has been made: if Alice made an election and electorIsOwner = True, then only a holding where owner = alice will be accepted.