---
title: "Composing dynamic recipes"
slug: "composing-dynamic-recipes"
updated: 2024-08-30T15:27:21Z
published: 2024-08-30T15:27:21Z
canonical: "support.lusid.com/composing-dynamic-recipes"
---

> ## Documentation Index
> Fetch the complete documentation index at: https://support.lusid.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Composing dynamic recipes

By default, a recipe is a static object. If you have a recipe (which is a JSON document) and copy its settings to form the basis of a new recipe, the two diverge from that moment on; neither inherits changes made to the other.

**Required reading**: [What is a recipe?](/v1/docs/what-is-a-recipe)

You are always likely to require a library of static recipes. But you can make quick and convenient changes by *composing* a recipe from a standard (or base) one on-the-fly. A ‘composed recipe’ is dynamic in the sense that it inherits changes made to the base recipe (or recipes). You can use a composed recipe anywhere a standard recipe can be used.

For example, you might want to take:

- [**Example 1**](/v1/docs/composing-dynamic-recipes#example-1-prepending-a-market-data-rule): A base recipe for valuing a portfolio of equities and compose a recipe with a market data rule prepended to prefer Bloomberg prices over Refinitiv prices.
- [**Example 2**](/v1/docs/composing-dynamic-recipes#example-2-changing-the-look-back-period): A base recipe for valuing a portfolio of equities and compose a recipe with a quote interval added to every market data rule to look back further in time for pricing data.
- [**Example 3**](/v1/docs/composing-dynamic-recipes#example-3-enabling-separate-leg-valuation): A base recipe for valuing a portfolio of FxForwards and compose a recipe with the `produceSeparateResultForLinearOtcLegs` option enabled in order to value legs separately.
- [**Example 4**](/v1/docs/composing-dynamic-recipes#example-4-merging-three-recipes): Base recipes for valuing separate portfolios of equities, bonds and FX options and compose a recipe able to value a portfolio containing all three.

## Understanding how composition works

To compose a recipe you must define a set of rules that specify:

1. Base recipe(s) to inherit settings from. You can inherit the entire JSON document, or just certain sections (that is, particular settings).
2. Operation(s) to perform to change or augment these inherited settings.

This composition ruleset has a unique identifier (scope and code) in the same way as a standard recipe. You can specify this scope and code when you value a portfolio or perform any other operation that requires a recipe. When you do, LUSID processes the rule set, composes a recipe on demand, and applies its settings.

> **Note**: If you change the base recipe, the composed recipe automatically inherits changes providing they do not conflict.

To define a composition ruleset and load it into LUSID, call the [UpsertRecipeComposer](https://www.lusid.com/docs/api#operation/UpsertRecipeComposer) API, specifying:

- A `scope` and `code` that together uniquely identify the composed recipe. It's important these do not clash with those of the base recipe.
- In the `operations` collection, at least one operation, each consisting of:
  - A `value` that is either:
    - `fromRecipe` to identify a base recipe to inherit some or all settings from.
    - `asString` or `asJson` to specify the content of a changed or augmented setting.
  - An `op` that is either `Prepend`, `Append`, `Update`, `Remove` or `Insert`. Note that `Prepend` and `Append` can only be used with collections of settings. `Insert` can only be used to add settings that do not yet exist. `Update` and `Remove` can only be used on settings that *do* exist.
  - A `path` that identifies a location at which to perform `op`. Note the `$` symbol refers to the entire JSON document when used in conjunction with `Insert` and `fromRecipe`, thereby inheriting the entire base recipe.

See below for examples. Note the order of operations in the collection is significant; LUSID performs them in the order specified.

> **Note**: You can call the [GetRecipeComposerResolvedInline](https://www.lusid.com/docs/api#operation/GetRecipeComposerResolvedInline) API to test your composition ruleset passes validation before loading it into LUSID if you wish.

Once loaded, you can call:

- The [GetRecipeComposer](https://www.lusid.com/docs/api#operation/GetRecipeComposer) API to examine the composition ruleset and, if necessary, the `UpsertRecipeComposer` API again to modify it.
- The [GetDerivedRecipe](https://www.lusid.com/docs/api#operation/GetDerivedRecipe) API to examine the composed recipe that LUSID will produce on demand.
- If necessary, the [DeleteRecipeComposer](https://www.lusid.com/docs/api#operation/DeleteRecipeComposer) API to delete the composition ruleset.

## Example 1: Prepending a market data rule

> **Note**: A recipe is a hierarchical JSON document and, in any collection, LUSID uses the **first matching object found**. Prepending a market data rule to the `marketRules` collection in the `market` object is therefore a technique for prioritising that rule.

Consider the following part of a base recipe retrieved from LUSID with a single market data rule designed to locate equity prices from Refinitiv:

```json
{
  "scope": "StandardRecipes",
  "code": "SimpleEquityPricer",
  "market": {
    "marketRules": [
      {
        "key": "Quote.Figi.*",
        "supplier": "DataScope",
        "dataScope": "MyRefinitivPrices",
        "quoteType": "Price",
        "field": "mid",
        "priceSource": "",
        "sourceSystem": "Lusid"
      }
    ]
  },
  "description": "Simple recipe to value equities using price * units",
  ...
}
```

We can call the `UpsertRecipeComposer` API to load a composition ruleset that inherits from and augments this recipe:

```json
curl -X POST 'https://<your-domain>.lusid.com/api/api/recipes/composer'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <your-API-access-token>'
  -d '{
  "recipeComposer": {
    "scope": "ComposedRecipes",
    "code": "SimpleEquityPricerBBG",
    "operations": [
      {
        "value": {
          "fromRecipe": {
            "scope": "StandardRecipes",
            "code": "SimpleEquityPricer"
          }
        },
        "path": "$",
        "op": "Insert"
      },
      {
        "value": {
          "asJson": "{\"key\": \"Quote.Figi.*\", \"supplier\": \"Bloomberg\", \"dataScope\": \"MyBBGPrices\", \"quoteType\": \"Price\", \"field\": \"mid\"}"
        },
        "path": "Market.MarketRules",
        "op": "Prepend"
      }
    ]
  }
}'
```

Note the following:

- The composed recipe has a scope of `ComposedRecipes` and a code of `SimpleEquityPricerBBG`. You can specify this scope and code anywhere the scope and code of the base recipe (`StandardRecipes` and `SimpleEquityPricer`) can be used.
- The first operation identifies the base recipe and inserts the entire JSON document; that is, inherits every setting. This operation is performed first.
- The second operation adds a new JSON object consisting of a market data rule to the start of the `marketRules` collection in the `market` object. This operation is performed second.

We can call the `GetDerivedRecipe` API with the scope and code of the composed recipe to examine it:

```json
curl -X GET 'https://<your-domain>.lusid.com/api/api/recipes/derived/ComposedRecipes/SimpleEquityPricerBBG'
  -H 'Authorization: Bearer <your-API-access-token>'
```

The response is as follows; the composed recipe has two market data rules, with the Bloomberg rule first and thus preferred:

```json
{
  "value": {
    "scope": "ComposedRecipes",
    "code": "SimpleEquityPricerBBG",
    "market": {
      "marketRules": [
        {
          "key": "Quote.Figi.*",
          "supplier": "Bloomberg",
          "dataScope": "MyBBGPrices",
          "quoteType": "Price",
          "field": "mid",
          "priceSource": "",
          "sourceSystem": "Lusid"
        },
        {
          "key": "Quote.Figi.*",
          "supplier": "DataScope",
          "dataScope": "MyRefinitivPrices",
          "quoteType": "Price",
          "field": "mid",
          "priceSource": "",
          "sourceSystem": "Lusid"
        }
      ],
      "suppliers": {},
      "options": {
        "defaultSupplier": "Lusid",
        "defaultInstrumentCodeType": "LusidInstrumentId",
        "defaultScope": "default",
        "attemptToInferMissingFx": false,
        "calendarScope": "CoppClarkHolidayCalendars",
        "conventionScope": "Conventions"
      },
      "specificRules": [],
      "groupedMarketRules": []
    },
    "pricing": {
      "modelRules": [],
      "modelChoice": {},
      "options": {
        "modelSelection": {
          "library": "Lusid",
          "model": "SimpleStatic"
        },
        "useInstrumentTypeToDeterminePricer": false,
        "allowAnyInstrumentsWithSecUidToPriceOffLookup": false,
        "allowPartiallySuccessfulEvaluation": false,
        "produceSeparateResultForLinearOtcLegs": false,
        "enableUseOfCachedUnitResults": false,
        "windowValuationOnInstrumentStartEnd": false,
        "removeContingentCashflowsInPaymentDiary": false,
        "useChildSubHoldingKeysForPortfolioExpansion": false,
        "validateDomesticAndQuoteCurrenciesAreConsistent": false,
        "conservedQuantityForLookthroughExpansion": "PV"
      },
      "resultDataRules": []
    },
    "aggregation": {
      "options": {
        "useAnsiLikeSyntax": false,
        "allowPartialEntitlementSuccess": false,
        "applyIso4217Rounding": false
      }
    },
    "description": "Simple recipe to value equities using price * units",
    "holding": {
      "taxLotLevelHoldings": true
    }
  }
}
```

We can now specify the scope and code of the composed recipe in a call to the [GetValuation](https://www.lusid.com/docs/api#operation/GetValuation) API:

```json
curl -X POST "https://<your-domain>.lusid.com/api/api/aggregation/$valuation"
  -H "Authorization: Bearer <your-API-access-token>"
  -H "Content-Type: application/json-patch+json"
  -d '{
    "portfolioEntityIds": [ {"scope": "Equities", "code": "UK"} ],
    "valuationSchedule": {"effectiveAt": "2024-03-11T00:00:00.0000000+00:00" },
    "recipeId": {"scope": "ComposedRecipes", "code": "SimpleEquityPricerBBG},
    "metrics": [{"key": "Valuation/PvInPortfolioCcy", "op": "Sum"}]
  }'
```

## Example 2: Changing the look back period

In this example:

- The base recipe has two market data rules. One has a `quoteInterval` of `3D.0D`, to look back three days from the valuation datetime for valid pricing data. The other has no `quoteInterval`, so LUSID looks back the default interval of one day.
- The composition ruleset has two operations. The first inherits all the settings in the base recipe. The second changes the `quoteInterval` field of every market data rule to one week. Note the `*` wildcard character must be encapsulated in square brackets.
- In the composed recipe, both market data rules have a `quoteInterval` set to `1W.0D`, including the rule without the field explicitly set before.

| **Base recipe** (part of response from LUSID) | **Composition ruleset** (ready to upsert to LUSID) | **Composed recipe** (part of response from LUSID) |
| --- | --- | --- |
| ```json { "scope": "StandardRecipes", "code": "Lookback", "market": { "marketRules": [ { "key": "Quote.Figi.*", "supplier": "Bloomberg", "dataScope": "MyBBGPrices", "quoteType": "Price", "field": "mid", "quoteInterval": "3D.0D" }, { "key": "Quote.Figi.*", "supplier": "DataScope", "dataScope": "MyRefinitivPrices", "quoteType": "Price", "field": "mid", } ] }, ... } ``` | ```json { "recipeComposer": { "scope": "ComposedRecipes", "code": "LookbackHarmonised", "operations": [ { "value": { "fromRecipe": { "scope": "StandardRecipes", "code": "Lookback" } }, "path": "$", "op": "Insert" }, { "value": { "asString": "1W.0D" }, "path": "Market.MarketRules.[*].QuoteInterval", "op": "Update" } ] } } ``` | ```json { "scope": "ComposedRecipes", "code": "LookbackHarmonised", "market": { "marketRules": [ { "key": "Quote.Figi.*", "supplier": "Bloomberg", "dataScope": "MyBBGPrices", "quoteType": "Price", "field": "mid", "quoteInterval": "1W.0D" }, { "key": "Quote.Figi.*", "supplier": "DataScope", "dataScope": "MyRefinitivPrices", "quoteType": "Price", "field": "mid", "quoteInterval": "1W.0D" } ] }, ... } ``` |

## Example 3: Enabling separate leg valuation

In this example:

- The base recipe has the default value of `false` for the `produceSeparateResultForLinearOtcLegs` option.
- The composition ruleset has two operations. The first inherits all the settings in the base recipe. The second changes the option to `true`.

| **Base recipe** (part of response from LUSID) | **Composition ruleset** (ready to upsert to LUSID) | **Composed recipe** (part of response from LUSID) |
| --- | --- | --- |
| ```json { "scope": "StandardRecipes", "code": "FxForwards", ... "pricing": { "modelRules": [], "modelChoice": {}, "options": { "produceSeparateResultForLinearOtcLegs": false, ... } }, ... } ``` | ```json { "recipeComposer": { "scope": "ComposedRecipes", "code": "FxForwardsSeparateLegs", "operations": [ { "value": { "fromRecipe": { "scope": "StandardRecipes", "code": "FxForwards" } }, "path": "$", "op": "Insert" }, { "value": { "asString": "true" }, "path": "Pricing.Options.ProduceSeparateResultForLinearOtcLegs", "op": "Update" } ] } } ``` | ```json { "scope": "ComposedRecipes", "code": "FxForwardsSeparateLegs", ... "pricing": { "modelRules": [], "modelChoice": {}, "options": { "produceSeparateResultForLinearOtcLegs": true, ... } }, ... } ``` |

## Example 4: Merging three recipes

In this example:

- Base recipe 1 has a market rule, base recipe 2 has a market rule and a pricing model rule, and base recipe 3 has a pricing model rule.
- The composition ruleset has three operations. The first inherits all the settings in base recipe 2, which is the most complex recipe. The second appends the market data rule from base recipe 1. The third prepends the pricing model rule from base recipe 3.

| **Base recipe 1** (part of response from LUSID) | **Composition ruleset** (ready to upsert to LUSID) | **Composed recipe** (part of response from LUSID) |
| --- | --- | --- |
| ```json { "scope": "StandardRecipes", "code": "Equities", "market": { "marketRules": [ { "key": "Quote.Figi.*", "supplier": "Bloomberg", "dataScope": "MyEquityPrices", "quoteType": "Price", "field": "mid", } ] }, ... } ``` | ```json { "recipeComposer": { "scope": "ComposedRecipes", "code": "EquitiesBondFxOptions", "operations": [ { "value": { "fromRecipe": { "scope": "StandardRecipes", "code": "Bonds" } }, "path": "$", "op": "Insert" }, { "value": { "fromRecipe": { "scope": "StandardRecipes", "code": "Equities" } }, "path": "Market.MarketRules", "op": "Append" }, { "value": { "fromRecipe": { "scope": "StandardRecipes", "code": "FxOptions" } }, "path": "Pricing.ModelRules", "op": "Prepend" } ] } } ``` | ```json { "scope": "ComposedRecipes", "code": "EquitiesBondFxOptions", "market": { "marketRules": [ { "key": "Quote.Isin.*", "supplier": "DataScope", "dataScope": "MyBondPrices", "quoteType": "Price", "field": "mid", }, { "key": "Quote.Figi.*", "supplier": "Bloomberg", "dataScope": "MyEquityPrices", "quoteType": "Price", "field": "mid", } ] }, "pricing": { "modelRules": [ { "supplier": "Lusid", "modelName": "BlackScholes", "instrumentType": "FxOption" }, { "supplier": "Lusid", "modelName": "BondLookupPricer", "instrumentType": "Bond" } ] }, ... } ``` |
| **Base recipe 2** (part of response from LUSID) |
| ```json { "scope": "StandardRecipes", "code": "Bonds", "market": { "marketRules": [ { "key": "Quote.Isin.*", "supplier": "DataScope", "dataScope": "MyBondPrices", "quoteType": "Price", "field": "mid", } ] }, "pricing": { "modelRules": [ { "supplier": "Lusid", "modelName": "BondLookupPricer", "instrumentType": "Bond" } ] }, ... } ``` |
| **Base recipe 3** (part of response from LUSID) |
| ```json { "scope": "StandardRecipes", "code": "FxOptions", ... "pricing": { "modelRules": [ { "supplier": "Lusid", "modelName": "BlackScholes", "instrumentType": "FxOption" } ] }, ... } ``` |
