Handling bond coupon, principal and maturity instrument lifecycle events

In this tutorial we'll examine instrument lifecycle events by using LUSID to perform the following task:

“I want to automate the process of handling lifecycle events emitted by LUSID for simple bond instruments in order to reduce friction and improve efficiency.”

We'll see how to:

  • Master a bond and examine the events LUSID emits at each milestone in the instrument's lifecycle.

  • Purchase a quantity of the bond on a particular date and understand whether we are entitled to the next coupon payment or not.

  • Sell part of the bond holding and examine the impact on subsequent bond coupon payments.

  • Receive the remaining principal at maturity and set the bond holding to zero, so it drops out of valuation reports.

A Jupyter Notebook containing code examples is coming soon. 

Mastering a bond instrument in LUSID and booking trades

Imagine we have a US Treasury bond paying 2.375% annually until 15 August 2026. We can model this vanilla fixed-rate bond in LUSID as an instrument of type Bond using the following economic definition:

{
  "instrumentType": "Bond",
  "startDate": "2016-08-15T00:00:00.0000000+00:00",
  "maturityDate": "2026-08-15T00:00:00.0000000+00:00",
  "domCcy": "USD",
  "flowConventions": {
    "currency": "USD",
    "paymentFrequency": "12M",
    "dayCountConvention": "Actual365",
    "rollConvention": "15",
    "businessDayConvention": "Following",
    "paymentCalendars": [],
    "resetCalendars": [],
    "accrualDateAdjustment": "Adjusted",
  },
  "principal": 1,
  "couponRate": 0.02375,
  "exDividendConfiguration": {
    "exDividendDays": 5
  }
}

Note the following:

  • The maturityDate field is set to the date on which the last coupon is paid and the principal returned: 15 August 2026.

  • The flowConventions.paymentFrequency field is set to 12M and flowConventions.rollConvention to 15 to determine that coupons are paid annually on 15 August each year.

  • The exDividendConfiguration.exDividendDays field is set to 5 to specify that the ex-dividend date for the bond is 10 August each year.

For simplicity, business days and holiday calendars are ignored in this tutorial.

We have two historical transactions to book into a suitable transaction portfolio for this bond:

  • A purchase of 10,000 units on 7 August 2020, settling on 9 August 2020.

  • A sale of 5,000 units on 9 August 2023, settling on 11 August 2023.

Understanding lifecycle events LUSID emits for this bond

Given these transactions, and with respect to a date 'today' of 20 March 2024, LUSID automatically emits the following lifecycle events for this bond:

Date

Lifecycle event

Status with respect to 20 March 2024

Eligible balance (known or estimate)

Coupon payment (known or estimate)

Notes

15 August 2020

BondCouponEvent

Historical

10000

$237.50

We are entitled to this coupon as the settlement date of the purchase transaction (9 August 2020) is before the ex-dividend date (10 August 2020).

15 August 2021

 

15 August 2022

 

15 August 2023

We are entitled to the full amount of this coupon as the settlement date of the sell transaction (11 August 2023) is after the ex-dividend date (10 August 2023).

15 August 2024

Future

5000

$118.75

 

15 August 2025

 

15 August 2026

Final coupon.

15 August 2026

BondPrincipalEvent

N/A

 

15 August 2026

MaturityEvent

N/A

N/A

 

Examining the default transaction template for bond coupon events

LUSID provides a default transaction template for every type of event. We can call the GetTransactionTemplate API to examine the default template for BondCouponEvent, which at the time of writing is as follows:

{
  "instrumentType": "Bond",
  "instrumentEventType": "BondCouponEvent",
  "description": "LUSID default template for automatically generated transactions in respect of Bond Coupon instrument events.",
  "scope": "default",
  "componentTransactions": [
    {
      "displayName": "Bond Income",
      "transactionFieldMap": {
        "transactionId": "{{instrumentEventId}}-{{holdingId}}",
        "type": "BondCoupon",
        "source": "default",
        "instrument": "{{instrument}}",
        "transactionDate": "{{BondCouponEvent.exDate}}",
        "settlementDate": "{{BondCouponEvent.paymentDate}}",
        "units": "{{eligibleBalance}}",
        "transactionPrice": {
          "price": "{{BondCouponEvent.couponPerUnit}}",
          "type": "CashFlowPerUnit"
        },
        "transactionCurrency": "{{BondCouponEvent.currency}}",
        "exchangeRate": "1",
        "totalConsideration": {
          "currency": "{{BondCouponEvent.currency}}",
          "amount": "{{BondCouponEvent.couponAmount}}"
        }
      },
      "transactionPropertyMap": []
    }
  ],
  ...
}

Note the following:

  • This template handles instrument events of type BondCouponEvent emitted by instruments of type Bond.

  • It is domiciled in the default (that is, system) transaction template scope.

  • It automatically generates a single output transaction for each portfolio with a holding in the underlying instrument. Note it is possible for a template to generate multiple output transactions.

  • This output transaction has no condition; that is, it is always produced when LUSID emits a bond coupon event.

  • This output transaction belongs to a BondCoupon transaction type grouped in the default transaction type source.

  • The transactionDate is the ex-dividend date and the settlementDate is the payment date.

  • The quantity held (units) is assessed dynamically per-portfolio and per-coupon.

  • The transactionPrice.price is the coupon per unit, which is calculated as the annual coupon rate multiplied by the principal and divided by the number of coupons per year.

  • The transactionCurrency is the currency specified in the bond economic definition, and an exchangeRate of 1 means the settlement currency is the same.

  • The totalConsideration.Amount is the coupon per unit scaled by the quantity held.

Note: This template does not set the Transaction/default/TradeToPortfolioRate system property to calculate the cost basis of the output transaction in the portfolio currency, in circumstances where this is different to the transaction currency. The recommended mechanism is to use a combination of a modified transaction type and a recipe to look up an exchange rate for the transaction date in the LUSID Quote Store dynamically. See how to do this.

In this tutorial we'll choose to use the default transaction template provided by LUSID, but you can create your own custom transaction template to override the default template and configure the process if you wish. Note you must create a custom transaction template if you want to nominate a different transaction type. If you do, you must set the transactionTemplateScope field on the instrumentEventConfiguration object for a portfolio to point to the custom transaction template scope.

Creating a suitable transaction type for bond coupon output transactions

The default transaction template mandates a BondCoupon transaction type grouped in the default transaction type source. We must create this transaction type if it does not exist.

Note: Transaction types are grouped in sources and reside in scopes. For more information on the difference, see this article.

In this example, we can call the SetTransactionType API as follows:

curl -X PUT 'https://<your-domain>.lusid.com/api/api/transactionconfiguration/types/default/BondCoupon?scope=default'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <your-API-access-token>'
  -d '{
  "aliases": [
    {
      "type": "BondCoupon",
      "description": "Transaction type for bond coupon event",
      "transactionClass": "Basic",
      "transactionRoles": "AllRoles",
      "isDefault": false
    }
  ],
  "movements": [
    {
      "movementTypes": "CashAccrual",
      "side": "Side2",
      "direction": 1,
      "mappings": [
        {
          "propertyKey": "Transaction/SHKs/BondCoupons",
          "setTo": "BondCoupons"
        }
      ],
      "name": "Add coupon to separate portfolio cash holding"
    },
    {
      "movementTypes": "Carry",
      "side": "Side1",
      "direction": 1,
      "name": "Report the coupon as income"
    }
  ],
  "calculations": [
    {
      "type": "Txn:TradeToPortfolioRate"
    }
  ]
}'

Note the following:

  • The transaction type alias is BondCoupon, to comply with the default transaction template.

  • The transaction type source is default (in the URL above), to comply with the default transaction template.

  • You can design a transaction type to have any economic impact you like. This one has the following two movements:

    • The first is a CashAccrual movement that maps gross coupon amounts to a BondCoupons sub-holding key (SHK), to report accrual separately to other cash holdings in the bond currency (USD). You could omit the mappings array to add coupons to the main USD cash holding instead. See how to calculate a net amount after tax.

    • The second is a Carry movement that reports the coupon as income.

  • The Txn:TradeToPortfolioRate calculation type looks up an exchange rate from the transaction to the portfolio currency dynamically, if different.

Examining the default transaction templates for bond principal and maturity events

In the same way, we can call the GetTransactionTemplate API to examine the default transaction templates for BondPrincipalEvent and MaturityEvent. Note the latter is designed to also be used by instruments other than bonds.

In this tutorial we'll choose to use the default templates again, but you can create your own custom templates to configure the process if you wish.

BondPrincipalEvent default transaction template

MaturityEvent default transaction template

{
  "instrumentType": "Bond",
  "instrumentEventType": "BondPrincipalEvent",
  "description": "LUSID default template for automatically generated
 transactions in respect of Bond Principal instrument events",
  "scope": "default",
  "componentTransactions": [
    {
      "displayName": "Bond Principal",
      "transactionFieldMap": {
        "transactionId": "{{instrumentEventId}}-{{holdingId}}",
        "type": "BondPrincipal",
        "source": "default",
        "instrument": "{{instrument}}",
        "transactionDate": "{{BondPrincipalEvent.paymentDate}}",
        "settlementDate": "{{BondPrincipalEvent.paymentDate}}",
        "units": "{{eligibleBalance}}",
        "transactionPrice": {
          "price": "{{BondPrincipalEvent.principalPerUnit}}",
          "type": "CashFlowPerUnit"
        },
        "transactionCurrency": "{{BondPrincipalEvent.currency}}",
        "exchangeRate": "1",
        "totalConsideration": {
          "currency": "{{BondPrincipalEvent.currency}}",
          "amount": "{{BondPrincipalEvent.principalAmount}}"
        }
      },
      "transactionPropertyMap": []
    }
  ],
   ...
}
{
  "instrumentType": "Bond",
  "instrumentEventType": "MaturityEvent",
  "description": "LUSID default template for automatically generated
 transactions in respect of instrument maturity events.",
  "scope": "default",
  "componentTransactions": [
    {
      "displayName": "Maturity",
      "transactionFieldMap": {
        "transactionId": "{{instrumentEventId}}-{{holdingId}}",
        "type": "Maturity",
        "source": "default",
        "instrument": "{{instrument}}",
        "transactionDate": "{{MaturityEvent.maturityDate}}",
        "settlementDate": "{{MaturityEvent.maturityDate}}",
        "units": "{{eligibleBalance}}",
        "transactionPrice": {
          "price": "0",
          "type": "Price"
        },
        "transactionCurrency": "{{holdingCurrency}}",
        "exchangeRate": "0",
        "totalConsideration": {
          "currency": "{{holdingCurrency}}",
          "amount": "0"
        }
      },
      "transactionPropertyMap": []
    }
  ],
  ...
}

With regard to transaction types, in summary:

  • The BondPrincipal default transaction template mandates a BondPrincipal transaction type grouped in the default transaction type source.

  • The Maturity default transaction template mandates a Maturity transaction type grouped in the default transaction type source.

Creating suitable transaction types for bond principal and maturity transactions

To use the default transaction templates, we can call the SetTransactionType API again to ensure compliant transaction types exist:

Recommended BondPrincipal transaction type

Recommended Maturity transaction type

curl -X PUT 'https://<your-domain>.lusid.com/api/api/
transactionconfiguration/types/default/BondPrincipal?scope=default'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <your-API-access-token>'
  -d '{
  "aliases": [
    {
      "type": "BondPrincipal",
      "description": "Transaction type for bond principal event",
      "transactionClass": "Basic",
      "transactionRoles": "AllRoles",
      "isDefault": false
    }
  ],
  "movements": [
    {
      "movementTypes": "CashReceivable",
      "side": "Side2",
      "direction": 1,
      "mappings": [
        {
          "propertyKey": "Transaction/SHKs/BondPrincipal",
          "setTo": "BondPrincipal"
        }
      ],
      "name": "Return principal to separate cash balance"
    },
    {
      "movementTypes": "StockMovement",
      "side": "Side1",
      "direction": -1,
      "name": "Set holding to zero"
    }
  ]
}'
curl -X PUT 'https://<your-domain>.lusid.com/api/api/
transactionconfiguration/types/default/Maturity?scope=default'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <your-API-access-token>'
  -d '{
  "aliases": [
    {
      "type": "Maturity",
      "description": "Transaction type for instrument maturity event",
      "transactionClass": "Basic",
      "transactionRoles": "AllRoles",
      "isDefault": false
    }
  ],
  "movements": []
}'

Note the following:

  • Both transaction types have the expected alias and are located in the default transaction type source (in the URLs above).

  • Our BondPrincipal transaction type has two movements:

    • The first is a CashReceivable movement that maps the principal amount to a BondPrincipal SHK, to report it separately to any other cash balance in the bond currency (USD). You could omit the mappings array to add the principal to the main USD cash holding instead.

    • The second is a StockMovement that sets the holding to zero. Note an instrument never expires in LUSID; it must always be possible to roll back the bitemporal timeline. But setting a holding to zero means it drops out of portfolio valuation reports.

  • Our Maturity transaction type has no movements, and consequently no economic impact. Note you could remove the StockMovement from the BondPrincipal transaction type and apply it to the Maturity transaction type instead. We have chosen to recommend the current approach of using BondPrincipal for both operations because it makes generating journal entry lines simpler. This advice may change in future.

Examining the impact of instrument lifecycle events on the portfolio

9 August 2020

Calling the GetHoldings API before the first coupon payment reveals one holding in the portfolio, for 10,000 settled units of the bond instrument:

We can call the BuildTransactions API with a suitable window to examine the single output transaction that has contributed to this position:

20 March 2024

Calling the GetHoldings API again 'today' reveals we now have two holdings:

  • A holding in the bond instrument for 5000 settled units, reflecting the sale of half the units in August 2023.

  • A USD cash holding categorized by a BondCoupons SHK, representing coupon accrual to date:

We can call the BuildTransactions API with a suitable window to examine the output transactions that have contributed to these positions:

1 January 2030

We can fast-forward LUSID and call the GetHoldings API again after the bond instrument has matured, at which point (and assuming no other activity) we will have two holdings in our portfolio:

  • A USD cash holding categorized by a BondCoupons SHK, representing total coupon accrual.

  • A USD cash holding categorized by a BondPrincipal SHK, representing the return of remaining principal.

Note we no longer have a holding in the bond instrument:

 

We can also fast-forward the BuildTransactions API to examine the output transactions that have contributed to these positions. Note there is no output transaction for the MaturityEvent; LUSID does not render transactions that have no economic impact: