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 to12M
andflowConventions.rollConvention
to15
to determine that coupons are paid annually on 15 August each year.The
exDividendConfiguration.exDividendDays
field is set to5
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 |
| 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 |
| N/A |
| ||
15 August 2026 |
| 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 typeBond
.It is domiciled in the
default
(that is, system) transaction templatescope
.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 thedefault
transaction typesource
.The
transactionDate
is the ex-dividend date and thesettlementDate
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 anexchangeRate
of1
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 aBondCoupons
sub-holding key (SHK), to report accrual separately to other cash holdings in the bond currency (USD). You could omit themappings
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.
|
|
|
|
With regard to transaction types, in summary:
The
BondPrincipal
default transaction template mandates aBondPrincipal
transaction type grouped in thedefault
transaction type source.The
Maturity
default transaction template mandates aMaturity
transaction type grouped in thedefault
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 | Recommended |
|
|
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 aBondPrincipal
SHK, to report it separately to any other cash balance in the bond currency (USD). You could omit themappings
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 theStockMovement
from theBondPrincipal
transaction type and apply it to theMaturity
transaction type instead. We have chosen to recommend the current approach of usingBondPrincipal
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: