Views:

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 StoreMatching market data rule field in recipeNotes
Data fieldExample value
scopeMy-BBG-PricesdataScopeThe recipe value must match the Quote Store value exactly.
providerBloombergsupplierThe recipe value must match the Quote Store value exactly. More information.
instrumentIdTypeFigikeyThe recipe key field has a syntax starting with Quote. for prices that allows for exact or partial matches. More information.
instrumentIdBBG000C05BD1
quoteTypePricequoteTypeThe recipe value must match the Quote Store value exactly.
fieldmidfieldThe recipe value must match the Quote Store value exactly. More information.
effectiveAt2024-01-15T00:00:00Zn/aThere 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 StoreMatching market data rule field in recipeNotes
Data fieldExample value
scopeMy-BBG-RatesdataScopeThe recipe value must match the Quote Store value exactly.
providerBloombergsupplierThe recipe value must match the Quote Store value exactly. More information.
instrumentIdTypeCurrencyPairkeyThe recipe key field has a syntax starting with Fx. for rates that allows for exact or partial matches. More information.
instrumentIdUSD/GBP
quoteTypeRatequoteTypeThe recipe value must match the Quote Store value exactly.
fieldmidfieldThe recipe value must match the Quote Store value exactly. More information.
effectiveAt2024-01-15T00:00:00Zn/aThere 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 fieldExample bid priceExample ask priceNotes
scopeMy-BBG-PricesMy-BBG-Prices 
providerBloombergBloomberg 
instrumentIdTypeFigiFigiTo synthesise an average, instrument identifier types must be the same.
instrumentIdBBG000C05BD1BBG000C05BD1To synthesise an average, values must identify the same instrument.
quoteTypePricePriceTo synthesise an average, quote types must be the same.
fieldbidask 
effectiveAt2024-01-15T00:00:00Z2024-01-15T00:00:00Z 
metricValue.Value1012 
metricValue.UnitGBPGBPTo 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.