Modelling mortgage-backed securities in LUSID

Prev Next

You can model a mortgage-backed security (MBS) contract as a special kind of ComplexBond in LUSID. See other uses for this instrument type.

Mastering an instrument

There are numerous tools you can use to master a MBS contract as an instrument as type ComplexBond in the LUSID Security Master.

Some fields are common to all types of instrument, such as an intuitive name, the requirement to specify a set of identifiers, and the facility to store extra information as properties.

Fields in the economic definition object are specific to ComplexBond. For more information on these fields, select ComplexBond from the definition dropdown in the API documentation:

In this tutorial, we’ll master a simple fixed schedule MBS contract by calling the UpsertInstruments API as follows:

curl -X POST "https://<your-domain>.lusid.com/api/api/instruments?scope=mycustominstrscope"
   -H "Content-Type: application/json-patch+json"
   -H "Authorization: Bearer <your-API-access-token>"
   -d '{
  "upsert-request-1": {
    "name": "MBS",
    "identifiers": {"ClientInternal": {"value": "MBS"}},
    "definition": {
      "instrumentType": "ComplexBond",                   
      "assetBacked": true,
      "assetPoolIdentifier": "MyMBSMarketDataIdentifier",
      "schedules": [
        {
          "scheduleType": "FixedSchedule",
          "startDate": "2024-12-01T00:00:00+00:00",
          "maturityDate": "2026-11-20T00:00:00+00:00",
          "paymentCurrency": "USD",
          "notional": 1,
          "couponRate": 0.05,
          "flowConventions": {
            "paymentFrequency": "1M",
            "dayCountConvention": "Act365",
            "rollConvention": "20",
            "businessDayConvention": "Following",
            "paymentCalendars": ["USD"],
            "currency": "USD",
            "accrualDateAdjustment": "Unadjusted",
          },
          "exDividendConfiguration": {
            "exDividendDays": 19,
            "applyThirty360PayDelay": true,
          }
        }
      ]
    }
  }
}'

Note the following:

  • We’ve chosen to master this instrument in a custom instrument scope (specified in the URL).

  • The identifiers field uniquely identifies the instrument using a ClientInternal identifier.

  • In the economic definition object:

    • The instrumentType must be ComplexBond.

    • The assetBacked field must be true to distinguish a MBS contract from other non-backed instruments mastered as type ComplexBond.

    • The assetPoolIdentifier should be set to an intuitive string that represents a market data identifier for pool factors loaded into the LUSID Quote Store (required for calculating principal repayments).

    • The notional should be unitised (set to 1) and the amount bought or sold specified on the transaction.

    • The couponRate should be expressed as a decimal rather than a percentage, so a bond paying:

      • 10% should have a couponRate of 0.1

      • 5.0% should have a couponRate of 0.05

      • 0.375% should have a couponRate of 0.00375 and so on.

    • The flowConventions object stores all the information necessary to determine coupon periods, accrued interest amounts and payment dates:

      • The paymentFrequency can be any tenor, in this case 1M to signify once a month. Supported day count conventions.

      • The rollConvention field should count back from maturity, so in this case 20 for a bond maturing on 20 November, which means the coupon dates are the 20th of every month. We also recommend setting a businessDayConvention to determine what should happen if this is not a good business day. More information on roll and business day conventions. Note business days in LUSID are determined by holiday calendars.

      • The accrualDateAdjustment field is optional and defaults to Adjusted. This is suitable for European bonds but for US bonds we recommend Unadjusted.

      • The settleDays field in a flow convention is deprecated, and resetDays is ignored for fixed schedules. The scope and code fields can be ignored unless you are loading a flow convention from a library.

    • The exDividendConfiguration object configures a special ex-div period for a MBS contract:

      • The exDividendDays field should record the difference between the coupon date and the first of the month (when pool factors are recorded in LUSID), so in this case 20 - 1 = 19.

      • The applyThirty360PayDelay field should be set to true to delay coupon and principal payments to the following month.

    • If you do not specify a stubType object, the first and last coupon periods are expected to be regular. More information coming soon.

    • The tradingConventions.priceScaleFactor field is set to 100 so market prices can be entered as a percentage of par.

    • If you do not specify a roundingConventions object, no rounding is applied at the instrument level.

Providing the request is successful, the response:

  • Confirms the globally-unique LUID for the instrument.

  • Generates extra fields that are stored as part of the instrument definition and can be filtered on.

  • Supplies default values for fields not explicitly specified in the request:

{
  "values": {
    "upsert-request-1": {
      "scope": "mycustominstrscope",
      "lusidInstrumentId": "LUID_00003EDZ",
      "version": {
        "asAtCreated": "2025-04-24T11:10:25.8725820+00:00",
        "userIdCreated": "00u91lo2d7X42sdse2p7",
        "requestIdCreated": "2025042411-35bc0fe68ef8414f9766ca62e6613d4b",
        "asAtModified": "2025-04-24T11:20:41.4671350+00:00",
        "userIdModified": "00u91lo2d7X42sdse2p7",
        "requestIdModified": "2025042411-65bd46cf085f4f43899b9d3004a3cff9",
        "asAtVersionNumber": 1,
        "entityUniqueId": "9c64bc2b-fe0b-4e95-b91c-7f8e5b11cc02"
      },
      "name": "MBS",
      "identifiers": {
        "LusidInstrumentId": "LUID_00003EDZ",
        "ClientInternal": "MBS"
      },
      "properties": [],
      "instrumentDefinition": {
        "identifiers": {},
        "calculationType": "Standard",
        "schedules": [
          {
            "startDate": "2024-12-01T00:00:00.0000000+00:00",
            "maturityDate": "2026-11-20T00:00:00.0000000+00:00",
            "flowConventions": {
              "currency": "USD",
              "paymentFrequency": "1M",
              "dayCountConvention": "Actual365",
              "rollConvention": "20",
              "paymentCalendars": [
                "USD"
              ],
              "resetCalendars": [],
              "settleDays": 0,
              "resetDays": 0,
              "leapDaysIncluded": true,
              "accrualDateAdjustment": "Unadjusted",
              "businessDayConvention": "F",
              "accrualDayCountConvention": "Actual365"
            },
            "couponRate": 0.05,
            "notional": 1,
            "paymentCurrency": "USD",
            "stubType": "None",
            "exDividendConfiguration": {
              "useBusinessDays": false,
              "exDividendDays": 19,
              "returnNegativeAccrued": false,
              "applyThirty360PayDelay": true
            },
            "scheduleType": "FixedSchedule"
          }
        ],
        "roundingConventions": [],
        "assetBacked": true,
        "assetPoolIdentifier": "MyMBSMarketDataIdentifier",
        "tradingConventions": {
          "priceScaleFactor": 100,
          "minimumOrderSize": 0,
          "minimumOrderIncrement": 0
        },
        "instrumentType": "ComplexBond"
      },
      "state": "Active",
      "assetClass": "Credit",
      "domCcy": "USD",
      "relationships": []
    }
  },
  ...
}

Booking a transaction to establish a position

Once an instrument is mastered, there are numerous ways we can book a transaction to record the acquisition of a quantity in a suitable portfolio, for example calling the BatchUpsertTransactions API as follows:

curl -X POST 'https://<your-domain>.lusid.com/api/api/transactionportfolios/FixedIncome/US/transactions/$batchUpsert?successMode=Partial&preserveProperties=true'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <your-API-access-token>'
  -d '{
  "transactionRequest-1": {
    "transactionId": "mbs_purchase_001",
    "type": "BuyMBS",
    "instrumentIdentifiers": {"Instrument/default/ClientInternal": "MBS"},
    "transactionDate": "2025-01-10T00:00:00.0000000+00:00",
    "settlementDate": "2025-01-13T00:00:00.0000000+00:00",
    "units": 100000,
    "transactionPrice": {
      "price": 0.98,
      "type": "CleanPrice"
    },
    "totalConsideration": {
      "amount": 0,
      "currency": "USD"
    },
    "properties": {
      "Transaction/MyProperties/TotalCapitalisedFees": {
        "key": "Transaction/MyProperties/TotalCapitalisedFees",
        "value": {
          "metricValue": {
            "value": 200,
            "unit": "USD"
          }
        }
      },
      "Transaction/default/CurrentFace": {
        "key": "Transaction/default/CurrentFace",
        "value": {
          "metricValue": {
            "value": 95000
          }
        }
      }
    }
  }
}'

Note the following:

  • The instrumentIdentifiers field uses the ClientInternal identifier to resolve the transaction to the correct MBS instrument (but it could use the LUID).

  • The type field invokes a custom BuyMBS transaction type to confer a particular economic impact (see below).

  • The units field specifies the face or purchase amount.

  • The transactionPrice object records the clean market price (not including accrued interest). This is used by LUSID to automatically calculate gross consideration (see below).

  • The totalConsideration object:

    • Sets the settlement currency to USD.

    • Specifies a cost of 0 to trigger LUSID to automatically derive total consideration (see below).

  • The trade fee is recorded using a custom property.

  • The outstanding notional is recorded as a non-unitised amount using the Transaction/default/CurrentFace system property, in this case 100000 × 0.95 = 95000.

Note: This example assumes the transaction, settlement and portfolio currencies are the same. If not, you can specify exchange rates.

We can call the SetTransactionType API to create a BuyMBS transaction type as follows:

curl -X PUT 'https://<your-domain>.lusid.com/api/api/transactionconfiguration/types/default/BuyMBS?scope=default'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <your-API-access-token>'
  -d '{
  "aliases": [
    {
      "type": "BuyMBS",
      "description": "Transaction type for MBS bond purchases",
      "transactionClass": "Basic",
      "transactionRoles": "AllRoles",
      "isDefault": false
    }
  ],
  "movements": [
    {
      "name": "Increase units of security",
      "movementTypes": "StockMovement",
      "side": "Side1WithCurrentFace",
      "direction": 1
    },
    {
      "name": "Decrease cash balance",
      "movementTypes": "CashCommitment",
      "side": "Side2",
      "direction": -1
    }
  ],
  "calculations": [
    {
      "type": "Txn:BondInterest"
    },
    {
      "type": "Txn:GrossConsideration"
    },
    {
      "type": "DeriveTotalConsideration",
      "formula": "Txn:GrossConsideration + Properties[Transaction/MyProperties/TotalCapitalisedFees]"
    }
  ]
}'

Note the following:

  • The Txn:BondInterest calculation automatically calculates the amount of accrued interest bought or sold and stores the result in the Transaction/default/BondInterest system property.

  • As a clean price is specified, the Txn:GrossConsideration calculation automatically calculates gross consideration according to the formula (units * price * Transaction/default/CurrentFace) + Transaction/default/BondInterest, and stores the result in the Transaction/default/GrossConsideration system property.

  • The DeriveTotalConsideration calculation automatically calculates total consideration according to the given, user-defined formula, which in this case sums gross consideration and total fees; this is stored as the totalConsideration.amount of the transaction.

  • The StockMovement uses a Side1WithCurrentFace custom side (see below) to establish a holding in the instrument with the specified number of units at a cost derived from the total consideration, and a record of the starting current face.

  • The CashCommitment movement uses the built-in Side2 to decrease the instrument currency holding by the total consideration.

We can call the SetSideDefinition API to create a Side1WithCurrentFace custom side as follows:

curl -X PUT 'https://<your-domain>.lusid.com/api/api/transactionconfiguration/sides/Side1WithCurrentFace?scope=default'
  -H 'Content-Type: application/json-patch+json'
  -H 'Authorization: Bearer <your-API-access-token>'
  -d '{
  "security": "Txn:LusidInstrumentId",
  "currency": "Txn:TradeCurrency",
  "rate": "Txn:TradeToPortfolioRate",
  "units": "Txn:Units",
  "amount": "Txn:TradeAmount",
  "currentFace": "Txn:CurrentFace"
}'

Note this is the same as the built-in Side1 except the currentFace field is set to Txn:CurrentFace, which returns the value of the Transaction/default/CurrentFace system property added to the transaction.

Auditing LUSID’s calculations of transaction amounts

We can navigate to Portfolio Management > Transactions in Output mode in the LUSID web app to call the BuildTransactions API with a suitable window and examine the output transaction generated by LUSID:

  • The gross transaction amount is units * price * Transaction/default/CurrentFace, so 100000 × 0.98 × 0.95 = 93100

  • The amount of accrued interest purchased is 164.38

  • Since the price is clean, the gross consideration is the gross transaction amount plus accrued interest: 93100 + 164.38 = 93264.38

  • The total consideration is gross consideration plus fees: 93264.38 + 200 = 93464.38:

Confirming positions on the settlement date

We can navigate to Portfolio Management > Holdings to call the GetHoldings API on the settlement date and see that we have:

  • 100,000 units of the MBS instrument with a cost calculated as total consideration minus accrued interest: 93464.38 - 164.38 = 93300

  • A USD currency holding with a negative amount reflecting the total consideration paid: change Effective dropdown to show Settlement date…

Valuing your position

To value your position, work through our valuation checklist.

Pricing models

The following pricing models are available for instruments of type ComplexBond. Note your choice impacts the market data required and the composition of your recipe:

Note: The default pricing model for bonds is currently ConstantTimeValueOfMoney, but we no longer recommend it. You should change this to one of the alternatives above. See how to do this.

Metrics

LUSID can report many hundreds of metrics in a valuation report. Note the following:

  • Valuation/PV* metrics calculate PV according to a formula specific to the pricing model.

  • Valuation/Accrued* metrics calculate accrual according to the flow convention defined in the underlying instrument. In most cases this increases in amount until the payment date, at which point it resets to zero and starts to accrue again the next day; there is no date on which the accrued appears as the full value of the coupon because it is assumed to be paid the day a period completes.

  • Valuation/Diagnostics/Accrual/* metrics diagnose accrual calculations.

  • Valuation/CleanPV* metrics calculate PV minus accrual.

  • ProfitAndLoss/* metrics calculate unrealised profit/loss as the difference between the PV and the holding cost at the start and again at the end of the valuation period.

  • Analytic/* metrics calculate yield to maturity and duration.

Market data

To value a MBS instrument position using the recommended BondLookupPricer model, load the following market data for the valuation date:

  1. A market price. This should be a clean price specified as a percentage of par. The quote can resolve to the MBS instrument using any identifier. The scaleFactor should be 100.

  2. A pool factor. This must have a quoteType of PoolFactor and an instrumentIdType of Isin but the instrumentId should be the assetPoolIdentifier value specified in the instrument economic definition. The scaleFactor should be 100.

For example, to value our position on 13 January 2025:

  1. Load two quotes into a particular quote scope (specified in the URL) with the effectiveAt field set to the valuation date:

    curl -X POST 'https://<your-domain>.lusid.com/api/api/quotes/MyMBSQuotes'
      -H 'Authorization: Bearer <your-API-access-token>'
      -H 'Content-Type: application/json-patch+json'
      -d '{
        "Quote-0001": {
          "quoteId": {
            "quoteSeriesId": {
              "provider": "Lusid",
              "instrumentIdType": "ClientInternal",
              "instrumentId": "MBS",
              "quoteType": "Price",
              "field": "mid"
            },
            "effectiveAt": "2025-01-13T00:00:00Z"
          },
          "metricValue": {
            "value": 98, "unit": "USD"
          },
          "scaleFactor": 100,
        },
        "Quote-0002": {
          "quoteId": {
            "quoteSeriesId": {
              "provider": "Lusid",
              "instrumentIdType": "Isin",
              "instrumentId": "MyMBSMarketDataIdentifier",
              "quoteType": "PoolFactor",
              "field": "mid"
            },
            "effectiveAt": "2025-01-13T00:00:00Z"
          },
          "metricValue": {
            "value": 95, "unit": "USD"
          },
          "scaleFactor": 100,
        }
      }'
  2. Create a recipe to locate this market data and change the default pricing model, for example:

    curl -X POST 'https://<your-domain>.lusid.com/api/api/recipes'
      -H 'Content-Type: application/json-patch+json'
      -H 'Authorization: Bearer <your-API-access-token>'
      -d '{
      "configurationRecipe": {
        "scope": "MyRecipes",
        "code": "MyBasicRecipe",
        "market": {
          "marketRules": [
            {
              "key": "Quote.ClientInternal.*",
              "dataScope": "MyMBSQuotes",
              "supplier": "Lusid",
              "quoteType": "Price",
              "field": "mid"
            },
            {
              "key": "Quote.Isin.*",
              "dataScope": "MyMBSQuotes",
              "supplier": "Lusid",
              "quoteType": "PoolFactor",
              "field": "mid"
            }
          ]
        },
        "pricing": {
          "modelRules": [
            {
              "instrumentType": "ComplexBond",
              "modelName": "BondLookupPricer"
            }
          ]
        }
      }
    }'
  3. Generate a valuation report with appropriate metrics, for example:

Assessing risk

LUSID provides an exposure calculation and supports both analytic and bump and valuation mechanisms for assessing risk.

Monitoring the lifecycle of the instrument

A MBS instrument ‘expires’ on the maturity date specified in the instrument economic definition.

Note: An instrument in LUSID is still available post-expiry, although the valuation is zero. If you set your holding to zero it no longer appears in reports unless you are deliberately backdating.

Handling automatic lifecycle events

LUSID is transitioning to a system where it automatically emits lifecycle events for supported instruments. We provide default transaction templates that you can use as-is to automatically generate transactions in portfolios with holdings, and recommendations for transaction types that deliver appropriate economic impacts for those generated transactions.

The following events are available for MBS instruments:

Instrument event type

Event emission criteria

If emitted, default transaction template generates…

Recommendations for transaction types

MbsCouponEvent

This event is automatically emitted by LUSID each time a coupon is due.

A transaction for the coupon amount.

More information coming soon

MbsPrincipalEvent

This event is automatically emitted by LUSID each time a principal repayment is due.

A transaction for the principal amount.

MbsPrincipalWriteOffEvent

MbsInterestShortfallEvent

MbsInterestDeferralEvent

MaturityEvent

This event is automatically emitted by LUSID on the maturity date of the instrument.

A transaction for all the units in the holding at a cost of zero.

Manually loading settlement transactions

If you do not want to turn on automatic instrument lifecycle events you can continue to monitor upcoming cashflows using Portfolio Management > CashLadder in the LUSID web app, or by calling the GetPortfolioCashLadder API directly.

You can call the GetUpsertablePortfolioCashFlows API to  return imminent cashflows as upsertable DTOs ready to manually load into LUSID as input transactions.