Handling exercise and expiry events for ExchangeTradedOption instruments

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 is 80 and the contractSize is 100, as specified by the exchange.

  • The deliveryType is Physical to take delivery of the underlying but it could be Cash.

  • The exerciseType is American so we can exercise at any time but it could be European.

  • The optionType is Call so we can exercise if the BMW share price exceeds the strike price, but it could be Put.

  • The number of contracts is set to 1 and the actual number specified on the transaction.

  • Set refSpotPrice to 0 (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:

  1. Create a corporate action source and subscribe the portfolio to it. See how to do this.

  2. Create transaction types for the transactions automatically generated by OptionExercisePhysicalEvent. More information.

  3. 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 type ExchangeTradedOption.

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

  • 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 the default transaction type source. It impacts the option holding.

    • The second has a condition to restrict it to Call options and belongs to a CallOptionPhysicalExercise transaction type. It establishes a holding in the underlying instrument, in this case an Equity representing BMW shares.

    • The third has a condition to restrict it to Put options and belongs to a PutOptionPhysicalExercise transaction type. It also establishes a holding in the underlying instrument, though note it is not generated in this example since ours is a Call 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 the Txn: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 a CallOptionPhysicalExerciseCustomSide (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 the OptionExercisePhysicalEvent (see below).

  • The instructionType is ElectForHolding 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 be exercise 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 type ExchangeTradedOption.

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

  • 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 the default transaction type source.

  • 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-in Side1.

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):