Time-based lifecycling (using a fixed rate bond)¶
This tutorial describes how to lifecycle instruments with pre-defined payments, e.g. a fixed rate bond. It is similar to the Lifecycling tutorial, in that it describes how lifecycle rules and events can be used to evolve instruments over time. However, there is one main difference:
- The Lifecycling tutorial describes a dividend event, which is something that the issuer defines on an ongoing basis. Only once the date and amount of a dividend payment has been defined, the issuer creates a distribution event accordingly.
- This tutorial describes a fixed rate bond, where all coupon payments are defined in advance. They are all encoded in the instrument definition. Hence, the issuer does not need to create distribution events on an ongoing bases. Instead, one lifecycle rule in combination with time events (a date clock) are used to lifecycle the bond.
Check out the Lifecycling concepts for a more in-depth description of the evolution of financial instruments over their lifetime.
In this tutorial, we are going to:
- create a fixed rate bond instrument and book a holding on it
- create a lifecycle rule
- create a lifecycle event (time event:
DateClockUpdate
) - process the event to produce the effects of a coupon payment
- instruct settlement by presenting a bond holding
- settle the resulting batch atomically
This example builds on the previous Settlement tutorial script. Some required concepts are explained there, so please check out that tutorial before you continue below.
Run the Script¶
The code for this tutorial can be executed via the runFixedRateBond
script in the
FixedRateBond.daml
module.
Instrument and Holding¶
For the purpose of showcasing time-based lifecycling, we need a suitable sample instrument. Fixed rate bonds pay a constant coupon rate at the end of each coupon period. The Bond Instrument packages page describes this instrument in more detail. Here, we briefly show how to create the bond instrument using a factory:
-- Create a fixed rate bond factory
fixedRateBondFactoryCid <- toInterfaceContractId @FixedRateBondFactory.I <$> submit bank do
createCmd FixedRateBond.Factory with
provider = bank
observers = mempty
-- Define an instrument key for the bond
let
bondInstrument = InstrumentKey with
issuer = bank
depository = bank
id = Id "FixedRateBond"
version = "0"
holdingStandard = TransferableFungible
initialTimestamp = dateToDateClockTime issueDate
-- Bank creates the bond instrument
fixedRateBondCid <- submit bank do
exerciseCmd fixedRateBondFactoryCid FixedRateBondFactory.Create with
fixedRate = FixedRate with
instrument = bondInstrument
description = "Instrument representing units of a fixed rate bond"
couponRate
periodicSchedule
holidayCalendarIds
calendarDataProvider = bank
dayCountConvention
currency = usdInstrument
notional
lastEventTimestamp = initialTimestamp
observers = Map.fromList pp
We also create a bond holding in Bob’s account:
-- Credit Bob's account with a bond holding
bobRequestCid <- submit bob do
createCmd CreditAccount.Request with
account = bobAccount
instrument = bondInstrument
amount = 100000.0
bobBondHoldingCid <- submit bank do exerciseCmd bobRequestCid CreditAccount.Accept
A holding represents the ownership of a certain amount of an instrument by an owner at a custodian. Check out the Holdings tutorial for more details.
Now, we have both an instrument definition and a holding. Let us proceed to lifecycle the bond, which is the main purpose of this tutorial.
Lifecycle Events and Rule¶
As mentioned earlier, we only need one single lifecycle rule to process all time events (since all coupon payments are pre-defined in the instrument terms):
-- Create a lifecycle rule
lifecycleRuleCid <- toInterfaceContractId @Lifecycle.I <$> submit bank do
createCmd Rule with
providers = Set.singleton bank
observers = mempty
lifecycler = bank
id = Id "LifecycleRule"
description = "Rule to lifecycle an instrument"
In order to lifecycle a coupon payment, we create a time event corresponding to the date of the first coupon:
-- Create a clock update event
firstCouponClockEventCid <- createClockUpdateEvent bank firstCouponDate mempty
Note that it is the bank that actively creates a DateClockUpdateEvent. This results in more control when to actually process the coupon payment. One could also use LedgerTime, but that could cause problems in some scenarios, for example:
- The system is down when the coupon should be processed. Processing it the next day is difficult if ledger time is automatically used, because the date returned from the ledger no longer matches the intended lifecycling date.
- A global ledger containing trades from regions in different time zones may require flexibility regarding when, in relation to ledger time, to process coupons and other events.
- The coupon payment depends on market data, which the data provider occasionally provides with a delay. Retroactively processing this is simpler if the lifecycler can provide the today date.
Now, we have what we need to actually lifecycle the bond. The Evolve
choice of the lifecycle
rule is exercised to process the time event:
-- Try to lifecycle the instrument
(lifecycleCid, [effectCid]) <- submit bank do
exerciseCmd lifecycleRuleCid Lifecycle.Evolve with
eventCid = firstCouponClockEventCid
observableCids = []
instrument = bondInstrument
Both the LedgerTime and the
DateClock implement the
TimeObservable
interface, which is used by Evolve
to specify the current time for the lifecycling.
The result of this is an effect describing the per-unit asset movements to be executed for bond holders. Each holder can now present their holding to claim the effect and instruct settlement of the associated entitlements.
A Claim Rule allows a holder of the target instrument to claim the effect resulting from the time event:
-- Create the claim rule
lifecycleClaimRuleCid <- toInterfaceContractId @Claim.I <$> submit bank do
createCmd Claim.Rule with
provider = bank
claimers = Set.singleton bob
settlers = Set.singleton bob
routeProviderCid
settlementFactoryCid
netInstructions = False
By presenting their holding they can instruct the settlement of the holding transfers described in the effect:
result <- submitMulti [bob] [public] do
exerciseCmd lifecycleClaimRuleCid Claim.ClaimEffect with
claimer = bob
holdingCids = [bobBondHoldingCid]
effectCid -- This is equivalent to writing effectCid = effectCid
batchId = Id "BondSettlement"
let [bobInstructionCid, bankInstructionCid, couponInstructionCid] = result.instructionCids
As a side-effect of settling the entitlements, the presented holding is exchanged for a holding of a new bond version. This is to prevent a holder from benefiting from a given effect twice (in our case: receiving the same coupon twice).
In our example of a bond coupon, only a single instruction is generated: the movement of cash from the bank to the bond holder. This instruction along with its batch is settled the usual way, as described in the previous Settlement tutorial.
-- Allocate instruction
(bobInstructionCid, _) <- submit bob do
exerciseCmd bobInstructionCid Instruction.Allocate with
actors = Set.singleton bob
allocation = Pledge bobBondHoldingCid
(bankInstructionCid, _) <- submit bank do
exerciseCmd bankInstructionCid Instruction.Allocate with
actors = Set.singleton bank
allocation = CreditReceiver
(couponInstructionCid, _) <- submit bank do
exerciseCmd couponInstructionCid Instruction.Allocate with
actors = Set.singleton bank
allocation = CreditReceiver
-- Approve instruction
bobInstructionCid <- submit bank do
exerciseCmd bobInstructionCid Instruction.Approve with
actors = Set.singleton bank
approval = DebitSender
bankInstructionCid <- submit bob do
exerciseCmd bankInstructionCid Instruction.Approve with
actors = Set.singleton bob
approval = TakeDelivery bobAccount
couponInstructionCid <- submit bob do
exerciseCmd couponInstructionCid Instruction.Approve with
actors = Set.singleton bob
approval = TakeDelivery bobAccount
-- Settle batch
submitMulti [bob] [public] do
exerciseCmd result.batchCid Batch.Settle with actors = Set.singleton bob
Note that the bank in this case does not actually transfer the cash from another account, but simply
credits Bob’s account by using the CreditReceiver
allocation type. In a real-world bond coupon
scenario one would additionally model the flow of funds from the issuer to the bank using the same
lifecycle process as described above.
Check out the Settlement concepts for a more in-depth description of the different steps in the settlement process and the settlement modes supported by the library.
Note that the lifecycling process above is not limited to fixed coupon bonds. It also works for other instruments with pre-defined payments, for example:
Instrument | Pre-defined variable |
---|---|
Foreign exchange swaps | FX rate |
Currency swaps | Interest rates (on both legs) |
Frequently Asked Questions¶
Which party should create and sign the lifecycle rules and events?¶
In the simplified scenario for this tutorial, we have used the bank as both the issuer and depository for the instruments involved. In a real-world case, instruments and their corresponding lifecycle rules and events would be maintained by an actual issuer, with the depository acting as a 3rd-party trust anchor.
Which parties typically take which actions in the lifecycle workflow?¶
The lifecycle interfaces governing the process leave the controllers of the various choices in the process up to the implementation.
- Typically, we would expect the issuer of an instrument to define the instrument terms, which in our example above govern the date and amount of each bond coupon.
- Lifecycle rules on the other hand are often controlled by 3rd-party calculation agents.
- The claiming of lifecycle effects is by default the responsibility of the owner of a holding. If instead the owner wants to delegate this responsibility to their custodian they can do so via a delegation contract.
- The party executing settlement can be chosen as well, as described in the previous tutorial on Settlement.
Summary¶
You have learned how to create a fixed coupon bond and how to use a lifecycle rule and events to process the payments pre-defined by the instrument. The key concepts to take away are:
- Lifecycle events cause the bond instrument to evolve over time.
- A lifecycle rule contains logic to calculate the effects an event has on an instrument and its holdings.
- A claim rule is used to instruct settlement for a given effect using a holding.