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 FxForward instruments in order to reduce friction and improve efficiency.”
We'll see how to:
Master a FxForward contract as an instrument in the LUSID Security Master.
Establish a position in a portfolio by purchasing a single unit of the contract.
Examine the events LUSID emits at each milestone in the instrument's lifecycle.
Configure LUSID to automatically exchange the two cashflows at maturity and terminate the contract.
A Jupyter Notebook containing code examples is coming soon.
Mastering a FxForward instrument in LUSID and establishing a position
Imagine on 1 January 2024 we want to enter into a contract to pay/sell 900 EUR and buy/receive 1000 USD in 5 months' time. We can model this FxForward in LUSID as an instrument of type FxForward
with the following economic definition:
{
"instrumentType": "FxForward",
"startDate": "2024-01-01T00:00:00.0000000+00:00",
"maturityDate": "2024-06-01T00:00:00.0000000+00:00",
"domCcy": "USD",
"domAmount": 1000,
"fgnCcy": "EUR",
"fgnAmount": -900,
"isNdf": false
}
Note the following:
The
startDate
is 1 January andmaturityDate
is 1 June 2024, signifying a five month contract.The domCcy must be the currency in which we want our holding in this instrument to be valued. It could be EUR, but we have chosen USD.
The
domAmount
is the amount ofdomCcy
. It could be negative, but we have chosen positive since we are buying/receiving USD.The
fgnCcy
is the other currency, in this case EUR. Note our holding will not be valued in this currency.The
fgnAmount
is the amount offgnCcy
. It must have the opposite sign todomAmount
, so in this example negative sincedomAmount
is positive.isNdf
is false (the default) to signify this is a deliverable contract. Note instrument events have different behavior for NDFs (not examined in this tutorial).
We can now purchase a single unit of this contract in a portfolio that happens to be denominated in a third currency, GBP:
[
{
"transactionId": "Txn-00001",
"type": "StockIn",
"instrumentIdentifiers": {"Instrument/default/ClientInternal": "FxForward-EURUSD-5M"},
"transactionDate": "2024-01-01T00:00:00.0000000+00:00",
"settlementDate": "2024-01-01T00:00:00.0000000+00:00",
"units": 1,
"transactionCurrency": "USD",
"totalConsideration": {"currency": "USD"},
"properties": {
"Transaction/default/TradeToPortfolioRate": {
"key": "Transaction/default/TradeToPortfolioRate",
"value": {
"metricValue": {
"value": 0.79
}
}
}
}
}
]
Note the following:
The type is
StockIn
since there is no cash outlay but it could beBuy
or some other long transaction type.The
transactionDate
andsettlementDate
both mark the start of the contract, 1 January 2024. The instrument defines the length.The number of
units
purchased is1
.The
transactionPrice
field is deliberately omitted, so LUSID sets the price to0
.The
transactionCurrency
andtotalConsideration.currency
(settlement currency) is thedomCcy
of the instrument, so in this caseUSD
.The
totalConsideration.amount
field is deliberately omitted, so LUSID sets the cost to0
.The
Transaction/default/TradeToPortfolioRate
system property records the spot rate on 1 January 2024 from transaction (USD) to portfolio (GBP) currency, in this example 0.79. Note we have chosen to hard-code this rate but you can configure LUSID to look it up dynamically in the Quote Store. See how to do this.
If we examine our holdings in this portfolio on 1 January we see we have a settled position in the FxForward instrument for zero cost:
Understanding instrument events emitted by LUSID
When the instrument matures on 1 June 2024, LUSID automatically emits:
A
FxForwardSettlementEvent
. This event is specific to FxForwards. The intention is that you handle this event to exchange the two cashflows.A
MaturityEvent
. This event is generic to several instrument types. The intention is that you handle this event to set your position in the instrument to zero, so it drops out of holding reports.
LUSID provides a default transaction template for every type of event. In this tutorial we'll choose to use the default transaction templates for both, but you can create your own custom transaction templates to configure the process if you wish.
Examining the default transaction template for settlement events
We can call the GetTransactionTemplate API to examine the default template for FxForwardSettlementEvent
, which at the time of writing is as follows:
{
"instrumentType": "FxForward",
"instrumentEventType": "FxForwardSettlementEvent",
"description": "LUSID default template for automatically generated transactions in respect of FX Forward Settlement events.",
"scope": "default",
"componentTransactions": [
{
"displayName": "Delivery of domestic amount",
"condition": "{{FxForwardSettlementEvent.isNdf}} eq false",
"transactionFieldMap": {
"transactionId": "{{instrumentEventId}}-{{holdingId}}-dom",
"type": "FxForwardDomPrincipal",
"source": "default",
"instrument": "{{instrument}}",
"transactionDate": "{{FxForwardSettlementEvent.maturityDate}}",
"settlementDate": "{{FxForwardSettlementEvent.maturityDate}}",
"units": "{{eligibleBalance}}",
"transactionCurrency": "{{FxForwardSettlementEvent.domCcy}}",
"exchangeRate": "1",
"totalConsideration": {
"currency": "{{FxForwardSettlementEvent.domCcy}}",
"amount": "{{FxForwardSettlementEvent.domTotalAmount}}"
}
},
"transactionPropertyMap": []
},
{
"displayName": "Delivery of foreign amount",
"condition": "{{FxForwardSettlementEvent.isNdf}} eq false",
"transactionFieldMap": {
"transactionId": "{{instrumentEventId}}-{{holdingId}}-fgn",
"type": "FxForwardFgnPrincipal",
"source": "default",
"instrument": "{{instrument}}",
"transactionDate": "{{FxForwardSettlementEvent.maturityDate}}",
"settlementDate": "{{FxForwardSettlementEvent.maturityDate}}",
"units": "{{eligibleBalance}}",
"transactionCurrency": "{{FxForwardSettlementEvent.domCcy}}",
"totalConsideration": {
"currency": "{{FxForwardSettlementEvent.fgnCcy}}",
"amount": "{{FxForwardSettlementEvent.fgnTotalAmount}}"
}
},
"transactionPropertyMap": []
},
{
"displayName": "Non-deliverable forward cash settlement",
"condition": "{{FxForwardSettlementEvent.isNdf}} eq true",
"transactionFieldMap": {
"transactionId": "{{instrumentEventId}}-{{holdingId}}-cash",
"type": "NdfSettlement",
"source": "default",
"instrument": "{{instrument}}",
"transactionDate": "{{FxForwardSettlementEvent.fixingDate}}",
"settlementDate": "{{FxForwardSettlementEvent.maturityDate}}",
"units": "{{eligibleBalance}}",
"transactionPrice": {
"price": "{{FxForwardSettlementEvent.cashFlowPerUnit}}",
"type": "CashFlowPerUnit"
},
"transactionCurrency": "{{FxForwardSettlementEvent.settlementCcy}}",
"exchangeRate": "1",
"totalConsideration": {
"currency": "{{FxForwardSettlementEvent.settlementCcy}}",
"amount": "{{FxForwardSettlementEvent.cashFlowAmount}}"
}
},
"transactionPropertyMap": []
}
],
...
}
Note the following:
This template handles instrument events of type
FxForwardSettlementEvent
emitted by instruments of typeFxForward
.It is domiciled in the
default
(that is, system) transaction templatescope
.It contains instructions for generating three output transactions in every portfolio with a holding in the instrument. We can examine the
condition
field to see that the first two transactions are for a deliverable FxForward and the third one (at the bottom) is for an NDF.The first transaction settles the domestic leg of a deliverable FxForward (USD in our example). It mandates a transaction type of
FxForwardDomPrincipal
(grouped in asource
ofdefault
) and sets both the transaction and settlement currency todomCcy
, so theexchangeRate
between them is1
.The second transaction settles the foreign leg (EUR in our example). It mandates a transaction type of
FxForwardFgnPrincipal
(also grouped in asource
ofdefault
) and sets the settlement currency tofgnCcy
(EUR) but the transaction currency todomCcy
(USD). Note theexchangeRate
field is omitted, so LUSID must be configured to look up the USD/EUR spot rate on the maturity date dynamically in the Quote Store. See how to do this.
Creating a suitable transaction type for settlement event output transactions
The default transaction template mandates FxForwardDomPrincipal
and FxForwardFgnPrincipal
transaction types grouped in the default
transaction type source. We must create these transaction types if they do not exist.
Note: Transaction types are grouped in sources and reside in scopes. For more information on the difference, see this article.
The definition of FxForwardDomPrincipal
and FxForwardFgnPrincipal
can actually be the same, so we can call the SetTransactionType API to create a single transaction type with two aliases, for example:
curl -X PUT 'https://<your-domain>.lusid.com/api/api/transactionconfiguration/types/default/FxForwardDomPrincipal?scope=default'
-H 'Content-Type: application/json-patch+json'
-H 'Authorization: Bearer <your-API-access-token>'
-d '{
"aliases": [
{
"type": "FxForwardDomPrincipal",
"description": "Settle the domestic leg of a FxForwardSettlementEvent",
"transactionClass": "CashFlows",
"transactionRoles": "AllRoles",
"isDefault": false
},
{
"type": "FxForwardFgnPrincipal",
"description": "Settle the foreign leg of a FxForwardSettlementEvent",
"transactionClass": "CashFlows",
"transactionRoles": "AllRoles",
"isDefault": false
}
],
"movements": [
{
"name": "Virtually sell the individual legs to calculate realised gain/loss",
"movementTypes": "StockMovement",
"side": "SignedSide1",
"direction": -1,
"movementOptions": ["Virtual"]
},
{
"name": "Manifest the cash holdings",
"movementTypes": "CashCommitment",
"side": "Side2",
"direction": 1,
}
],
"calculations": [
{
"type": "Txn:TradeToPortfolioRate"
},
{
"type": "Txn:ExchangeRate"
}
]
}'
Note the following:
The first movement uses the
Virtual
option to calculate realised gain/loss for each leg without actually effecting a sale. Note the direction is negative and a customSignedSide1
is used (see below).The second movement creates currency holdings using the built-in
Side2
. Note the direction is positive, but the side respects the sign of each leg's total consideration, so the USD holding ends up positive and the EUR holding ends up negative.The calculation types configure LUSID to look up exchange rates from transaction to portfolio and from transaction to settlement currencies respectively in the Quote Store, which is required for the maturity date. See how to set this up.
We can call the SetSideDefinition API to create the custom SignedSide1
(note this step must be performed before creating the transaction type). The only difference to the built-in Side1
is the amount field:
In
Side1
the value isTxn:TradeAmount
, which is a calculated field that converts the sign of the amount to match that of the units.In
SignedSide1
the value isTxn:TotalConsiderationInTradeCurrency
, which is the same except it respects the sign of the amount; this is important because the total consideration of one leg (EUR in our example) is negative.
For example:
curl -X PUT 'https://<your-domain>.lusid.com/api/api/transactionconfiguration/sides/SignedSide1?scope=default'
-H 'Content-Type: application/json-patch+json'
-H 'Authorization: Bearer <your-API-access-token>'
-d '{
"security": "Txn:LusidInstrumentId",
"currency": "Txn:TradeCurrency",
"rate": "Txn:TradeToPortfolioRate",
"units": "Txn:Units",
"amount": "Txn:TotalConsiderationInTradeCurrency"
}'
Examining the default transaction template for maturity events
In the same way, we can call the GetTransactionTemplate API to examine the default transaction template for MaturityEvent
, which at the time of writing is as follows. Note the price and cost are set to zero:
{
"instrumentType": "FxForward",
"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": []
}
],
...
}
Creating a suitable transaction type for maturity event output transactions
The default transaction template mandates a Maturity
transaction type grouped in the default
transaction type source. We must call the SetTransactionType API to create this transaction type if it does not exist, for example:
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": "Generic transaction type for maturity events for many different instrument types",
"transactionClass": "Basic",
"transactionRoles": "AllRoles",
"isDefault": false
}
],
"movements": [
{
"name": "Reduce holding to zero",
"movementTypes": "StockMovement",
"side": "Side1",
"direction": -1
}
]
}'
Loading spot rates for the maturity date into the LUSID Quote Store
In order that LUSID may calculate realised gains/losses for the output transactions generated by FxForwardSettlementEvent
, we must load the following exchange rates into the LUSID Quote Store for the maturity date, 1 June 2024:
Rate from the
domCcy
of the instrument (USD in our example) to thefgnCcy
(EUR). Let's imagine the USD/EUR rate has moved in our favour from the implied strike rate of 0.90 in the instrument definition to 0.93.Rate from the
domCcy
(USD) to the portfolio currency (GBP). Let's imagine the USD/GBP rate has slipped from 0.79 (hard-coded in the purchase transaction) to 0.75.
We can call the UpsertQuotes API to load these exchange rates into the Quote Store. See an example.
In order for LUSID to locate and apply these exchange rates, we must also:
Apply the
Txn:TradeToPortfolioRate
andTxn:ExchangeRate
calculation types to theFxForwardDomPrincipal
andFxForwardFgnPrincipal
transaction types, as demonstrated above.Create a new (or configure an existing) recipe to have market data rules able to locate the rates loaded into the Quote Store. See an example.
Register this recipe with the portfolio(s) holding the FxForward. See how to patch an existing portfolio.
Examining the impact of instrument lifecycle events on the portfolio at maturity date
Calling the GetHoldings API on 1 June 2024 reveals that the FxForwardSettlementEvent
has generated two cash holdings in the portfolio representing the exchange of the two currencies, one for 1000 USD and one for -900 EUR:
Note the following:
We no longer have a holding in the FxForward instrument itself since the number of units was set to zero by the
MaturityEvent
.The portfolio cost of the USD holding reflects the cost of ‘purchasing’ 1000 USD at the USD/GBP (trade to portfolio rate) of 0.75 on 1 June 2024, namely £750.
The portfolio cost of the EUR holding reflects the same original cost minus the gain we have made from entering into the forward contract (relative to not having done so): £750 - £24.19 = £725.81.
We can call the BuildTransactions API with a suitable window to see the three output transactions that have contributed to these positions, and examine the gain/loss calculations in more detail. The first two transactions are generated by FxForwardSettlementEvent
and the third by MaturityEvent
:
To see the realised gain/loss calculations, click the +/- button at the end of each row (highlighted in red above). For the first transaction settling the domestic (USD) leg, we can see we made a loss of -£40 because the USD/GBP exchange rate slipped from 0.79 on 1 January to 0.75 on 1 June:
However, for the foreign (EUR) leg we see we made a gain of £64.19 because the USD/EUR exchange rate improved from 0.9 (implied) on 1 January to 0.93 on 1 June. Our total realised gain for the instrument as a whole is therefore £64.19 - £40 = £24.19, or $32.26 at the USD/GBP exchange rate of 0.75:
We can see this more clearly by creating an A2B report for the period 31 December 2023 11:59:59 to 1 June 2024 (note setting the start date to just before the initial purchase of the FxForward contract prevents LUSID calculating a MarketValue (Start), which is not useful in this situation):