You can model an exchange-traded futures contract as an instrument of type Future in LUSID. See how to do this.
LUSID emits the following instrument lifecycle events for a Future providing suitable market data can be located, which you can optionally handle to reduce workload and improve efficiency:
FutureMarkToMarketEventFutureExpiryEvent
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,
"markToMarketConventions": {
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
contractsfield should be set to1.The
markToMarketConventionsobject must be specified if you want LUSID to emit a dailyFutureMarkToMarketEvent. If you do not, and only want LUSID to emit a singleFutureExpiryEventon thematurityDate, omit this object.The
markToMarketConventions.calendarCodefield should be set to the code of the most appropriate calendar for the exchange. If you omit this field, LUSID emits aFutureMarkToMarketEventeach weekday irrespective of whether it is a holiday in that jurisdiction. Note the scope of the calendar is determined by thecalendarScopefield in a portfolio recipe, which isCoppClarkHolidayCalendarsby default.The time component of the
maturityDateshould be set to the time that settlement prices are released by the exchange, for example 5:30pm. LUSID emits aFutureMarkToMarketEventeach day at this time, beginning on the transaction date and ending on the day before thematurityDate.The
deliveryTypefield should be set toCash. LUSID does not emit instrument events for physical settlement.The
tradingConventions.priceScaleFactorfield can be set to1for most futures (this is the default), but may be set to100for 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
transactionDateandsettlementDateare 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
FutureMarkToMarketEventat 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 theFutureinstrument to zero and realise daily gain or loss.A
FutureExpiryEventon 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
FutureMarkToMarketEventemitted by instruments of typeFuture.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 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
FutureMarkToMarkettransaction type grouped in thedefaulttransaction typesource.The quantity held (
units) is assessed dynamically per-weekday and per-portfolio.The
transactionCurrencyandtotalConsideration.currency(settlement currency) are both set to thedomCcyof the instrument economic definition, so theexchangeRatebetween them is1.The
totalConsideration.amountis deliberately not set. Instead, LUSID automatically calculates gross consideration and stores the result in theTransaction/default/GrossConsiderationsystem property. It is your responsibility to set total consideration equal to gross consideration (see below).
Note: This template does not specify the
Transaction/default/TradeToPortfolioRatesystem property to maintain the cost basis of the portfolio. If the transaction and portfolio currencies are different, we recommend specifying theTxn:TradeToPortfolioRatecalculation in theFutureMarkToMarkettransaction 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
VariationMarginmovement to adjust the notional cost up or down without impacting the units of the holding. Note thesidereferences aMTMCustomSide(see below).The second is a
CashReceivablemovement that realises daily gain or loss. Note thedirectionis positive but values generated by the event are signed negative if they are losses.
The
DeriveTotalConsiderationtransaction 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
FutureExpiryEventemitted by instruments of typeFuture.The generated output transaction belongs to a
FutureCashSettlementtransaction type grouped in thedefaulttransaction typesource.The
totalConsideration.amountis 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
StockMovementmovement to set the units of the holding to zero. Note thesidereferences aNotionalCustomSide(see below).The second is a
CashReceivablemovement that realises gain or loss for the contract as a whole. Note thedirectionis positive but the value generated by the event is signed negative if it is a loss.
The
Txn:GrossConsiderationtransaction type calculation calculates gross consideration asnotionalAmount - (notionalCost + variationMargin).The
DeriveTotalConsiderationcalculation 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
amountfield is set toTxn:TotalConsiderationinstead ofTxn:TradeAmount, to respect the negative sign of losses.The
notionalAmountfield is set to theTransaction/default/NotionalAmountsystem 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
FutureMarkToMarketEventis 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
FutureExpiryEventis 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
dataScopefield set toMyFutureMarketPricesScope, to locate the correct quote scope.The
quoteIntervalfield set to0D.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 Portfolio Management > 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:
.png?sv=2022-11-02&spr=https&st=2025-11-02T03%3A39%3A28Z&se=2025-11-02T04%3A02%3A28Z&sr=c&sp=r&sig=GL6rYUmZEMZmdeGOFO5AF30DR95l6d1HO6qHP18prUI%3D)
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):
.png?sv=2022-11-02&spr=https&st=2025-11-02T03%3A39%3A28Z&se=2025-11-02T04%3A02%3A28Z&sr=c&sp=r&sig=GL6rYUmZEMZmdeGOFO5AF30DR95l6d1HO6qHP18prUI%3D)
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):
.png?sv=2022-11-02&spr=https&st=2025-11-02T03%3A39%3A28Z&se=2025-11-02T04%3A02%3A28Z&sr=c&sp=r&sig=GL6rYUmZEMZmdeGOFO5AF30DR95l6d1HO6qHP18prUI%3D)
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:
.png?sv=2022-11-02&spr=https&st=2025-11-02T03%3A39%3A28Z&se=2025-11-02T04%3A02%3A28Z&sr=c&sp=r&sig=GL6rYUmZEMZmdeGOFO5AF30DR95l6d1HO6qHP18prUI%3D)
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:
.png?sv=2022-11-02&spr=https&st=2025-11-02T03%3A39%3A28Z&se=2025-11-02T04%3A02%3A28Z&sr=c&sp=r&sig=GL6rYUmZEMZmdeGOFO5AF30DR95l6d1HO6qHP18prUI%3D)
Auditing output transactions generated by instrument events
We can use Portfolio Management > Transactions in Output mode to call the BuildTransactions API with a suitable window to examine the output transactions generated by these instrument events:
.png?sv=2022-11-02&spr=https&st=2025-11-02T03%3A39%3A28Z&se=2025-11-02T04%3A02%3A28Z&sr=c&sp=r&sig=GL6rYUmZEMZmdeGOFO5AF30DR95l6d1HO6qHP18prUI%3D)
To confirm the total realised gain/loss, click the +/- button at the end of the FutureCashSettlement row (highlighted in yellow below):
.png?sv=2022-11-02&spr=https&st=2025-11-02T03%3A39%3A28Z&se=2025-11-02T04%3A02%3A28Z&sr=c&sp=r&sig=GL6rYUmZEMZmdeGOFO5AF30DR95l6d1HO6qHP18prUI%3D)