In this tutorial we'll see how to construct a recipe to value a GBP-denominated portfolio containing US and UK equities, using market prices and FX spot rates from Bloomberg pre-loaded into the LUSID Quote Store.

A recipe must have a scope and code that together uniquely identify it, and optionally a description. The core framework of a recipe is thus a JSON document as follows:

  {
    "scope": "my-recipe-scope",
    "code": "my-recipe-code",
    "description": "A simple recipe to value equities using price * units",
    "market": {},
    "pricing": {},
    "aggregation": {},
    "holding": {}
  }

In order to be functional, a recipe must have at least one market data rule in the market section. Every other aspect of a recipe defaults to a sensible value. To value a basket of equities, typically there is no need to change pricing models (that is, populate the pricing section) or set options (the aggregation and holding sections).

Note: You are likely to want to change pricing models and set options to value instruments of types other than Equity. This impacts the market data required, and therefore the composition of the market section.

Constructing a market data rule for market prices

We must construct a market data rule for each category of market data to locate; that is, one rule for market prices, and a second rule for FX rates.

To do this for market prices, we need to understand how they were loaded into the LUSID Quote Store, for example:

Market price in LUSID Quote Store

Matching market data rule field in recipe

Notes

Data field

Example value

scope

My-BBG-Prices

dataScope

The recipe value must match the Quote Store value exactly.

provider

Bloomberg

supplier

The recipe value must match the Quote Store value exactly. More information.

instrumentIdType

Figi

key

The recipe key field has a syntax starting with Quote. for prices that allows for exact or partial matches. More information.

instrumentId

BBG000C05BD1

quoteType

Price

quoteType

The recipe value must match the Quote Store value exactly.

field

mid

field

The recipe value must match the Quote Store value exactly. More information.

effectiveAt

2024-01-15T00:00:00Z

n/a

There is no matching field in the recipe, but if the valuation date is >24 hours after the effective date, you might need to set the quoteInterval field in the recipe to ‘look back’ further (that is, consider older market data valid). More information.

Our first market data rule to locate prices might look like this:

  {
    "scope": "my-recipe-scope",
    "code": "my-recipe-code",
    "description": "A simple recipe to value equities using price * units",
    "market": {
      "marketRules": [
        {
          "key": "Quote.Figi.*",
          "supplier": "Bloomberg",
          "dataScope": "My-BBG-Prices",
          "quoteType": "Price",
          "field": "mid"
        }
      ]
    },
    "pricing": {},
    "aggregation": {},
    "holding": {}
  }

Constructing a market data rule for FX rates

In exactly the same way, we need to understand how FX rates were loaded into the LUSID Quote Store, for example:

FX rate in LUSID Quote Store

Matching market data rule field in recipe

Notes

Data field

Example value

scope

My-BBG-Rates

dataScope

The recipe value must match the Quote Store value exactly.

provider

Bloomberg

supplier

The recipe value must match the Quote Store value exactly. More information.

instrumentIdType

CurrencyPair

key

The recipe key field has a syntax starting with Fx. for rates that allows for exact or partial matches. More information.

instrumentId

USD/GBP

quoteType

Rate

quoteType

The recipe value must match the Quote Store value exactly.

field

mid

field

The recipe value must match the Quote Store value exactly. More information.

effectiveAt

2024-01-15T00:00:00Z

n/a

There is no matching field in the recipe, but if the valuation date is >24 hours after the effective date, you might need to set the quoteInterval field in the recipe to ‘look back’ further (that is, consider older market data valid). More information.

Our second market data rule to locate FX rates might look like this:

  {
    "scope": "my-recipe-scope",
    "code": "my-recipe-code",
    "description": "A simple recipe to value equities using price * units",
    "market": {
      "marketRules": [
        {
          "key": "Quote.Figi.*",
          "supplier": "Bloomberg",
          "dataScope": "My-BBG-Prices",
          "quoteType": "Price",
          "field": "mid"
        },
        {
          "key": "Fx.USD.GBP",
          "supplier": "Bloomberg",
          "dataScope": "My-BBG-Rates",
          "quoteType": "Rate",
          "field": "mid"
        }
      ]
    },
    "pricing": {},
    "aggregation": {},
    "holding": {}
  }

Note that a recipe is a hierarchical document, and LUSID reads the market data rule for prices before the market data rule for FX rates. Since these rules are for different categories of market data, there is no impact. However, if you specify two rules for the same category of market data (that is, with the same key, for example one rule for Bloomberg prices and a second rule for Refinitiv prices), then prices from Bloomberg are preferred. You can use this feature to set up a hierarchy of preferred suppliers.

Synthesising a mid price from a bid and ask price

What if Bloomberg does not always supply a mid price, but instead provides separate bid and ask prices? By default, the valuation will fail, because the first market data rule is looking exclusively for mid prices. However, we can fall back to synthesising an average from the bid and ask, and use that instead.

Imagine bid and ask prices like these have been loaded into the Quote Store:

Data field

Example bid price

Example ask price

Notes

scope

My-BBG-Prices

My-BBG-Prices

provider

Bloomberg

Bloomberg

instrumentIdType

Figi

Figi

To synthesise an average, instrument identifier types must be the same.

instrumentId

BBG000C05BD1

BBG000C05BD1

To synthesise an average, values must identify the same instrument.

quoteType

Price

Price

To synthesise an average, quote types must be the same.

field

bid

ask

effectiveAt

2024-01-15T00:00:00Z

2024-01-15T00:00:00Z

metricValue.Value

10

12

metricValue.Unit

GBP

GBP

To synthesise an average, currency units must be the same.

We can group market data rules and perform an operation on them such as synthesising an average value, or taking the latest or highest value.

To do this, we add a groupedMarketRules section and nominate an operation, in this case AverageOfAllQuotes. Note the composition of market data rules themselves within a group is identical, but that other rules apply to groups:

  {
    "scope": "my-recipe-scope",
    "code": "my-recipe-code",
    "description": "A simple recipe to value equities using price * units",
    "market": {
      "marketRules": [
        {
          "key": "Quote.Figi.*",
          "supplier": "Bloomberg",
          "dataScope": "My-BBG-Prices",
          "quoteType": "Price",
          "field": "mid"
        },
        {
          "key": "Fx.USD.GBP",
          "supplier": "Bloomberg",
          "dataScope": "My-BBG-Rates",
          "quoteType": "Rate",
          "field": "mid"
        }
      ],
      "groupedMarketRules": [
        {
          "marketDataKeyRuleGroupOperation": "AverageOfAllQuotes",
          "marketRules": [
            {
              "key": "Quote.Figi.*",
              "supplier": "Bloomberg",
              "dataScope": "My-BBG-Prices",
              "quoteType": "Price",
              "field": "bid"
            },
            {
              "key": "Quote.Figi.*",
              "supplier": "Bloomberg",
              "dataScope": "My-BBG-Prices",
              "quoteType": "Price",
              "field": "ask"
            }
          ]
        }
      ]
    },
    "pricing": {},
    "aggregation": {},
    "holding": {}
  }

When we value the portfolio now, an equity without a mid price is valued using an average of the bid and ask prices.

With our recipe complete, we can upsert it to LUSID using either:

  • The UpsertConfigurationRecipe API.

  • The LUSID web app, by navigating to the Data Management > Recipes dashboard, clicking the Create recipe button, and pasting the JSON document into the box.