Views:

A recipe is a hierarchical, modular document containing rules whose primary purpose is to govern the process of performing a valuation operation. See how to create or generate a recipe.

Note: Recipes can also be used to organise data and provide methodologies governing other analytical operations, such as reconciliation and cash reporting.

A recipe is responsible for:

  • Locating market data
    This can be equity or bond prices or FX rates held in the LUSID Quote Store, or more complex market data for valuing derivatives such as interest rate curves or volatility surfaces held in the LUSID Complex Market Data Store.
  • Changing the default pricing models for instrument types
    Each instrument type (equity, bond, future and so on) has a default pricing model provided by LUSID that you can either override or swap for a pricing model from a 3rd party vendor.
  • Providing a set of default options
    You can override the defaults to configure various aspects of a recipe's behavior, including aggregation (the process of deciding which metrics to report).
  • Optionally, incorporating valuation results from other systems
    Partial or complete results can be held in the LUSID Structured Results Store and incorporated into or replace certain valuation results calculated by LUSID.
     

Locating market data

A recipe must be able to locate market data suitable for each type of instrument you wish to value:

  • For simple exchange-traded instruments such as equities, you are likely to require market prices held in the LUSID Quote Store.
  • For complex exchange-traded or OTC instruments, you are likely to require interest rate, discounting or credit curves and/or volatility surfaces held in the LUSID Complex Market Data Store.
  • For any instrument (including a cash instrument) held in a different currency to the portfolio currency, you are likely to require FX rates held in the LUSID Quote Store.

Note: You can call APIs to check the quality/quantity of market data required for the valuation operation you are trying to perform. See how to do this.

By default, a recipe must be able to locate market data that is valid ~ one day prior to each date in the valuation schedule. The precise validity period is determined by the validation time. For example, if you are valuing a portfolio containing a single equity on:

  • 7 March 2022, the time defaults to 00:00:00 and the recipe must be able to locate at least one market price for that equity (and an FX rate, if applicable) effective 6 March 00:00:00 -> 7 March 00:00:00. Prices effective 5 March or earlier, or 7 March later than midnight are not valid.
  • 7 March 2022 17:00:00, the recipe must be able to locate at least one market price effective 6 March 00:00:00 -> 7 March 17:00:00.
  • 7 March 2022 23:59:59, the recipe must be able to locate at least one market price effective 6 March 00:00:00 -> 7 March 23:59:59.

In other words, there is an automatic look back, or ‘grace’, period of between 24 and 48 hours. You can specify an explicit quoteInterval to look back further than this if you wish (see below).

For each category of market data (that is, market price, FX rate, interest rate curve and so on) you are required to locate you must specify a market data rule in the marketRules array of the market section of your recipe. Consider the following example, of three market data rules intended to value a portfolio on 7 March 2022:

Note the following:

  • Each market data rule is self-contained and must contain all the information required to locate a particular category of market data in an appropriate store.
  • Market data rules are processed in order. The first matching rule found for a category of market data is used (as determined by its key).
  • You can provide multiple rules for the same key in order to provide fallback logic. For example, you could have two rules for Quote.Figi.* that are identical except the first has a supplier of DataScope and the second a supplier of Bloomberg. Prices from Refinitiv would be preferred, but the recipe would fall back to Bloomberg if no Refinitiv prices could be found.

For information on all the fields and allowed values in a market data rule, examine the API documentation (expand the configurationRecipe/market/marketRules section).

The following table summarises core fields; note in particular that:

  • The key field defines the category of market data to locate.
  • Given the category, all the other fields define the rule's criteria for retrieving appropriate data.
Market data rule fieldStatusApplicable LUSID market data store(s)ExplanationMatching market data field in store
keyMandatoryQuote, Complex Market DataA dot-separated string specifying the category of market data to locate, and implicitly the store in which to locate it. See this table for more information.n/a
supplierMandatoryQuote, Complex Market DataThe market data vendor. This value must match the value of the market data provider field in the appropriate store. See a list of valid vendors.provider
priceSourceOptionalQuote, Complex Market DataThe sub-supplier to the market data vendor (above). This value must match the value of the market data priceSource field if it is specified, for example  Tradeweb or RRPS for Refinitiv DataScope.priceSource
dataScopeMandatoryQuote, Complex Market DataThis value must match the scope encapsulating the market data in the appropriate store.scope
quoteTypeMandatoryQuote

If the rule locates market data in the Quote Store, this value must match the value of the quoteType field, for example Price or Rate.
 

If the rule locates market data in the Complex Market Data Store, omit this field.

quoteType
fieldMandatoryQuote

If the rule locates market data in the Quote Store, this value must match the value of the field field, for example bid, mid or ask. Note this value is case-sensitive, so Bid does not match bid. See valid values for different vendors.
 

If the rule locates market data in the Complex Market Data Store, omit this field.

field
quoteIntervalOptionalQuote, Complex Market Data

Specifies a 'look back' period for market data longer than the implicit default of 1D.0D, which looks back to 00:00:00 on the day before the valuation date. If no market data is found within this range then a market data resolver error occurs.
 

You can use the <startdate>.<enddate> tenor syntax to look back further, for example 1W.0D to look back seven days from the valuation date, or 0D.0D to look back to the beginning of the valuation date itself. If you want to consider the latest data invalid instead you can change the end date, for example 2D.1D to look back two days before the valuation date, but not to include the valuation date itself. For more information, see the API documentation.

 

Note if you choose BD and your valuation schedule specifies a holiday calendar, LUSID ignores non-business days and looks back further.

n/a
sourceSystemOptionalQuote, Complex Market DataDefaults to Lusid to locate market data in either the Quote Store or Complex Market Data Store. Do not change this value unless you want to locate data in a 3rd party store.n/a

The following table explains the key field in more detail:

Market data categoryLUSID market data storekey field syntaxExamples
Market priceQuote StoreQuote.<IdentifierType>.<IdentifierValue>
  • Quote.RIC.* to locate prices loaded with an instrumentIdType of RIC.
  • Quote.LusidInstrumentId.* to locate prices loaded with an instrumentIdType of LusidInstrumentId.
  • Quote.Isin.GB0031348658 to locate prices loaded with an instrumentIdType of Isin and instrumentId of GB0031348658.

Note: Quote.*.* does NOT locate any price loaded using any unique or non-unique instrument identifier.

FX spot rateFx.<FgnCcy>.<DomCcy>
  • Fx.USD.GBP to locate rates loaded with an instrumentIdType of CurrencyPair and instrumentId of USD/GBP (note forward slash).
  • Fx.*.* to locate any FX rate.
Discount factor curveComplex Market Data StoreRates.<Ccy>.<Ccy>OIS
  • Rates.USD.USDOIS to locate USD discount factor curves with a marketAsset of USD/USDOIS.
  • Rates.*.* to locate any discounting curve.
Interest rate projection curveRates.<Ccy>.<Tenor>.<Index>
  • Rates.GBP.1Y.LIBOR to locate one year GBP - LIBOR IR projection curves loaded with a marketAsset of GBP/1Y/LIBOR.
  • Rates.*.*.* to locate any IR projection curve.
Credit curveCredit.<REDCode>.<Ccy>.<Seniority>.<RestructuringClause>
  • Credit.FF667M.USD.SNR.MR to locate senior CDS with modified restructuring loaded with a marketAsset of FF667M/USD/SNR/MR.
  • Credit.*.*.*.* to locate any credit curve.
Equity volatility surfaceEquityVol.<RIC>.<Ccy>.<L-or-LN>
  • EquityVol.AAPL.USD.LN to locate Apple log normal volatility surfaces loaded with a marketAsset of AAPL/USD/LN.
  • EquityVol.*.*.* to locate any equity volatility surface.
FX volatility surfaceFxVol.<FgnCcy>.<DomCcy>.<L-or-LN>
  • FxVol.GBP.USD.L to locate GBPUSD normal volatility surfaces loaded with a marketAsset of GBP/USD/L.
  • FxVol.*.*.* to locate any FX volatility surface.
Interest rate volatility surfaceIrVol.<Ccy>.<L-or-LN>
  • IrVol.USD.LN to locate USD log-normal volatility surfaces loaded with a marketAsset of USD/LN.
  • IrVol.*.* to locate any IR volatility surface.

When you call the GetValuation API, you may see a MarketResolverFailure similar to the one below if one or more market data rules fails to locate suitable market data in an appropriate store. The first step is to make sure field values in the market data rule match those in market data objects according to the table above. If they do, you can try setting the allowPartiallySuccessfulEvaluation pricing model option to True to relax validation and call the API again, though of course missing market data will not be used in calculations:

{"name":"MarketResolverFailure","errorDetails":[{"id":"Failed to resolve market data item [Provider: Edi, PriceSource: , InstrumentId: BBG000C05BD1, InstrumentIdType: Figi, QuoteType: Price, Field: mid].","detail":"Attempted to resolve with supplier [Edi] and sourceSystem [Lusid] in scope [recipe1] for effective date [07/03/2022 00:00:00], quoteInterval [None] and with predicate [AsAt=Latest], but failed because failed to find quote in store."}…]",…}


Changing the default pricing models

LUSID provides a default pricing model for each instrument type, for example the SimpleStatic model for instruments of type Equity to calculate value by multiplying the number of units held by the market price on the valuation date:

Instrument typeDefault pricing model
Bond, FxForwardConstantTimeValueOfMoney
All other typesSimpleStatic

You can change the default pricing model for any instrument type except Equity to provide a more suitable or sophisticated result. You can even conditionally select different pricing models for the same instrument type.

Note: The combination of instrument type and pricing model impacts the quantity and quality of market data required. Find out more about this

For each instrument type whose pricing model you wish to change, specify a vendor model rule in the modelRules array of the pricing section of your recipe. Consider the following example, of three vendor model rules to change the default models for bonds, FxForwards and equity options (all others are left unchanged):

For information on all the fields and allowed values in a vendor model rule, examine the API documentation (expand the configurationRecipe/pricing/modelRules section). The following table summarises core fields:

Vendor model rule fieldStatusExplanation
supplierMandatoryDefaults to Lusid to use built-in pricing models. Do not change this value unless you want to use a pricing model from a 3rd party vendor.
modelNameMandatoryIf supplier is Lusid, must be one of the model names listed here.
instrumentTypeMandatoryIf supplier is Lusid, must be one of the instrument types listed here.
addressKeyFiltersOptionalConditionally selects different pricing models for the same instrument type.
modelOptionsOptional

Configures pricing models for certain instrument types. If instrumentType is:
 

  • FxForward, choose FxForwardModelOptions.
  • FundingLeg, choose FundingLegOptions.
  • EquityOption or EquitySwap, choose EquityModelOptions.

For all other instrument types, omit this field to use the default value of EmptyModelOptions.
 

Note: If you are valuing a fund of funds portfolio and want to ‘look through’ to value holdings in child portfolios, choose IndexModelOptions.
 

For more information on the option fields available, examine the API documentation (expand the modelOptions section) and select the type from the dropdown:

Selecting different pricing models for the same instrument type

You can condition pricing model selection on custom properties added to instruments, on instrument features, or both.

For example, you might want to specify that:

  • American FX option contracts are valued using the BjerksundStensland1993 pricing model. See the code snippet in red below.
  • European FX option contracts sourced from Bloomberg are valued using the BlackScholes pricing model. See the code snippet in blue below.
  • European FX option contracts sourced from UBS are valued using the Discounting pricing model. See the code snippet in green below.

To do this, specify multiple vendor model rules for the same instrument type and distinguish them using the addressKeyFilters field:

  • The left field must reference the 3 stage key of either a property or a feature. In the example below, Instrument/FX/DataProvider is a property added to instruments of type FxOption to record the original data provider, while Instrument/Features/ExerciseType is a feature key referencing one of the available exercise types for FX option contracts in LUSID.
  • The operator field must either be eq or neq.
  • The right field must be a string value on which to match, with a resultValueType of ResultValueString. Note this means only properties with an underlying data type of String are currently supported.

Note: Vendor model rules are processed in the order they are specified in a recipe. The first matching rule found is used. Instruments that do not match any rules fall back to using the default pricing model for the instrument type, which is SimpleStatic for FX option contracts.

"pricing": {
"modelRules": [{
  "supplier": "Lusid",
  "instrumentType": "FxOption",
  "addressKeyFilters": [{
    "left": "Instrument/Features/ExerciseType",
    "operator": "eq",
    "right": {
      "value": "American",
      "resultValueType": "ResultValueString"
    }
  }],
  "modelName": "BjerksundStensland1993"
},
{
  "supplier": "Lusid",
  "instrumentType": "FxOption",
  "addressKeyFilters": [{
    "left": "Instrument/Features/ExerciseType",
    "operator": "eq",
    "right": {
      "value": "European",
      "resultValueType": "ResultValueString"
    }
  },
  {
    "left": "Instrument/FX/DataProvider",
    "operator": "eq",
    "right": {
      "value": "Bloomberg",
      "resultValueType": "ResultValueString"
    }
  }],
  "modelName": "BlackScholes"
},
{
  "supplier": "Lusid",
  "instrumentType": "FxOption",
  "addressKeyFilters": [{
    "left": "Instrument/Features/ExerciseType",
    "operator": "eq",
    "right": {
      "value": "European",
      "resultValueType": "ResultValueString"
    }
  },
  {
    "left": "Instrument/FX/DataProvider",
    "operator": "eq",
    "right": {
      "value": "UBS",
     "resultValueType": "ResultValueString"
    }
  }],
  "modelName": "Discounting"
}],
...

For a more detailed demonstration of conditional pricing model selection, examine this Jupyter Notebook.

Changing default recipe options

A recipe has many options that default to sensible values.

Market data options

OptionDefault valueExplanation
attemptToInferMissingFxFalseSet to True to infer missing FX rates from inverses (for example, GBP/EUR from EUR/GBP) or from comparisons with USD (for example, GBP/USD and EUR/USD).
calendarScopeCoppClarkHolidayCalendarsSet to the scope of a different holiday calendar. You can then specify code(s) of this calendar in various places (such as the definitions of instruments with a term structure) to cause LUSID to automatically factor in market holidays when (for example) generating cashflows. 
For more information on these and other market data options, see the class reference.

Pricing model options

OptionDefault valueExplanation
allowPartiallySuccessfulEvaluationFalseSet to True to allow the valuation process to continue if failures are encountered.
produceSeparateResultForLinearOtcLegsFalseSet to True to value the legs of instruments such as futures, FxForwards and interest rate swaps separately.
For more information on these and other pricing model options, see the class reference.

Aggregation options

Aggregation is the process of deciding which of the many metrics LUSID can calculate to include in your valuation report. 

Metrics themselves are not specified in a recipe but rather in a call to the GetValuation API. However, aggregation options are stored in a recipe.

OptionDefault valueExplanation
applyIso4217RoundingFalseSet to True to round metrics that report currency values to the ISO 4217 standard number of decimal places.
For more information on this and other aggregation options, see the class reference.


Incorporating valuation results calculated by other systems

LUSID uses the market data and pricing models marshalled by a recipe to calculate valuation results for metrics such as PV and PnL.

You can optionally choose to incorporate or replace results for certain metrics with those calculated by a 3rd party system. You might do this if you prefer the precision of the external result data over that calculated by LUSID, or you want to reconcile the two.

For example, LUSID calculates PV for a bond from a combination of accrual and market price. You can choose to replace the calculation of accrual using the Valuation/InstrumentAccrued metric that LUSID provides with accrual results from a 3rd party system, and arrive at a different calculation of PV. For more information on this scenario, including how to load external result data into the LUSID Structured Result Store, see this article.

For each category of external result data you wish to incorporate you must specify a result data rule in the resultDataRules array of the pricing section of your recipe. Consider the following example, of one rule that retrieves result data of type UnitResult/Analytic encapsulated in a particular scope from the Structured Result Store:

"pricing": {
  "resultDataRules": [
    {
      "resourceKey": "UnitResult/*",
      "supplier": "Client",
      "dataScope": "my-bond-accrual-result-srs-scope",
      "documentCode": "instrumentaccrued-results",
      "quoteInterval": "2D",
      "documentResultType": "UnitResult/Analytic",
      "resultKeyRuleType": "ResultDataKeyRule"
    }
  ]
  ...
},
...