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 to1
.The
markToMarketEvent
object must be specified if you want LUSID to emit a dailyFutureMarkToMarketEvent
. If you do not, and only want LUSID to emit a singleFutureExpiryEvent
on thematurityDate
, 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 aFutureMarkToMarketEvent
each weekday irrespective of whether it is a holiday in that jurisdiction. Note the scope of the calendar is determined by thecalendarScope
field in a portfolio recipe, which isCoppClarkHolidayCalendars
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 aFutureMarkToMarketEvent
each day at this time, beginning on the transaction date and ending on the day before thematurityDate
.The
deliveryType
field should be set toCash
. LUSID does not emit instrument events for physical settlement.The
tradingConventions.priceScaleFactor
field can be set to1
for most futures (this is the default), but may be set to100
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
andsettlementDate
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 theFuture
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 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
FutureMarkToMarket
transaction type grouped in thedefault
transaction typesource
.The quantity held (
units
) is assessed dynamically per-weekday and per-portfolio.The
transactionCurrency
andtotalConsideration.currency
(settlement currency) are both set to thedomCcy
of the instrument economic definition, so theexchangeRate
between them is1
.The
totalConsideration.amount
is deliberately not set. Instead, LUSID automatically calculates gross consideration and stores the result in theTransaction/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 theTxn:TradeToPortfolioRate
calculation in theFutureMarkToMarket
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 theside
references aMTMCustomSide
(see below).The second is a
CashReceivable
movement that realises daily gain or loss. Note thedirection
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 typeFuture
.The generated output transaction belongs to a
FutureCashSettlement
transaction type grouped in thedefault
transaction typesource
.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 theside
references aNotionalCustomSide
(see below).The second is a
CashReceivable
movement that realises gain or loss for the contract as a whole. Note thedirection
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 asnotionalAmount - (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 toTxn:TotalConsideration
instead ofTxn:TradeAmount
, to respect the negative sign of losses.The
notionalAmount
field is set to theTransaction/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 toMyFutureMarketPricesScope
, to locate the correct quote scope.The
quoteInterval
field 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 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):