You can model an exchange-traded options contract as an instrument of type ExchangeTradedOption
in LUSID. See how to do this.
LUSID can emit the following instrument lifecycle events for an ExchangeTradedOption
, which you may handle to reduce workload and improve efficiency:
OptionExercisePhysicalEvent
. This event is not automatically emitted by LUSID, but can be manually triggered to exercise a physically-settled option that is in-the-money. More information.OptionExerciseCashEvent
. This event is not automatically emitted by LUSID, but can be manually triggered to exercise a cash-settled option that is in-the-money. More information coming soon.ExpiryEvent
. This event is automatically emitted by LUSID on the expiry date, but only if you have chosen not to exercise. More information.
Mastering an instrument and establishing a position in a portfolio
Imagine we have a options contract on BMW shares trading on the Eurex exchange that expires on Friday 21 March 2025. We can model this in LUSID as an instrument of type ExchangeTradedOption
with the following economic definition:
"definition": {
"instrumentType": "ExchangeTradedOption",
"startDate": "2025-01-01T00:00:00.0000000+00:00",
"contractDetails": {
"domCcy": "EUR",
"strike": 80,
"contractSize": 100,
"country": "DE",
"deliveryType": "Physical",
"description": "BMW GR Equity OMON",
"exchangeCode": "Eurex",
"exerciseDate": "2025-03-21T00:00:00.0000000+00:00",
"exerciseType": "American",
"optionCode": "BMW",
"optionType": "Call",
"underlying": {
"instrumentType": "MasteredInstrument",
"identifiers": {"Instrument/default/Isin": "DE0005190003"}
},
"underlyingCode": "MyIDForBMWQuotes"
},
"contracts": 1,
"refSpotPrice": 0
}
For much more information on modelling instruments of type ExchangeTradedOption
, and how to reference the underlying BMW equity as a MasteredInstrument
, see this article. Note with regard to instrument events:
The
strike
price of the underlying is80
and thecontractSize
is100
, as specified by the exchange.The
deliveryType
isPhysical
to take delivery of the underlying but it could beCash
.The
exerciseType
isAmerican
so we can exercise at any time but it could beEuropean
.The
optionType
isCall
so we can exercise if the BMW share price exceeds the strike price, but it could bePut
.The number of
contracts
is set to1
and the actual number specified on the transaction.Set
refSpotPrice
to0
(or omit it, since this is the default) unless directed otherwise by FINBOURNE Technical Support.
We can now establish a position by booking a transaction in this instrument in a suitable portfolio, for example:
{
"transactionId": "my_option_purchase_001",
"type": "BuyETO",
"instrumentIdentifiers": {"Instrument/default/LusidInstrumentId": "LUID_00003EB8"},
"transactionDate": "2025-01-14T00:00:00.0000000+00:00",
"settlementDate": "2024-01-15T00:00:00.0000000+00:00",
"units": 1000,
"transactionPrice": {
"price": 3.13,
"type": "Price"
},
"totalConsideration": {
"amount": 0,
"currency": "EUR"
},
"properties": {
"Transaction/MyProperties/TradeCommission": {
"key": "Transaction/MyProperties/TradeCommission",
"value": {
"metricValue": {
"value": 170,
"unit": "EUR"
}
}
}
}
}
For much more information on booking transactions in ExchangeTradedOption
instruments, including details of a BuyETO
transaction type that automatically calculates gross consideration and total consideration, see this article. Note with regard to instrument events:
LUSID calculates gross consideration as
(units * price * contracts * contract size) / scale factor
, so in this case(1000 * 3.13 * 1 * 100) / 1 = 313,000
.The trade commission is recorded using a custom property, and LUSID calculates total consideration as gross consideration plus this amount:
313,000 + 170 = 313,170
.
Exercising a physically-settled option
If the deliveryType
of the ExchangeTradedOption
is Physical
, you can choose to exercise an in-the-money option by triggering LUSID to emit an OptionExercisePhysicalEvent
. To do this:
Create a corporate action source and subscribe the portfolio to it. See how to do this.
Create transaction types for the transactions automatically generated by
OptionExercisePhysicalEvent
. More information.Load an event instruction into the portfolio to trigger
OptionExercisePhysicalEvent
on a particular date. More information.
Note no market data is required to exercise a physically-settled option. LUSID exercises at the strike price specified in the instrument definition.
Examining the default transaction template for OptionExercisePhysicalEvent
LUSID provides a default transaction template for every type of event. In this tutorial we’ll use the default template for OptionExercisePhysicalEvent
, but note you can create a custom transaction template to configure the process if you wish.
We can call the GetTransactionTemplate API to examine this default template, which at the time of writing is as follows:
{
"instrumentType": "ExchangeTradedOption",
"instrumentEventType": "OptionExercisePhysicalEvent",
"description": "LUSID default template for automatically generated transactions in respect of physically-settled exchange traded option exercise.",
"scope": "default",
"componentTransactions": [
{
"displayName": "Adjust option position cost",
"condition": "{{OptionExercisePhysicalEvent.ChosenElection}} eq {{OptionExercisePhysicalEvent.OptionExerciseElection}}",
"transactionFieldMap": {
"transactionId": "{{instrumentEventId}}-{{holdingId}}-adjust-existing",
"type": "PhysicallySettledOptionExercise",
"source": "default",
"instrument": "{{instrument}}",
"transactionDate": "{{OptionExercisePhysicalEvent.exerciseDate}}",
"settlementDate": "{{OptionExercisePhysicalEvent.deliveryDate}}",
"units": "{{eligibleBalance}}",
"transactionPrice": {
"price": "0",
"type": "Price"
},
"transactionCurrency": "{{holdingCurrency}}",
"exchangeRate": "1",
"totalConsideration": {
"currency": "{{holdingCurrency}}",
"amount": "{{holdingCost}}"
}
},
"transactionPropertyMap": []
},
{
"displayName": "Exercise Call",
"condition": "{{OptionExercisePhysicalEvent.ChosenElection}} eq {{OptionExercisePhysicalEvent.OptionExerciseElection}} and {{OptionExercisePhysicalEvent.optionType}} eq 'call'",
"transactionFieldMap": {
"transactionId": "{{instrumentEventId}}-{{holdingId}}-exercise-call",
"type": "CallOptionPhysicalExercise",
"source": "default",
"instrument": "{{OptionExercisePhysicalEvent.underlyingInstrument}}",
"transactionDate": "{{OptionExercisePhysicalEvent.exerciseDate}}",
"settlementDate": "{{OptionExercisePhysicalEvent.deliveryDate}}",
"units": "{{OptionExercisePhysicalEvent.entitledUnits}}",
"transactionPrice": {
"price": "0",
"type": "Price"
},
"transactionCurrency": "{{OptionExercisePhysicalEvent.strikeCurrency}}",
"exchangeRate": "1",
"totalConsideration": {
"currency": "{{OptionExercisePhysicalEvent.strikeCurrency}}",
"amount": "{{OptionExercisePhysicalEvent.underlyingTotalConsideration}}"
}
},
"transactionPropertyMap": [
{
"propertyKey": "Transaction/default/GrossConsideration",
"value": "{{OptionExercisePhysicalEvent.strikeAmount}}"
}
]
},
{
"displayName": "Exercise Put",
"condition": "{{OptionExercisePhysicalEvent.ChosenElection}} eq {{OptionExercisePhysicalEvent.OptionExerciseElection}} and {{OptionExercisePhysicalEvent.optionType}} eq 'put'",
"transactionFieldMap": {
"transactionId": "{{instrumentEventId}}-{{holdingId}}-exercise-put",
"type": "PutOptionPhysicalExercise",
"source": "default",
"instrument": "{{OptionExercisePhysicalEvent.underlyingInstrument}}",
"transactionDate": "{{OptionExercisePhysicalEvent.exerciseDate}}",
"settlementDate": "{{OptionExercisePhysicalEvent.deliveryDate}}",
"units": "{{OptionExercisePhysicalEvent.entitledUnits}}",
"transactionPrice": {
"price": "0",
"type": "Price"
},
"transactionCurrency": "{{OptionExercisePhysicalEvent.strikeCurrency}}",
"exchangeRate": "1",
"totalConsideration": {
"currency": "{{OptionExercisePhysicalEvent.strikeCurrency}}",
"amount": "{{OptionExercisePhysicalEvent.underlyingTotalConsideration}}"
}
},
"transactionPropertyMap": [
{
"propertyKey": "Transaction/default/GrossConsideration",
"value": "{{OptionExercisePhysicalEvent.strikeAmount}}"
}
]
}
],
...
}
Note the following:
This template handles instrument events of type
OptionExercisePhysicalEvent
emitted by instruments of typeExchangeTradedOption
.It is domiciled in the
default
(that is, system) transaction templatescope
.It contains instructions for automatically generating output transactions in every portfolio with a holding in the option:
The first generated transaction belongs to a
PhysicallySettledOptionExercise
transaction type grouped in thedefault
transaction typesource
. It impacts the option holding.The second has a
condition
to restrict it toCall
options and belongs to aCallOptionPhysicalExercise
transaction type. It establishes a holding in the underlying instrument, in this case anEquity
representing BMW shares.The third has a
condition
to restrict it toPut
options and belongs to aPutOptionPhysicalExercise
transaction type. It also establishes a holding in the underlying instrument, though note it is not generated in this example since ours is aCall
option.
Note: This template does not set the
Transaction/default/TradeToPortfolioRate
system property for any generated transaction. If the portfolio base currency is different, we recommend specifying theTxn:TradeToPortfolioRate
calculation in transaction types to look up spot rates dynamically in the LUSID Quote Store. See how to do this.
Creating a suitable PhysicallySettledOptionExercise transaction type
The default transaction template mandates a PhysicallySettledOptionExercise
transaction type grouped in the default
transaction type source for the first generated transaction. 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.
We can call the SetTransactionType API as follows:
curl -X PUT 'https://<your-domain>.lusid.com/api/api/transactionconfiguration/types/default/PhysicallySettledOptionExercise?scope=default'
-H 'Content-Type: application/json-patch+json'
-H 'Authorization: Bearer <your-API-access-token>'
-d '{
"aliases": [
{
"type": "PhysicallySettledOptionExercise",
"description": "Transaction type for physically settled option event",
"transactionClass": "Basic",
"transactionRoles": "AllRoles",
"isDefault": false
}
],
"movements": [
{
"name": "Adjust cost of option",
"movementTypes": "StockMovement",
"side": "Side1",
"direction": -1
}
]
}'
Note the following:
The transaction type alias is
PhysicallySettledOptionExercise
, to comply with the default transaction template.The transaction type source is
default
(specified in the URL), to comply with the default transaction template.You can design a transaction type to have any economic impact you like. This recommendation has one
StockMovement
, to decrease the units and cost of the option holding down to zero.
Creating a suitable CallOptionPhysicalExercise transaction type
For the second generated transaction, the default transaction template mandates a CallOptionPhysicalExercise
transaction type grouped in the default
transaction type source. We must create this transaction type if it does not exist for the Call
option in this example.
We can call the SetTransactionType API as follows:
curl -X PUT 'https://<your-domain>.lusid.com/api/api/transactionconfiguration/types/default/CallOptionPhysicalExercise?scope=default'
-H 'Content-Type: application/json-patch+json'
-H 'Authorization: Bearer <your-API-access-token>'
-d '{
"aliases": [
{
"type": "CallOptionPhysicalExercise",
"description": "Transaction type for physically settled call option event",
"transactionClass": "Basic",
"transactionRoles": "AllRoles",
"isDefault": false
}
],
"movements": [
{
"name": "Increase cost of underlying",
"movementTypes": "StockMovement",
"side": "Side1",
"direction": 1
},
{
"name": "Decrease cash balance",
"movementTypes": "CashCommitment",
"side": "CallOptionPhysicalExerciseCustomSide",
"direction": -1
}
]
}'
Note the following:
The transaction type alias is
CallOptionPhysicalExercise
, to comply with the default transaction template.The transaction type source is
default
(specified in the URL), to comply with the default transaction template.You can design a transaction type to have any economic impact you like. This recommendation has two movements:
The first is a
StockMovement
to establish a holding in the underlying instrument with the units and cost set to the number of option contracts multiplied by the contract size of each at the strike price.The second is a
CashCommitment
with aCallOptionPhysicalExerciseCustomSide
(see below) that decreases a currency holding by the gross consideration.
We can call the SetSideDefinition API to create a CallOptionPhysicalExerciseCustomSide
as follows:
curl -X PUT 'https://<your-domain>.lusid.com/api/api/transactionconfiguration/sides/CallOptionPhysicalExerciseCustomSide?scope=default'
-H 'Content-Type: application/json-patch+json'
-H 'Authorization: Bearer <your-API-access-token>'
-d '{
"security": "Txn:SettleCcy",
"currency": "Txn:SettlementCurrency",
"rate": "SettledToPortfolioRate",
"units": "Transaction/default/GrossConsideration",
"amount": "Transaction/default/GrossConsideration"
}'
Note this is the same as the built-in Side2
except the units
and amount
fields are set to the gross rather than the total consideration.
Loading an event instruction to trigger OptionExercisePhysicalEvent
We can call the UpsertInstrumentEventInstructions API to load an event instruction into the portfolio on 28 February and trigger OptionExercisePhysicalEvent
, for example:
{
"instrumentEventInstructionId": "ExerciseAmericanCallOptionOnBMW-2025-02-28",
"instrumentEventId": "LUID_00003EB8_OptionExercisePhysicalEvent_American",
"instructionType": "ElectForHolding",
"holdingId": 75498862,
"electionKey": "exercise",
"entitlementDateInstructed": "2025-02-28T00:00:00.0000000+00:00"
}
For more information on event instructions, see this article. Note the following:
The
instrumentEventInstructionId
can be any intuitive string that uniquely identifies this event instruction in this portfolio.The
instrumentEventId
must be the unique identifier of theOptionExercisePhysicalEvent
(see below).The
instructionType
isElectForHolding
to apply the event to our option holding in the portfolio.The
holdingId
must be the unique identifier for this holding (see below).The
electionKey
must beexercise
to trigger LUSID to emit the event.The
entitlementDateInstructed
field is set to the date we wish to exercise.
To discover the instrumentEventId
and holdingId
, call the QueryApplicableInstrumentEvents API for the portfolio, for example:
{
"values": [
{
"portfolioId": {"scope": "UKEquities", "code": "MyPortfolio"},
"lusidInstrumentId": "LUID_00003EB8",
"instrumentType": "ExchangeTradedOption",
"instrumentEventType": "OptionExercisePhysicalEvent",
"instrumentEventId": "LUID_00003EB8_OptionExercisePhysicalEvent_American"
"holdingId": 75498862,
...
Examining the impact of OptionExercisePhysicalEvent on the portfolio
15 January
We can use Dashboard > Holdings in the LUSID web app to call the GetHoldings API on the settlement date of the option purchase to see that we start with:
An option holding for 1000 units at a cost of 313,710
A currency holding for -€313,710:
28 February
On 28 February we load the event instruction to exercise the option and then examine holdings again:
The option holding has been removed since units and cost are now zero. Note there is no P&L associated with this movement.
A holding in the underlying BMW equity instrument has been established, with the units set to the number of BMW shares purchased (
1000 × 100 = 100,000
) and the cost to that of those shares at the strike price plus the cost of the option:(100,000 × 80) + 313,170 = 8,313,170
.The currency holding reflects the total cost of establishing the BMW holding: -€8,313,710:
We can use Dashboard > Transactions in Output mode to call the BuildTransactions API with a suitable window to examine the output transactions generated by this event:
Expiring an unexercised option
If you do not exercise an option before the expiry date specified in the instrument definition, LUSID automatically emits an ExpiryEvent
.
Examining the default transaction template for ExpiryEvent
LUSID provides a default transaction template for every type of event. In this tutorial we’ll use the default template for ExpiryEvent
, but note you can create a custom transaction template to configure the process if you wish.
We can call the GetTransactionTemplate API to examine this default template, which at the time of writing is as follows:
{
"instrumentType": "ExchangeTradedOption",
"instrumentEventType": "ExpiryEvent",
"description": "LUSID default template for automatically generated transactions in respect of instrument expiry events.",
"scope": "default",
"componentTransactions": [
{
"displayName": "Expiry",
"transactionFieldMap": {
"transactionId": "{{instrumentEventId}}-{{holdingId}}",
"type": "Expiry",
"source": "default",
"instrument": "{{instrument}}",
"transactionDate": "{{ExpiryEvent.expiryDate}}",
"settlementDate": "{{ExpiryEvent.expiryDate}}",
"units": "{{eligibleBalance}}",
"transactionPrice": {
"price": "0",
"type": "Price"
},
"transactionCurrency": "{{holdingCurrency}}",
"exchangeRate": "1",
"totalConsideration": {
"currency": "{{holdingCurrency}}",
"amount": "0"
}
},
"transactionPropertyMap": []
}
],
...
}
Note the following:
This template handles instrument events of type
ExpiryEvent
emitted by instruments of typeExchangeTradedOption
.It is domiciled in the
default
(that is, system) transaction templatescope
.It contains instructions for automatically generating a single output transaction in every portfolio with a holding in the unexercised option.
The generated transaction belongs to a
Expiry
transaction type grouped in thedefault
transaction typesource
.The price and total consideration of the generated transaction are set to
0
.
Creating a suitable Expiry transaction type
The default transaction template mandates an Expiry
transaction type grouped in the default
transaction type source. We must create this transaction type if it does not exist.
We can call the SetTransactionType API as follows:
curl -X PUT 'https://<your-domain>.lusid.com/api/api/transactionconfiguration/types/default/Expiry?scope=default'
-H 'Content-Type: application/json-patch+json'
-H 'Authorization: Bearer <your-API-access-token>'
-d '{
"aliases": [
{
"type": "Expiry",
"description": "Transaction type for expiry event",
"transactionClass": "Basic",
"transactionRoles": "AllRoles",
"isDefault": false
}
],
"movements": [
{
"name": "Set units to zero",
"movementTypes": "StockMovement",
"side": "Side1",
"direction": -1
}
]
}'
Note the following:
The transaction type alias is
Expiry
, to comply with the default transaction template.The transaction type source is
default
(specified in the URL), to comply with the default transaction template.You can design a transaction type to have any economic impact you like. This recommendation has one
StockMovement
, to decrease the units and cost of the option holding down to zero using the built-inSide1
.
Examining the impact of ExpiryEvent on the portfolio
15 January
We can use Dashboard > Holdings in the LUSID web app to call the GetHoldings API on the settlement date of the option purchase transaction to see that we start with:
An option holding for 1000 units at a cost of 313,710
A currency holding for -€313,710:
21 March
If we move the effective date to the expiry date we can see that the option holding has been removed (units set to zero) and the currency holding is unchanged:
We can use Dashboard > Transactions in Output mode to call the BuildTransactions API with a suitable window to examine the output transaction generated by this event:
To audit P&L associated with this transaction, click the ± button at the end of the row (highlighted above):