Modelling exchange-traded options in LUSID


You can model an exchange-traded options contract as an instrument of type ExchangeTradedOption. See all supported instruments.

Mastering an instrument

There are numerous tools you can use to master an ExchangeTradedOption in the LUSID Security Master.

Some fields are common to all types of instrument, such as an intuitive name, the requirement to specify a set of identifiers, and the facility to store extra information as properties.

Fields in the economic definition object are specific to ExchangeTradedOption. For more information on these fields, select ExchangeTradedOption from the definition dropdown in the API Reference:

Specifying an underlying instrument

If the underlying of an ExchangeTradedOption is:

  • An equity or an exchange-traded future, the recommended approach is to master that as a separate instrument in LUSID and provide a reference to a MasteredInstrument. If you do this, instrument events are available. See below for more information.

  • A bond, interest rate, index, commodity or any other type of asset, the only option is to provide an inline definition as part of the ExchangeTradedOption. Instrument events are not available.

Mastering an ExchangeTradedOption instrument

The following call to the UpsertInstruments API masters an option on AAPL that are already mastered as an instrument of type Equity in LUSID:

curl -X POST 'https://mydomain.lusid.com/api/api/instruments?scope=MyCustomInstrScope'
   -H 'Content-Type: application/json-patch+json'
   -H 'Authorization: Bearer <my-API-access-token>'
   -d '{"upsert-request-1": {
    "name": "OptionOnAPPL",
    "identifiers": {"ClientInternal": {"value": "OptionOnAPPL"}},
    "definition": {
      "instrumentType": "ExchangeTradedOption",
      "startDate": "2024-09-21T00:00:00.0000000+00:00",
      "contractDetails": {
        "domCcy": "USD",
        "strike": 250,
        "contractSize": 100,
        "country": "US",
        "deliveryType": "Physical",
        "description": "Option on APPL equity",
        "exchangeCode": "CME",
        "exerciseDate": "2024-12-21T00:00:00.0000000+00:00",
        "exerciseType": "American",
        "optionCode": "ZQF4C",
        "optionType": "Call",
        "underlying": {
          "instrumentType": "MasteredInstrument",
          "identifiers": {"Instrument/default/Figi": "US0378331005"}
        },
        "underlyingCode": "MyIDForAPPLQuotes"
      },
      "contracts": 1,
      "refSpotPrice": 0
    }
  }
}'

Note the following:

  • The ExchangeTradedOption instrument is mastered in a custom instrument scope (specified in the URL).

  • The identifiers object is set to reference a ClientInternal unique identifier.

  • startDate is a mandatory field but note is not subsequently used by LUSID.

  • deliveryType can be Physical (if you intend to take delivery of the underlying) or Cash.

  • exerciseType can be American, European or Bermudan.

  • optionType can be Put or Call.

  • The underlying object must either:

    • Have the underlying.instrumentType field set to MasteredInstrument if the underlying is already mastered as an Equity or Future in LUSID, and the underlying.identifiers object set to reference a unique identifier for that instrument, in this case a FIGI. Note the underlying must reside in the same instrument scope as the ExchangeTradedOption, or else in the default instrument scope.

    • Be populated with a full inline economic definition if the underlying is any other type of instrument.

  • underlyingCode can be set to any intuitive string that represents a market data identifier for the underlying instrument loaded into the LUSID Quote Store.

  • contracts should be set to 1 and the amount bought or sold specified on the transaction.

Providing the request is successful, the response:

  • Confirms the globally-unique LUID of the ExchangeTradedOption instrument (in this case LUID_00003E8D) and also that of the MasteredInstrument if LUSID is able to resolve it correctly (in this case LUID_00003E8E). If not, the unknown instrument is returned.

  • Generates extra fields that are stored as part of the instrument definition and can be filtered on.

  • Supplies default values for fields not explicitly specified in the request:

{
  "values": {
    "upsert-request-1": {
      "scope": "MyCustomInstrScope",
      "lusidInstrumentId": "LUID_00003E8D",
      "name": "OptionOnAPPL",
      "identifiers": {
        "ClientInternal": "OptionOnAPPL",
        "LusidInstrumentId": "LUID_00003E8D"
      },
      "properties": [],
      "instrumentDefinition": {
        "startDate": "2024-09-21T00:00:00.0000000+00:00",
        "contractDetails": {
          "domCcy": "USD",
          "strike": 250,
          "contractSize": 100,
          "country": "US",
          "deliveryType": "Physical",
          "description": "Option on APPL",
          "exchangeCode": "CME",
          "exerciseDate": "2024-12-21T00:00:00.0000000+00:00",
          "exerciseType": "American",
          "optionCode": "ZQF4C",
          "optionType": "Call",
          "underlying": {
            "identifiers": {
              "Instrument/default/Figi": "US0378331005"
            },
            "masteredDomCcy": "USD",
            "masteredInstrumentType": "Equity",
            "masteredLusidInstrumentId": "LUID_00003E8E",
            "masteredName": "Apple",
            "masteredScope": "MyCustomInstrScope",
            "masteredAssetClass": "Equities",
            "instrumentType": "MasteredInstrument"
          },
          "underlyingCode": "MyIDForAPPLQuotes",
          "deliveryDays": 0,
          "businessDayConvention": "F",
          "settlementCalendars": []
        },
        "contracts": 1,
        "refSpotPrice": 0,
        "tradingConventions": {
          "priceScaleFactor": 1,
          "minimumOrderSize": 0,
          "minimumOrderIncrement": 0
        },
        "instrumentType": "ExchangeTradedOption"
      },
      "state": "Active",
      "assetClass": "Unknown",
      "domCcy": "USD",
      "relationships": []
    }
  },
  ...
}

Booking a transaction to establish a position

Once an instrument is mastered, you can book a transaction in an ExchangeTradedOption instrument to record the acquisition of a quantity in a particular transaction portfolio, for example:

curl -X POST 'https://mydomain.lusid.com/api/api/transactionportfolios/MyPortfolioScope/MyPortfolioCode/transactions/$batchUpsert?successMode=Partial&preserveProperties=true'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <API-access-token>'
  -d '{
  "transactionRequest-1": {
    "transactionId": "my_option_purchase_001",
    "type": "BuyETO",
    "instrumentIdentifiers": {"Instrument/default/LusidInstrumentId": "LUID_00003E8D"},
    "transactionDate": "2024-10-01T09:00:00.0000000+00:00",
    "settlementDate": "2024-10-03T09:00:00.0000000+00:00",
    "units": 1,
    "transactionPrice": {
      "price": 100,
      "type": "Price"
    },
    "totalConsideration": {
      "amount": 0,
      "currency": "USD"
    },
    "properties": {
      "Transaction/MyProperties/TradeCommission": {
        "key": "Transaction/MyProperties/TradeCommission",
        "value": {
          "metricValue": {
            "value": 50,
            "unit": "USD"
          }
        }
      }
    }
  }
}'

Note the following:

  • The type field invokes a custom BuyETO transaction type to confer a particular economic impact (see below).

  • The units field specifies the number of contracts.

  • The transactionPrice object records the market price of the options contract (not the underlying). This is used by LUSID to automatically calculate gross consideration (see below).

  • The totalConsideration object:

    • Sets the settlement currency to USD.

    • Specifies a cost of 0 to enable LUSID to automatically derive total consideration (see below).

  • The cost of entering into the contract is recorded as a custom property.

Note: This example assumes the transaction, settlement and portfolio currencies are all the same. If not, you can specify exchange rates.

You might create a BuyETO transaction type as follows:

curl -X PUT 'https://mydomain.lusid.com/api/api/transactionconfiguration/types/default/BuyETO?scope=default'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <API-access-token>'
  -d '{
  "aliases": [
    {
      "type": "BuyETO",
      "description": "Transaction type for option purchases",
      "transactionClass": "Basic",
      "transactionRoles": "AllRoles",
      "isDefault": false
    }
  ],
  "movements": [
    {
      "name": "Increase units by number purchased",
      "movementTypes": "StockMovement",
      "side": "Side1",
      "direction": 1
    },
    {
      "name": "Decrease cash by cost of trade commission",
      "movementTypes": "CashCommitment",
      "side": "BuyETOCashCustomSide",
      "direction": -1
    }
  ],
  "calculations": [
    {
      "type": "Txn:GrossConsideration"
    },
    {
      "type": "DeriveTotalConsideration",
      "formula": "Txn:GrossConsideration + Properties[Transaction/MyProperties/TradeCommission]"
    }
  ]
}'

Note the following:

  • The StockMovement movement sets the units of the holding to the number purchased in the transaction.

  • The CashCommitment movement decreases a cash balance by the trade commission (see the custom side below).

  • The Txn:GrossConsideration transaction type calculation automatically calculates gross consideration (amount before fees) as price * units * contractSize, and stores the result in the Transaction/default/GrossConsideration system property.

  • The DeriveTotalConsideration transaction type calculation automatically calculates the totalConsideration.amount field according to the given formula, which in this case sums gross consideration and commission.

You might create a MyBuyETOCashCustomSide as follows:

curl -X PUT 'https://mydomain.lusid.com/api/api/transactionconfiguration/sides/MyBuyCashETOCustomSide?scope=default'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <my-API-access-token>'
  -d '{
  "security": "Txn:SettleCcy",
  "currency": "Txn:SettlementCurrency",
  "rate": "SettledToPortfolioRate",
  "units": "Transaction/MyProperties/TotalCapitalisedFees",
  "amount": "Transaction/MyProperties/TotalCapitalisedFees"
}'

Loading market data and confirming positions

It is mandatory to load market data for the underlying instrument into the LUSID Quote Store before you can audit your position in a portfolio holding an ExchangeTradedOption instrument.

Loading market data

You must load a market price for the underlying (in this case AAPL) on the settlement date (in this case 3 October 2024) of a transaction in a ExchangeTradedOption, for example:

curl -X POST 'https://mydomain.lusid.com/api/api/quotes/MyEquityQuotes'
  -H 'Authorization: Bearer <my-API-access-token>'
  -H 'Content-Type: application/json-patch+json'
  -d '{
    "Quote-0001": {
      "quoteId": {
        "quoteSeriesId": {
          "provider": "Lusid",
          "instrumentIdType": "LusidInstrumentId",
          "instrumentId": "MyIDForAPPLQuotes",
          "quoteType": "Price",
          "field": "mid"
        },
        "effectiveAt": "2024-10-03T00:00:00Z"
      },
      "metricValue": {
        "value": 240, "unit": "USD"
      }
    }
  }'

Note the following:

  • This price is encapsulated in a MyEquityQuotes quote scope (specified in the URL).

  • The instrumentIdType must be LusidInstrumentId.

  • The instrumentId must be the underlyingCode specified in the ExchangeTradedOption instrument definition, in this case MyIDForAPPLQuotes.

  • The provider, quoteType and field fields should be set as above, to avoid validation errors.

Creating a recipe to locate this market data

You must create a recipe that is able to locate this market data, for example:

curl -X POST 'https://mydomain.lusid.com/api/api/recipes'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <API-access-token>'
  -d '{
  "configurationRecipe": {
    "scope": "MyRecipes",
    "code": "MyBasicPortfolioRecipe",
    "market": {
      "marketRules": [
        {
          "key": "Quote.LusidInstrumentId.MyIDForAPPLQuotes",
          "dataScope": "MyEquityQuotes",
          "supplier": "Lusid",
          "quoteType": "Price",
          "field": "mid"
        }
      ]
    }
  }
}'

Note the following:

  • The key must have a prefix of Quote.LusidInstrumentId. and suffix of the underlyingCode, in this case MyIDForAPPLQuotes.

  • The dataScope must match the quote scope in which prices are encapsulated in the LUSID Quote Store, in this case MyEquityQuotes.

  • The other fields must match their respective quote fields exactly (values are case-sensitive).

Registering the recipe with the parent portfolio

You must register the recipe with the portfolio holding the ExchangeTradedOption, for example:

curl -X POST 'https://mydomain.lusid.com/api/api/transactionportfolios/MyPortfolioScope/MyPortfolioCode/details'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <API-access-token>'
  -d '[
  {
    "value": {
      "scope": "MyRecipes",
      "code": "MyBasicPortfolioRecipe"
    },
    "path": "/instrumentEventConfiguration/recipeId",
    "op": "add"
  }
]'

Confirming positions

With market data in place, you can generate a holdings report to see the impact of the transaction on security and cash holdings, for example:

Auditing LUSID’s transaction type calculations

You can examine the output transaction generated by LUSID in reponse to the upserted input transaction, for example: