A recipe has dozens of settings that fine-tune how LUSID finds market data, prices instruments and adds up the results, most of which default to sensible values out-of-the-box.
Note that a recipe is a JSON document organised into four top-level objects: market, pricing, aggregation and holding:
{
"configurationRecipe": {
"scope": "MyRecipes",
"code": "ValuationRecipe",
"description": "A recipe for valuing multi-asset, multi-region portfolios",
"market": {},
"pricing": {},
"aggregation": {},
"holding": {}
}
}
Market data
The market object controls how LUSID locates market data.
Field | Default value | Explanation |
|---|
market.marketRules
market.specificRules
market.groupedMarketRules
| N/A | The market data rules that actually locate market data. More information. |
market.suppliers
| N/A | Legacy field. Do not set. |
market.options.defaultScope
| default if you leave out market.options altogether; no value if you supply market.options without it
| When set, LUSID adds a catch-all market data rule for each asset type (for example Quote.LusidInstrumentId.* and Fx.*.*), pointing at the supplied scope. It tries these rules last, after all of your own rules. If a valuation error names a rule you didn't write, it is one of these catch-all rules, and it means none of your own rules found the data. The catch-all rules look for data on the valuation date only. The catch-all FX rule looks for FX rates stored with a quote type of Price, not Rate. Catch-all rules match a wide range of data and can be slow. We recommend writing explicit market data rules for the data you need. Either set defaultScope on purpose as a safety net, or leave it out. Without it, an error tells you which rule to add, for example Suggest adding a market data key rule with key Fx.GBP.USD. Note that supplying "options": {} creates no catch-all rules, but leaving out options altogether creates them in the default scope. |
market.options.defaultSupplier
| Lusid
| The supplier used by the catch-all rules. It has no effect on your own rules, which each name their own supplier, or when there is no defaultScope. |
market.options.defaultInstrumentCodeType
| LusidInstrumentId
| The instrument identifier used by the catch-all rules. For example, if your prices are stored against FIGIs, set this to Figi. Otherwise the catch-all rule doesn't find them. Allowed values: LusidInstrumentId, Figi, RIC, QuotePermId, Isin, CurrencyPair, ClientInternal, Cusip, Sedol. |
market.options.attemptToInferMissingFx
| false
| Set to true to infer missing FX rates. For example, if a AUDJPY rate is required for a valuation but cannot be located in the Quote Store, then: If a JPYAUD rate is located, LUSID returns the inverse: 1/JPYAUD. LUSID triangulates through USD, then GBP, then EUR. It tries USD for every missing rate first, and only tries GBP and then EUR for rates it still can't find. So for example if USDAUD and USDJPY are located, LUSID returns USDJPY/USDAUD.
An inferred rate takes the earlier effective date of the two rates it was built from, so one stale rate makes the result stale too. LUSID also infers missing FX forward curves in the same way. LUSID looks up more quotes when this is enabled, so valuations can be slightly slower. See this article and this Jupyter Notebook. This does not apply to FX fixings for NDFs; use attemptToInferMissingFxOnFixings instead (below). |
market.options.attemptToInferMissingFxOnFixings
| false
| Set to true to apply the same inference (above) to FX fixings, for example the fixing of a non-deliverable FX forward. LUSID infers the rate on the fixing date. This is disabled by default because a fixing is a contractual historical rate: without it, LUSID uses a fixing only if it finds the exact currency pair in the right direction. This setting works entirely independently of attemptToInferMissingFx. |
market.options.calendarScope
| CoppClarkHolidayCalendars
| The scope of the holiday calendars LUSID uses. LUSID looks for every calendar code in this one scope: instrument schedules, flow conventions, curves and business-day valuation schedules. Set it if you maintain your own calendars in your own scope. Caution: if LUSID can't find a calendar code that an instrument uses, it uses a weekends-only calendar instead, with no error. If schedule dates look one business day out, check this first. |
market.options.conventionScope
| Conventions
| The scope of the flow conventions and index conventions that instruments refer to by name. If a convention isn't found, the valuation fails with Could not find FlowConventions with code .... |
market.options.pricingBasis
JSON only | Mid
| Set to Bid or Ask to value on that side of the price quote, for example for a bid-struck or swing-priced NAV. Every instrument price rule then reads the bid or ask field, whatever field the rule names. You don't need to duplicate your rules. FX, curve, spread, rate and volatility rules are never affected. Mid doesn't force mid prices: each rule reads the field it names. To show several sides of the quote side by side, use the PricingBasis metric option instead. If your bid and ask prices are stored somewhere different from your mid prices, you can add separate bidMarketRules and offerMarketRules to market. Set field on each of those rules, because a rule without field reads the mid price. When a NAV is struck on Bid or Ask, LUSID refuses portfolios that hold short positions. |
Pricing
The pricing object determines the pricing models used by instrument types to calculate PV and other metrics.
Top-level field | Default value | Explanation |
|---|
pricing.modelRules
| N/A | The vendor model rules that change the default pricing models for all instrument types other than Equity, and also set options for those pricing models that fine-tune calculations. More information. |
pricing.modelChoice
| Legacy field. Do not set. Use pricing.modelRules to explicitly set pricing models for instrument types. |
pricing.options
| See the tables in the rest of this section below. |
pricing.resultDataRules
| The result data rules that locate external valuation results held in the Structured Result Store. |
pricing.holdingPricingInfo
| Despite being located in the pricing object, this configures how market data rules in the market object fall back to using holding-level pricing data rather than market prices held in the Quote Store. More information. |
Configuring pricing models
Field | Default value | Explanation |
|---|
pricing.options.modelSelection
| {"library": "Lusid", "model": "SimpleStatic"}
| Legacy field. Do not change. Use pricing.modelRules to explicitly set a pricing model for each instrument type. |
pricing.options.useInstrumentTypeToDeterminePricer
| false
| Legacy field. Do not change. Use pricing.modelRules to explicitly set a pricing model for each instrument type. |
pricing.options.allowAnyInstrumentsWithSecUidToPriceOffLookup
| false
| Legacy field. Do not change. |
pricing.options.enableUseOfCachedUnitResults
| false
| Legacy field. Do not change. |
pricing.options.allowFallbackOnModelDecline
JSON only | false
| Under development. Contact technical support for more information. |
Handling failures, matured and forward-starting instruments
Field | Default value | Explanation |
|---|
pricing.options.allowPartiallySuccessfulEvaluation
| false
| Set to true so that one failure doesn't fail the whole valuation. Holdings that fail show an error in their PV, exposure and accrued cells instead of a number; the Valuation/ModelError metric explains why. Server errors still fail the request. In a cash flow query, an instrument that fails is reported as a placeholder row. Caution: In a lookthrough valuation, a constituent that fails counts as zero, so the remaining constituents are weighted more heavily (LUSID adds a warning to each row). Keep disabled for reconciliations, cash flow extracts and lookthrough valuations. See Troubleshooting valuation. |
pricing.options.returnZeroPv
| {"instrumentMatured": false}
| Set to {"instrumentMatured": true} to value an instrument after its last payment date with a PV and exposure of zero, instead of returning an error. LUSID still attaches the reason (the instrument has matured) so you can see why the value is zero. Note credit default swaps are never treated as matured. See Troubleshooting valuation. |
pricing.options.windowValuationOnInstrumentStartEnd
| false
| Set to true to value an instrument at zero, with no error, before its start date and from its maturity date onwards. LUSID returns only the PV, needs no market data for these instruments, and leaves other metrics blank. Cash flow queries are not affected. |
pricing.options.removeContingentCashflowsInPaymentDiary
| false
| Legacy field. Do not change. |
The two zero-PV settings behave differently:
| returnZeroPv.instrumentMatured
| windowValuationOnInstrumentStartEnd
|
|---|
Before the start date (forward-starting instruments) | Priced as normal | Zero |
On the maturity date | Priced as normal | Zero |
After the last payment date | Zero PV and exposure, with an error explaining why | Zero PV only, with no error |
If both are set | | Takes priority |
Legs and units
Field | Default value | Explanation |
|---|
pricing.options.produceSeparateResultForLinearOtcLegs
| false
| Set to true to report each leg of an instrument such as an interest rate swap, FX forward, FX swap or equity swap as a separate row. Each row populates the Valuation/LegIdentifier and Valuation/LegIndex metrics An option premium is also reported as its own row. When enabled, metrics using the DateTimeAgo or benchmark metric options fail for holdings that produce more than one row. An FxForward priced with SimpleStatic also fails; see Modelling FxForwards in LUSID. |
pricing.options.fxForwardContractsAsUnitsInBothLegs
| false
| Applies to FxForwards booked as instruments and valued with produceSeparateResultForLinearOtcLegs set to true. By default, Holding/Units on the foreign leg is the number of contracts multiplied by the ratio of the foreign to domestic amount. Set to true to report the number of contracts on both legs, so that Holding/Units × Valuation/InstrumentPV = Valuation/PV on each leg. |
Prices, currencies and accrued interest
Field | Default value | Explanation |
|---|
pricing.options.validateDomesticAndQuoteCurrenciesAreConsistent
| false
| Set to true to check that a price quote's currency matches the instrument's domestic currency when a model prices the instrument from a quote (for example SimpleStatic or BondLookupPricer). If the currencies don't match, the valuation fails with an error. When false, LUSID uses a quote in the wrong currency as it is, without converting it. Prices in a minor currency unit, such as GBp for a GBP instrument, are rescaled either way. We recommend setting this to true. |
pricing.options.useInstrumentScaleFactorAsDefault
| false
| Set to true to use a factor specified at the instrument level to scale market prices loaded into the LUSID Quote Store. LUSID uses the instrument's factor only when the quote has no scale factor of its own. It doesn't apply to cash flow queries or instrument events. |
pricing.options.fundingCurveByCurrency
JSON only | None (LUSID discounts each currency on <CCY>OIS) | Names the discount curve for each currency, for example {"GBP": "GBPOIS-USDCOLL"} for a collateral curve. The discount curve key then becomes Rates.GBP.GBPOIS-USDCOLL, and rates risk is reported against that curve. Projection curves are not affected. Keep OIS in every discount curve name. LUSID doesn't check that the curve exists when you save the recipe. For an example, see this article. |
pricing.accrualDefinition
(in pricing, not pricing.options) | SOD
| Set to EOD to include the valuation day in LUSID's accrued interest calculation. The allowed values are SOD and EOD. LUSID doesn't check the value when you save the recipe. Any other value makes every valuation that calculates accrued interest fail. Credit default swaps and CDS indices always report end-of-day accrued interest. |
Bonds, asset-backed securities and loans
Field | Default value | Explanation |
|---|
pricing.options.fixedIncomeValuationsUsingCurrentFace
JSON only | false
| Set to true to scale results for Bond, ComplexBond and InflationLinkedBond holdings by the holding's current face, instead of its units. The PV calculation changes only for holdings where you have booked a current face. note PIK bonds always use current face. It doesn't apply to asset-backed bonds, which use mbsValuationUsingHoldingCurrentFace (see below). |
pricing.options.mbsValuationUsingHoldingCurrentFace
| false
| Set to true to value asset-backed ComplexBond holdings using a pool factor worked out from the holding's current face, instead of a pool factor from the Quote Store. A holding with no current face is valued at a pool factor of 1. Valuations of large portfolios can be slower with this enabled. |
pricing.options.defaultPoolFactorsToUnity
JSON only | false
| Set to true to treat a missing pool factor for an asset-backed bond valued using the BondLookupPricer pricingmodel as 1 (the full original face), for example for a newly issued security before its first factor is published. It has no effect under ConstantTimeValueOfMoney. When false and LUSID cannot find a pool factor, the holding fails to value; LUSID never uses zero. LUSID looks for the pool factor on the bond's most recent payment date, using the quoteInterval of your pool factor rule. |
pricing.options.suppressAutomaticLoanPrincipalRepayment
JSON only | false
| Set to true to stop LUSID generating a LoanPrincipalRepaymentEvent at the end of the schedule for FlexibleLoan contracts held on a LoanFacility. You must then book rollovers or repayment events yourself. Otherwise the contract stays open and may fail to value once it has matured. Stand-alone loans, loans on a WholeLoanFacility, interest repayments and repayment events you load yourself are not affected. Set this on the recipe the portfolio uses for instrument events; setting it on a recipe used purely for valuation has no effect. |
Structured result data
Field | Default value | Explanation |
|---|
pricing.options.convertSrsCashFlowsToPortfolioCurrency
| false
| Set to true to convert cash flows you have loaded into the Structured Result Store to portfolio currency in cash flow queries. LUSID uses the spot rate on each cash flow's payment date, so you need FX rates for those dates. If a rate is missing, that instrument's cash flows fail. Cash flows that LUSID calculates itself are not affected. |
pricing.options.enableLegLevelInferenceForCustomSrsColumns
| false
| Set to true to fill in missing UnitResult/* values between an instrument and its legs: An instrument's value is the sum of its legs' values, and a missing leg counts as zero. A missing leg's value is the instrument's value less the legs you supplied, split evenly between the missing legs.
Values you supply always take priority. |
pricing.options.scaleInstrumentAccruedOverrideByContractSize
| false
| By default, LUSID treats a Valuation/InstrumentAccrued value from the Structured Result Store as the accrued interest for one unit. Set to true to multiply it by the instrument's contract size instead, which was LUSID's earlier behaviour. |
Risk
For more on how LUSID calculates risk, see How does LUSID assess risk?
Field | Default value | Explanation |
|---|
pricing.options.riskEngine
| Bump
| Set to Adjoint to calculate rates deltas analytically where LUSID can. Currently this is for FX forwards and interest rate swaps priced with Discounting. Everything else is still calculated by bumping curves. Parity calculates both: LUSID reports the bumped number and fails the measure if the two disagree. |
pricing.options.riskBumpOptions
JSON only | None (see below) | Sets the size of the bumps LUSID uses to calculate risk. |
pricing.options.inflationConvexity
JSON only | None (no correction) | Applies a Jarrow-Yildirim convexity correction to projected inflation index values. It affects only the InflationForward model, which LUSID uses for risk and scenario analysis, not for book PV. It makes no difference where the instrument's observation lag matches the curve's. You must supply all four parameters: nominalIndexCorrelation (between -1 and 1), indexVolatility, nominalVolatility and nominalMeanReversion (each greater than zero, as decimals). LUSID checks them when you save the recipe. |
pricing.options.findOrCalculate and pricing.options.findOrCalculateWriteThrough
| Off and false
| Leave these at their defaults unless FINBOURNE advises otherwise. |
Changing risk bump sizes
Every member of riskBumpOptions is optional. Any member you leave out keeps its default.
Member | Default value | Explanation |
|---|
deltaShift
| 0.0001 (1bp)
| The bump size for delta and gamma measures. |
resultSensitivity
| 0.0001 (1bp)
| The move LUSID reports results per. For example, set to 0.01 to report risk per 1% move. Changing deltaShift changes how LUSID estimates the result, not the unit it reports in. |
deltaShiftOverrides
| None | A bump size for a particular asset type, for example {"Credit": 0.001}. Keys are asset types, such as Rates, Credit, Fx and IrVol. For Fx, the bump is an absolute move in the quoted rate, so 0.001 is 10 pips. |
ladderShiftOverrides
| FX: ±0.5%, ±1%, ±5%, ±10%. Everything else: ±50bp, ±100bp, ±250bp, ±500bp. Both include 0. | The bumps used by ladder measures (for example Risk/Rates/Difference/Ladder), by asset type. Each list must be in increasing order. |
parityRelativeTolerance
| 0.001
| How far apart the two numbers may be when riskEngine is Parity. |
Ladder measures are currently reported correctly only when deltaShift and resultSensitivity are at their defaults and there is no deltaShiftOverrides entry for the ladder's asset type. Otherwise each rung is scaled by resultSensitivity ÷ the bump size. For example, "deltaShiftOverrides": {"Fx": 0.001} makes every FX ladder rung one tenth of its true value. If you need ladders, change only ladderShiftOverrides.
For example:
"riskBumpOptions": {
"deltaShiftOverrides": { "Credit": 0.001 },
"ladderShiftOverrides": { "Rates": [-0.02, -0.01, 0, 0.01, 0.02] }
}
LUSID bumps credit curves by 10bp for credit delta and gamma, and sets a ±100bp/±200bp grid for the rates ladder. The rates ladder is reported correctly, because there is no Rates entry in deltaShiftOverrides.
Named prices
Field | Default value | Explanation |
|---|
pricing.options.namedPrices
JSON only | None | Defines names you can use to request a particular kind of price in one column. Each name combines a side of the quote with an optional notional dealing cost. See below. |
For example, a fund might define a creation price, a cancellation price and a plain mid price:
"namedPrices": [
{ "name": "creation", "base": "Offer", "ndc": "AddBuy" },
{ "name": "cancellation", "base": "Bid", "ndc": "SubtractSell" },
{ "name": "perfRef", "base": "Mid" }
]
name must start with a letter and contain only letters and digits. A recipe can have up to 32 names.
base is Bid, Mid or Offer. Mid reads each price rule's own field, as an ordinary valuation does; it doesn't average bid and offer.
ndc is None (the default), AddBuy or SubtractSell. LUSID always calculates the buy cost from the offer-side value and the sell cost from the bid-side value. For AddBuy or SubtractSell, the valuation needs a notional dealing cost table, either on the request (notionalDealingCostTableId) or on the fund's NAV type.
To request a named price, add a NamedPrice option to a Valuation/PV, Valuation/PvInReportCcy or Valuation/PvInPortfolioCcy metric. Spell the name exactly as it is defined, including capitals:
{ "key": "Valuation/PV", "op": "Value", "options": { "NamedPrice": "creation" } }
You can't combine NamedPrice with the PricingBasis, Ndc or scenario options on the same metric. Request them as separate metrics instead. See Setting options on metrics.
Lookthrough portfolios
For more on what look lookthrough means in LUSID, see Setting up and valuing a 'fund of funds' portfolio.
Field | Default value | Explanation |
|---|
pricing.options.conservedQuantityForLookthroughExpansion
| PV
| Which quantity LUSID keeps the same when it expands a holding into its lookthrough constituents: PV or Exposure. |
pricing.options.useChildSubHoldingKeysForPortfolioExpansion
| false
| By default, each lookthrough constituent takes the sub-holding key values of the parent holding. Set to true to keep each constituent's own sub-holding keys from the child portfolio instead. |
Aggregation
Aggregation is the process of deciding which of the many metrics LUSID can calculate to include in your valuation report, and how to group and add them up. Metrics themselves are not specified in a recipe but rather in a call to the GetValuation API. However, aggregation settings are stored in a recipe.
Field | Default value | Explanation |
|---|
aggregation.options.useAnsiLikeSyntax
| false
| Controls how strictly LUSID checks a grouped report. By default, if you request a metric with "op": "Value" that isn't in the group-by and has different values within a group, LUSID leaves that cell blank. Set to true to reject any grouped request where a metric with "op": "Value" isn't in the group-by, whatever its values, with the error ANSI mode enabled. Found unexpected columns. Note: Currently enabling this setting rejects every grouped request, even one containing only group-by keys and aggregates, because of a column LUSID adds internally (Lookthrough/ScalingWarning). We recommend leaving it disabled for now. |
aggregation.options.allowPartialEntitlementSuccess
| false
| Applies when you value a portfolio group and aren't entitled to view some of its portfolios. Set to true to value the portfolios you can see, instead of failing the request. The results don't show which portfolios were left out. LUSID ignores this setting when you request group totals or collateral roll-ups, because those would be wrong. |
aggregation.options.applyIso4217Rounding
| false
| Set to true to round metrics that report currency values to the ISO 4217 standard number of decimal places. LUSID rounds halves away from zero, so JPY 1000.5 becomes 1001. LUSID rounds each holding before adding them up, so totals are sums of rounded values. For example, two holdings each worth GBP 10.125 total 20.26, not 20.25. Per-unit, instrument-level metrics such as Valuation/InstrumentPV are never rounded. More information on rounding. |
Holdings
Field | Default value | Explanation |
|---|
holding.taxLotLevelHoldings
| true
| By default, LUSID values each tax lot separately and populates Holding/TaxLotId, so an ungrouped report can have several rows per holding. Grouped totals are the same either way. Set to false for one row per holding, which is faster and easier to reconcile with your holdings. If the recipe changes during a date range you value, LUSID uses the value at the latest date for the whole range. |