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 forQuote.Figi.*
that are identical except the first has asupplier
ofDataScope
and the second asupplier
ofBloomberg
. 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 field | Status | Applicable LUSID market data store(s) | Explanation | Matching market data field in store |
key | Mandatory | Quote, Complex Market Data | A 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 |
supplier | Mandatory | Quote, Complex Market Data | The 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 |
priceSource | Optional | Quote, Complex Market Data | The 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 |
dataScope | Mandatory | Quote, Complex Market Data | This value must match the scope encapsulating the market data in the appropriate store. | scope |
quoteType | Mandatory | Quote | If the rule locates market data in the Quote Store, this value must match the value of the If the rule locates market data in the Complex Market Data Store, omit this field. | quoteType |
field | Mandatory | Quote | If the rule locates market data in the Quote Store, this value must match the value of the If the rule locates market data in the Complex Market Data Store, omit this field. | field |
quoteInterval | Optional | Quote, Complex Market Data | Specifies a 'look back' period for market data longer than the implicit default of You can use the
Note if you choose | n/a |
sourceSystem | Optional | Quote, Complex Market Data | Defaults 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 category | LUSID market data store | key field syntax | Examples |
Market price | Quote Store | Quote.<IdentifierType>.<IdentifierValue> |
Note: |
FX spot rate | Fx.<FgnCcy>.<DomCcy> |
| |
Discount factor curve | Complex Market Data Store | Rates.<Ccy>.<Ccy>OIS |
|
Interest rate projection curve | Rates.<Ccy>.<Tenor>.<Index> |
| |
Credit curve | Credit.<REDCode>.<Ccy>.<Seniority>.<RestructuringClause> |
| |
Equity volatility surface | EquityVol.<RIC>.<Ccy>.<L-or-LN> |
| |
FX volatility surface | FxVol.<FgnCcy>.<DomCcy>.<L-or-LN> |
| |
Interest rate volatility surface | IrVol.<Ccy>.<L-or-LN> |
|
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 type | Default pricing model |
Bond , FxForward | ConstantTimeValueOfMoney |
All other types | SimpleStatic |
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 field | Status | Explanation |
supplier | Mandatory | Defaults 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. |
modelName | Mandatory | If supplier is Lusid , must be one of the model names listed here. |
instrumentType | Mandatory | If supplier is Lusid , must be one of the instrument types listed here. |
addressKeyFilters | Optional | Conditionally selects different pricing models for the same instrument type. |
modelOptions | Optional | Configures pricing models for certain instrument types. If
For all other instrument types, omit this field to use the default value of Note: If you are valuing a fund of funds portfolio and want to ‘look through’ to value holdings in child portfolios, choose For more information on the option fields available, examine the API documentation (expand the |
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 typeFxOption
to record the original data provider, whileInstrument/Features/ExerciseType
is a feature key referencing one of the available exercise types for FX option contracts in LUSID. - The
operator
field must either beeq
orneq
. - The
right
field must be a stringvalue
on which to match, with aresultValueType
ofResultValueString
. Note this means only properties with an underlying data type ofString
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
Option | Default value | Explanation |
attemptToInferMissingFx | False | Set 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). |
calendarScope | CoppClarkHolidayCalendars | Set 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
Option | Default value | Explanation |
allowPartiallySuccessfulEvaluation | False | Set to True to allow the valuation process to continue if failures are encountered. |
produceSeparateResultForLinearOtcLegs | False | Set 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.
Option | Default value | Explanation |
applyIso4217Rounding | False | Set 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" } ] ... }, ...