Handling mark to market and expiry lifecycle events for Future instruments

You can model exchange-traded futures contracts as instruments of type Future in LUSID.

LUSID emits the following instrument lifecycle events for a Future, which you can optionally handle to reduce workload and improve efficiency:

  • FutureMarkToMarketEvent

  • FutureExpiryEvent

Mastering an instrument and establishing a position

Imagine we have a E-mini S&P futures contract trading on the Globex exchange that matures on Friday 21 March 2025. We can model this in LUSID as an instrument of type Future with the following economic definition:

"definition": {
  "instrumentType": "Future",
  "startDate": "2024-09-21T10:00:00.0000000+00:00",
  "maturityDate": "2025-03-21T17:30:00.0000000+00:00",
  "identifiers": {},
  "contractDetails": {
    "domCcy": "USD",
    "contractCode": "ESHS",
    "contractMonth": "H",
    "contractSize": 50,
    "exchangeCode": "GLOBEX",
    "deliveryType": "Cash"
  },
  "contracts": 1,
  "markToMarketEvent": {
    calendarCode: "USD"
  }
  "tradingConventions": {
    "priceScaleFactor": 1
  }
} 

For much more information on modelling instruments of type Future, see this article. Note with regard to instrument events:

  • The contracts field should be set to 1.

  • The markToMarketEvent object must be specified if you want LUSID to emit a daily FutureMarkToMarketEvent. If you do not, and only want LUSID to emit a single FutureExpiryEvent on the maturityDate, omit this object.

  • The markToMarketEvent.calendarCode field should be set to the code of the most appropriate calendar for the exchange. If you omit this field, LUSID emits a FutureMarkToMarketEvent each weekday irrespective of whether it is a holiday in that jurisdiction. Note the scope of the calendar is determined by the calendarScope field in a portfolio recipe, which is CoppClarkHolidayCalendars by default.

  • The time component of the maturityDate should be set to the time that settlement prices are released by the exchange, for example 5:30pm. LUSID emits a FutureMarkToMarketEvent each day at this time, beginning on the transaction date and ending on the day before the maturityDate.

  • The deliveryType field should be set to Cash. LUSID does not emit instrument events for physical settlement.

  • The tradingConventions.priceScaleFactor field can be set to 1 for most futures (this is the default), but may be set to 100 for bond futures (for example).

We can now establish a position by booking a transaction in this instrument into a suitable portfolio, for example:

{
  "transactionId": "my_future_purchase_001",
  "type": "BuyFuture",
  "instrumentIdentifiers": {"Instrument/default/LusidInstrumentId": "LUID_00003E7N"},
  "transactionDate": "2024-09-21T09:00:00.0000000+00:00",
  "settlementDate": "2024-09-21T09:00:00.0000000+00:00",
  "units": 10,
  "transactionPrice": {
    "price": 6000,
    "type": "Price"
  },
  "totalConsideration": {
    "amount": 0,
    "currency": "USD"
  },
  "properties": {
    "Transaction/MyProperties/TradeCommission": {
      "key": "Transaction/MyProperties/TradeCommission",
      "value": {
        "metricValue": {
          "value": 50.00,
          "unit": "USD"
        }
      }
    }
  }
}

For much more information on booking transactions in Future instruments, including details of the recommended BuyFuture transaction type to automatically calculate notional amount, gross consideration and total consideration, see this article. Note with regard to instrument events:

  • The transactionDate and settlementDate are both Wednesday 25 September 2024 at 9am. LUSID begins emitting events the same day at 5:30pm.

  • LUSID calculates notional as (units * price * contracts * contract size) / scale factor, so in this case (10 * 6000 * 1 * 50) / 1 = 3,000,000.

  • The fee representing the cost of entering into the contract is recorded using a custom property. This represents both the gross and total consideration for this transaction.

Understanding lifecycle events emitted by LUSID

For our holding in this instrument in this portfolio, and providing suitable market data can be located in the Quote Store, LUSID automatically emits:

  • A FutureMarkToMarketEvent at the time specified by the maturity date each weekday, beginning on the transaction date (Wednesday 25 September 2024 5:30pm in our example) and continuing until the day before the maturity date (Thursday 20 March 2025 5:30pm). You should handle this event to set the PV of the Future instrument to zero and realise daily gain or loss.

  • A FutureExpiryEvent on the maturity date (Friday 21 March 2025 5:30pm). You should handle this event to cash settle the contract and set your position to zero, so it drops out of holding and valuation reports.

LUSID provides a default transaction template for each type of event. In this tutorial we'll choose to use the default transaction templates for both types, but you can create your own custom transaction templates to configure the process if you wish.

Examining the default transaction template for FutureMarkToMarketEvent

We can call the GetTransactionTemplate API to examine the default template for FutureMarkToMarketEvent, which at the time of writing is as follows:

{
  "instrumentType": "Future",
  "instrumentEventType": "FutureMarkToMarketEvent",
  "description": "LUSID default template for automatically generated transactions in respect of Future Mark To Market events.",
  "scope": "default",
  "componentTransactions": [
    {
      "displayName": "Mark to market",
      "transactionFieldMap": {
        "transactionId": "{{instrumentEventId}}-{{holdingId}}",
        "type": "FutureMarkToMarket",
        "source": "default",
        "instrument": "{{instrument}}",
        "transactionDate": "{{FutureMarkToMarketEvent.effectiveDate}}",
        "settlementDate": "{{FutureMarkToMarketEvent.effectiveDate}}",
        "units": "{{eligibleBalance}}",
        "transactionCurrency": "{{FutureMarkToMarketEvent.settlementCurrency}}",
        "exchangeRate": "1",
        "totalConsideration": {
          "currency": "{{FutureMarkToMarketEvent.settlementCurrency}}"
        }
      },
      "transactionPropertyMap": [
        {
          "propertyKey": "Transaction/default/GrossConsideration",
          "value": "{{FutureMarkToMarketEvent.markToMarketAmount}}"
        }
      ]
    }
  ],
  ...
}

Note the following:

  • This template handles instrument events of type FutureMarkToMarketEvent emitted by instruments of type Future.

  • 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 underlying instrument. Note it is possible for a template to generate multiple output transactions.

  • The generated transaction has no condition, so it is always produced when LUSID emits this event.

  • The generated transaction belongs to a FutureMarkToMarket transaction type grouped in the default transaction type source.

  • The quantity held (units) is assessed dynamically per-weekday and per-portfolio.

  • The transactionCurrency and totalConsideration.currency (settlement currency) are both set to the domCcy of the instrument economic definition, so the exchangeRate between them is 1.

  • The totalConsideration.amount is deliberately not set. Instead, LUSID automatically calculates gross consideration and stores the result in the Transaction/default/GrossConsideration system property. It is your responsibility to set total consideration equal to gross consideration (see below).

Note: This template does not specify the Transaction/default/TradeToPortfolioRate system property to maintain the cost basis of the portfolio. If the transaction and portfolio currencies are different, we recommend specifying the Txn:TradeToPortfolioRate calculation in the FutureMarkToMarket transaction type to look up a spot rate dynamically in the LUSID Quote Store. See how to do this.

Creating a suitable FutureMarkToMarket transaction type

The default transaction template mandates a FutureMarkToMarket 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.

We can call the SetTransactionType API as follows:

curl -X PUT 'https://<your-domain>.lusid.com/api/api/transactionconfiguration/types/default/FutureMarkToMarket?scope=default'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <your-API-access-token>'
  -d '{
  "aliases": [
    {
      "type": "FutureMarkToMarket",
      "description": "Transaction type for transactions automatically generated by FutureMarkToMarketEvent",
      "transactionClass": "Basic",
      "transactionRoles": "AllRoles",
      "isDefault": false
    }
  ],
  "movements": [
    {
      "name": "Adjust notional cost up or down"
      "movementTypes": "VariationMargin",
      "side": "MTMCustomSide",
      "direction": 1
    },
    {
      "name": "Realise daily gain or loss"
      "movementTypes": "CashReceivable",
      "side": "Side2",
      "direction": 1,
    }
  ],
  "calculations": [
    {
      "type": "DeriveTotalConsideration",
      "formula": "Txn:GrossConsideration"
    }
  ]
}'

Note the following:

  • The transaction type alias is FutureMarkToMarket, 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 VariationMargin movement to adjust the notional cost up or down without impacting the units of the holding. Note the side references a MTMCustomSide (see below).

    • The second is a CashReceivable movement that realises daily gain or loss. Note the direction is positive but values generated by the event are signed negative if they are losses.

  • The DeriveTotalConsideration transaction type calculation sets total consideration equal to gross consideration. This is important as the event itself does not emit a total consideration.

We might call the SetSideDefinition API to create a MTMCustomSide as follows:

curl -X PUT 'https://<your-domain>.lusid.com/api/api/transactionconfiguration/sides/MTMCustomSide?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:TotalConsideration"
}'

Note this is the same as the built-in Side1 except the amount field is set to Txn:TotalConsideration instead of Txn:TradeAmount, to respect the negative sign of losses.

Examining the default transaction template for FutureExpiryEvent

In the same way, we can call the GetTransactionTemplate API to examine the default transaction template for FutureExpiryEvent, which at the time of writing is as follows:

{
  "instrumentType": "Future",
  "instrumentEventType": "FutureExpiryEvent",
  "description": "LUSID default template for automatically generated transactions in respect of Future Expiry events.",
  "scope": "default",
  "componentTransactions": [
    {
      "displayName": "Cash settlement transaction",
      "transactionFieldMap": {
        "transactionId": "{{instrumentEventId}}-{{holdingId}}",
        "type": "FutureCashSettlement",
        "source": "default",
        "instrument": "{{instrument}}",
        "transactionDate": "{{FutureExpiryEvent.expiryDate}}",
        "settlementDate": "{{FutureExpiryEvent.expiryDate}}",
        "units": "{{eligibleBalance}}",
        "transactionCurrency": "{{FutureExpiryEvent.settlementCurrency}}",
        "exchangeRate": "1",
        "totalConsideration": {
          "currency": "{{FutureExpiryEvent.settlementCurrency}}"
        }
      },
      "transactionPropertyMap": [
        {
          "propertyKey": "Transaction/default/NotionalAmount",
          "value": "{{FutureExpiryEvent.notionalAmount}}"
        }
      ]
    }
  ],
  ...
}

Note the following:

  • This template handles instrument events of type FutureExpiryEvent emitted by instruments of type Future.

  • The generated output transaction belongs to a FutureCashSettlement transaction type grouped in the default transaction type source.

  • The totalConsideration.amount is deliberately not set. It is your responsibility to ensure LUSID calculates gross consideration and total consideration (see below).

Creating a suitable FutureCashSettlement transaction type

The default transaction template mandates a FutureCashSettlement 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/FutureCashSettlement?scope=default'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <your-API-access-token>'
  -d '{
  "aliases": [
    {
      "type": "FutureCashSettlement",
      "description": "Transaction type for transactions automatically generated by FutureExpiryEvent",
      "transactionClass": "Basic",
      "transactionRoles": "AllRoles",
      "isDefault": false
    }
  ],
  "movements": [
    {
      "name": "Set units to 0"
      "movementTypes": "StockMovement",
      "side": "NotionalCustomSide",
      "direction": -1
    },
    {
      "name": "Realise contract gain or loss"
      "movementTypes": "CashReceivable",
      "side": "Side2",
      "direction": 1,
    }
  ],
  "calculations": [
    {
      "type": "Txn:GrossConsideration"
    },
    {
      "type": "DeriveTotalConsideration",
      "formula": "Txn:GrossConsideration"
    }
  ]
}'

Note the following:

  • The transaction type alias is FutureCashSettlement, 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 movement to set the units of the holding to zero. Note the side references a NotionalCustomSide (see below).

    • The second is a CashReceivable movement that realises gain or loss for the contract as a whole. Note the direction is positive but the value generated by the event is signed negative if it is a loss.

  • The Txn:GrossConsideration transaction type calculation calculates gross consideration as notionalAmount - (notionalCost + variationMargin).

  • The DeriveTotalConsideration calculation sets total consideration equal to gross consideration. This is important as the event itself does not emit a total consideration.

We can call the SetSideDefinition API to create a NotionalCustomSide as follows:

curl -X PUT 'https://<your-domain>.lusid.com/api/api/transactionconfiguration/sides/NotionalCustomSide?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:TotalConsideration",
  "notionalAmount": "Transaction/default/NotionalAmount"
}'

Note this is the same as the built-in Side1 except:

  • The amount field is set to Txn:TotalConsideration instead of Txn:TradeAmount, to respect the negative sign of losses.

  • The notionalAmount field is set to the Transaction/default/NotionalAmount system property, to handle LUSID’s calculation of notional amount.

Loading suitable market data into the LUSID Quote Store

In order that LUSID may calculate gross consideration and notional amounts for instrument lifecycle events, we must load the following market data into LUSID:

  • A settlement market price for the instrument every weekday (not Saturday or Sunday). If LUSID cannot locate a price for a particular weekday, a FutureMarkToMarketEvent is not generated that day.

  • A final market price for the instrument on the maturity date. If LUSID cannot locate a market price for that date, a FutureExpiryEvent is not generated.

Consider the following example dates:

Date

Time

Price

Wednesday 25 September 2024

17:29:00

6002

Thursday 26 September 2024

6004

Friday 27 September 2024

5998

Monday 30 September 2024

6001

Tuesday 1 October 2024

6003

Friday 21 March 2025

6005

We can call the UpsertQuotes API to load these market prices into the Quote Store. In the following example, a market price for Wednesday 25 September 2024 at 5:29pm is encapsulated in a MyFutureMarketPricesScope (specified in the URL), and the underlying instrument is identified by LUID:

curl -X POST 'https://<your-domain>.lusid.com/api/api/quotes/MyFutureMarketPricesScope'
  -H 'Authorization: Bearer <your-API-access-token>'
  -H 'Content-Type: application/json-patch+json'
  -d '{
  "Quote-0001": {
    "quoteId": {
      "quoteSeriesId": {
        "provider": "Lusid",
        "instrumentIdType": "LusidInstrumentId",
        "instrumentId": "LUID_00003E7N",
        "quoteType": "Price",
        "field": "mid"
      },
      "effectiveAt": "2024-09-25T17:29:00Z"
    },
    "metricValue": {
      "value": 6002, "unit": "USD"
    }
  },

Note that in order to locate these market prices, the portfolio recipe must have a market rule with:

  • The dataScope field set to MyFutureMarketPricesScope, to locate the correct quote scope.

  • The quoteInterval field set to 0D.0D, to prevent look up of stale quotes:

"market": {
  "marketRules": [
    {
      "key": "Quote.LusidInstrumentId",
      "supplier": "Lusid",
      "dataScope": "MyFutureMarketPricesScope",
      "quoteType": "Price",
      "field": "mid",
      "quoteInterval": "0D.0D"
    },
    ...
  ]
}

Examining the impact of instrument lifecycle events on the portfolio

Wednesday 25 September 2024 12pm

We can use Dashboard > Valuations in the LUSID web app to call the GetValuation API on the transaction date before the first FutureMarkToMarketEvent and see that we have:

  • 10 units of the instrument holding with a notional exposure of 3,000,000.

  • A cash holding of -$50 reflecting the cost of entering into the contract:

Wednesday 25 September 2024 5:30pm

If we move the Effective time to 5:30pm we can see that the first FutureMarkToMarketEvent has increased the instrument exposure to 3,001,000 and the cash holding to $950, reflecting the difference between the daily settlement price (6002) and the purchase price (6000):

Thursday 26 September 2024 5:30pm

If we move the Effective date to Thursday we can see that the second FutureMarkToMarketEvent has increased the instrument exposure to 3,002,000 and the cash holding to $1,950, reflecting the difference between the daily settlement price (6004) and the purchase price (6000):

Friday 27 September 2024 5:30pm

If we move the Effective date to Friday we can see that the third FutureMarkToMarketEvent has decreased the instrument exposure to 2,999,000 and the cash holding to -$1,050, reflecting the difference between the daily settlement price (5998) and the purchase price (6000), and so on:

Friday 21 March 2025 5:30pm

If we move the Effective date to the maturity date we can see that the FutureExpiryEvent has removed the instrument holding and set the cash holding to $2,450, reflecting the difference between the final price (6005) and the purchase price (6000). This is the total realised gain:

Auditing output transactions generated by instrument events

We can use Dashboard > Transactions in Output mode to call the BuildTransactions API with a suitable window to examine the output transactions generated by these instrument events:

To confirm the total realised gain/loss, click the +/- button at the end of the FutureCashSettlement row (highlighted in yellow below):