Modelling FxForwards in LUSID

Prev Next

The recommended way to model a foreign exchange or currency forward contract in LUSID is to create an instrument of type FxForward. See all supported instruments.

We recommend that the instrument definition stores the contract details (amounts and dates) and that you book a unitised transaction in a portfolio (that is, one unit of the instrument at zero cost).

You can model either:

  • A deliverable FxForward: The exchange of two quantities of two currencies at a specified date and time. See the bulk of this article.

  • A non-deliverable FxForward (NDF): The payment of a settlement amount based on the exchange rate between two currencies and amounts at a specified date and time. See the appendix.

Note: You can choose not to master an instrument and simply book a forward-settled cash transaction directly in a portfolio, but note LUSID's full suite of analytical capability is not available. See how to do this.

Mastering an instrument

There are numerous tools you can use to master a FxForward 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 FxForward. For more information on these fields, select FxForward from the definition dropdown in the UpsertInstruments API reference, or alternatively examine the FxForward schema:

For example, the following call masters an FxForward representing an obligation to pay/sell 1,000,000 GBP and buy/receive 1,200,000 USD on 1 June 2026 (that is, 6 months from the instrument start date, at an implied strike rate of 1.2):

curl -X POST 'https://<your-domain>.lusid.com/api/api/instruments'
   -H 'Authorization: Bearer <your-API-access-token>'
   -H 'Content-Type: application/json'
   -d '{"upsert-request-1": {
    "name": "GBPUSD 6M FxForward 20260601",
    "identifiers": {
      "ClientInternal": {
        "value": "FWD-GBPUSD-20260601",
        }
    },
    "definition": {
      "instrumentType": "FxForward",
      "startDate": "2026-01-01T00:00:00.0000000+00:00",
      "maturityDate": "2026-06-01T10:00:00.0000000+00:00",
      "domAmount": 1200000,
      "domCcy": "USD",
      "fgnAmount": -1000000,
      "fgnCcy": "GBP",
      "isNdf": false
    }
  }
}'

Note the following:

  • The startDate is 1 January 2026 and maturityDate is 01 June 2026, signifying a six month contract.

  • The domCcy must be the currency in which you want your holding in this instrument to be valued while the contract is in effect. It could be GBP, but in this example it is USD.

  • The domAmount is the amount of domCcy. It could be negative, but in this example it is positive since USD is being bought/received.

  • The fgnCcy is the other currency, in this case GBP. Note your holding will not be valued in this currency.

  • The fgnAmount is the amount of fgnCcy. It must have the opposite sign to domAmount, so in this example negative since domAmount is positive.

  • isNdf is false (the default) to signify this is a deliverable contract.

Providing the request is successful, the response:

  • Confirms the globally-unique LUID of the FxForward (in this case LUID_00003GZG).

  • 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": "default",
      "lusidInstrumentId": "LUID_00003GZG",
      "name": "GBPUSD 6M FxForward 20260601",
      "identifiers": {
        "ClientInternal": "FWD-GBPUSD-20260601",
        "LusidInstrumentId": "LUID_00003GZG"
      },
      "properties": [],
      "instrumentDefinition": {
        "startDate": "2026-01-01T00:00:00.0000000+00:00",
        "maturityDate": "2026-06-01T10:00:00.0000000+00:00",
        "domAmount": 1200000,
        "domCcy": "USD",
        "fgnAmount": -1000000,
        "fgnCcy": "GBP",
        "refSpotRate": 0,
        "isNdf": false,
        "fixingDate": "0001-01-01T00:00:00.0000000+00:00",
        "bookedAsSpot": false,
        "instrumentType": "FxForward"
      },
      "state": "Active",
      "assetClass": "FX",
      "domCcy": "USD",
      "relationships": [],
    }
  },
  ...
}

Booking a transaction to establish a position

Once the instrument is mastered, you can book a transaction to record a purchase of the contract in a particular portfolio, for example:

curl -X POST "https://<your-domain>.lusid.com/api/api/transactionportfolios/<scope>/<code>/transactions"
   -H "Authorization: Bearer <your-API-access-token>"
   -H "Content-Type: application/json"
   -d '[ {
        "transactionId": "fxfwd-gbp-usd-6m-01",
        "type": "StockIn",
        "instrumentIdentifiers": {
          "instrument/default/LusidInstrumentId": "LUID_00003GZG"
        },
        "transactionDate": "2021-11-15T00:00:00.0000000+00:00",
        "settlementDate": "2021-11-15T00:00:00.0000000+00:00",
        "units": 1,
        "transactionPrice": {
          "price": 0,
          "type": "Price"
        },
        "totalConsideration": {
          "amount": 0,
          "currency": "USD"
        },
        "transactionCurrency": "USD",
        "exchangeRate": 1
    }
]'

As mentioned above, an FxForward should be mastered with the currency amounts encapsulated in the instrument, and the transaction itself unitised (that is, with the units set to 1). Note also:

  • The totalConsideration.amount (cost) should be 0.

  • The transactionCurrency and the totalConsideration.currency (settlement currency) should be the domCcy of the instrument, in this example USD. LUSID relies on holding currency being domestic currency to calculate P&L correctly.

  • The transactionDate and the settlementDate should mark the start of the contract, in this example 15 November 2021 (the instrument defines the length).

  • This transaction uses the built-in StockIn transaction type rather than the built-in Buy, since there is no cash outlay. However, you could define your own custom transaction type to represent FxForward purchases if you wish.

Confirming positions

We can generate a holdings report on the transaction/settlement date of 1 January 2026 to see that we have one unit of the contract at zero cost:

Auditing LUSID's automatically-generated transactions

We can examine output transactions to understand how LUSID has arrived at this position:

Valuing your position

You can value your position against actual or estimated exchange rates at any time, and calculate the notional profit or loss (that is, the amount you have gained or lost by virtue of having entered into the fixed rate contract, relative to not having done so).

In the example above, we are obliged to buy 1,200,000 USD at a cost of 1,000,000 GBP in 6 months from the instrument start date, which means the implied strike rate is 1.2. If we value the contract after 3 months and the current spot rate has decreased to 1.1, then we would expect LUSID to show our position with a notional profit of 100,000 USD, since we have 'gained' this amount by fixing the forward rate. On the other hand, if the spot rate has increased to 1.3, then we would expect LUSID to show a notional loss of 100,000 USD.

The following pricing models are available for an FxForward:

Pricing model

Notes

ConstantTimeValueOfMoney

This is the default. See how to specify a different model.

SimpleStatic

ForwardFromCurve

ForwardFromCurveUndiscounted

ForwardWithPoints

ForwardWithPointsUndiscounted

ForwardSpecifiedRate

ForwardSpecifiedRateUndiscounted

There is currently an issue with these models. They are available for use but require a market data workaround; see the examples in this Jupyter Notebook, or contact Technical Support for details. In the meantime, the ForwardFromCurve and ForwardFromCurveUndiscounted models are recommended for most use cases.

Discounting

This is not really a model, but rather provides a set of options that enables you to emulate other models. Despite its name, you can emulate undiscounted models. Contact Technical Support for more information.

The recommended models have different market data requirements and capabilities. Note that, by default:

  • LUSID values the entire instrument. You can change this for most models to value the domestic and foreign legs separately by setting the produceSeparateResultForLinearOtcLegs=true recipe option.

  • LUSID values the instrument in the currency of the domestic leg. You can change this for most models to value in a report currency, which could be the portfolio currency (if different) or even a completely separate currency. More information.

  • LUSID does not infer missing FX rates. You can change this to infer (for example) USDGBP as 1/GBPUSD by setting the attemptToInferMissingFx=true recipe option. For more information on FX inference and triangulation, see this Jupyter Notebook.

The market data required and the valuation results themselves may differ depending on the model chosen, and any changes you make to the defaults above. For more information, including worked examples of every model and option, see this Jupyter Notebook:

Pricing model

Discounting performed?

Available valuations

Market data required for valuation date

Market data store

Notes

ConstantTimeValueOfMoney

No

Whole instrument

1 FX spot rate: DOMFGN

Quote Store

PV is the sum of the two legs with FGN converted to DOM at the spot rate.

Separate legs

2 FX spot rates: DOMFGN and FGNDOM

SimpleStatic

No

Whole instrument

Quote for the instrument that is a value per unit of the domestic currency.

Quote Store

PV is value per unit of domestic currency multiplied by the absolute value of the domestic amount.

ForwardFromCurve

Yes

Whole instrument

1 forward rate curve, 1 discounting curve

Complex Market Data Store

For more information on curve interpolation and extrapolation, see section 5 of this Jupyter Notebook.

1 FX spot rate: DOMFGN

Quote Store

The FX spot rate is a required dependency but does not impact the valuation.

Separate legs

1 forward rate curve, 1 discounting curve

Complex Market Data Store

For more information on curve interpolation and extrapolation, see section 5 of this Jupyter Notebook.

2 FX spot rates: DOMFGN and FGNDOM

Quote Store

The FX spot rates are used to convert FGN to DOM.

ForwardFromCurveUndiscounted

No

Whole instrument

1 forward rate curve

Complex Market Data Store

For more information on curve interpolation and extrapolation, see section 5 of this Jupyter Notebook.

1 FX spot rate: DOMFGN

Quote Store

The FX spot rate is a required dependency but does not impact the valuation.

Separate legs

1 forward rate curve

Complex Market Data Store

For more information on curve interpolation and extrapolation, see section 5 of this Jupyter Notebook.

2 FX spot rates: DOMFGN and FGNDOM

Quote Store

The FX spot rates are used to convert FGN to DOM.

Valuing in report currency

By default, the whole FxForward is valued in the domestic currency, which you can report using the Valuation/PV metric. If the domestic and portfolio currencies are different, you can also specify the Valuation/PvInPortfolioCcy metric to convert to the portfolio currency using forward and spot rates (depending on the model).

You can provide a more sophisticated reporting configuration but note that the results are not interchangeable. For example, to report in a completely separate currency that is neither the domestic leg nor the portfolio currency you can set the convertToReportCcy=true model option and then specify the reportCurrency parameter in your call to the GetValuation API.

For more information on the implications of this, see section 3 of this Jupyter Notebook.

Assessing risk

LUSID provides an exposure calculation and supports both analytic and bump and valuation mechanisms for assessing risk.

Monitoring the lifecycle of the instrument

The FxForward instrument type is tightly integrated into LUSID's instrument event framework. To enable this for your domain, you must:

  1. Register a recipe with every portfolio holding a FxForward.

  2. Create transaction types to determine the economic impact of the transactions automatically generated by FxForward events.

For much more information, see handling instrument events for FxForwards.

Instrument event type

Event emission criteria

If emitted, effect of LUSID default transaction template

FxForwardSettlementEvent

This event is automatically emitted by LUSID on the maturity date (or fixing date for a NDF).

For a deliverable forward, two transactions are generated, one for each currency with opposite signs. For a NDF, a single transaction is generated representing the net difference.

MaturityEvent

This event is automatically emitted by LUSID on the maturity date to reduce the holding to zero.

A single transaction for the total number of holding units at zero cost is generated.

EarlyCloseOutEvent

This event is not automatically emitted by LUSID. You can manually load it to close early.

A single transaction is generated.

Appendix: Modelling a NDF

Rather than exchanging notionals at maturity, LUSID cash-settles a NDF as the difference between the nominal strike rate and a fixing rate which you must provide as market data.

The sections below describe how NDF modelling differs from that of a deliverable FxForward. Read the main body of this article first for the full setup process.

Mastering an instrument

You can master an instrument using the same basic definition as a deliverable FxForward, but note the following key differences:

  • Set isNdf to true.

  • Set fixingDate to the date on which the exchange rate is fixed for cash settlement. This is typically a few business days before maturityDate.

  • Optionally, set settlementCurrency to the currency in which the net cash difference is paid. This defaults to domCcy if not specified.

For example:

"definition": {
  "instrumentType": "FxForward",
  "startDate": "2026-01-01T00:00:00.0000000+00:00",
  "maturityDate": "2026-06-01T10:00:00.0000000+00:00",
  "domAmount": 1200000,
  "domCcy": "USD",
  "fgnAmount": -1000000,
  "fgnCcy": "GBP",
  "isNdf": true,
  "fixingDate": "2026-05-28T00:00:00.0000000+00:00",
  "settlementCurrency": "USD"
}

Booking a transaction to establish a position

You can book a transaction in the same way as a deliverable FxForward: one unit at zero cost. Both transactionCurrency and totalConsideration.currency (settlement currency) should be set to domCcy.

Valuing your position

Before fixingDate, you can value your position in the same way as a deliverable FxForward. Note a NDF is held and valued in domCcy as just a single leg, so using the default ConstantTimeValueOfMoney pricing model the only spot rate required is DOMFGN, which in our example is USD/GBP.

After fixingDate, and providing LUSID can locate a fixing rate loaded into the Quote Store (see below), an NDF instrument holding is valued at zero. This is because the value is carried by the unsettled cash holding for the settlement amount.

Monitoring the lifecycle of the instrument

The same instrument events apply to a NDF but note that LUSID automatically emits FxForwardSettlementEvent:

  1. On fixingDate rather than maturityDate, but only if

  2. A fixing rate for fixingDate can be located in the Quote Store. If not, the event silently fails with a market data error; no settlement transaction is generated and MaturityEvent is emitted on maturityDate instead to reduce the holding to zero.

Providing a fixing rate

A key difference is the direction of the FX rate required. For a NDF, LUSID requires the fixing rate expressed in the opposite direction: FGNDOM, which in our example is GBP/USD.

By default, LUSID does not infer or triangulate this rate. You can set the AttemptToInferMissingFxOnFixings recipe option (not to be confused with attemptToInferMissingFx), but providing explicit rates is always preferable.

If settlementCurrency is set to a currency other than domCcy in the instrument definition, LUSID additionally requires an explicit DOMSETTLE rate on fixingDate to convert the settlement amount to the output currency.