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 methods you can use to master an MBS contract as an instrument of 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"],
            "resetCalendars": [],
            "currency": "USD",
            "accrualDateAdjustment": "Unadjusted"
          },
          "exDividendConfiguration": {
            "exDividendDays": 19,
            "applyThirty360PayDelay": true
          }
        }
      ],
      "tradingConventions": {
        "priceScaleFactor": 100
      }
    }
  }
}'

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 an MBS contract from other non-backed instruments mastered as type ComplexBond.

    • The assetPoolIdentifier is the identifier LUSID uses to look up pool factors for this instrument in the Quote Store. It is often the ISIN of the pool. It can be any string, but your pool factor quotes must use exactly this value. More information.

    • 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 sets the payment delay typical of agency MBS:

      • The exDividendDays field records the delay between the end of each accrual period and the coupon date. For a bond paying on the 20th whose accrual periods run from the 1st of one month to the 1st of the next, this is 20 - 1 = 19. Note: Do not specify the exDividendDays field on its own (that is, outside this object).

      • The applyThirty360PayDelay field should be set to true. LUSID then ends each accrual period exDividendDays before the coupon date (in this case, on the 1st of the month). This is also the date for which LUSID expects a pool factor. More information.

    • If you do not specify a stubType object, the first and last coupon periods are expected to be regular. See how to model irregular periods.

    • 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": []
    }
  },
  ...
}

Providing pool factors

LUSID must have access to pool factors in order to perform almost any operation on a portfolio holding an MBS, including calculating transaction amounts, generating coupon and principal events, and valuing a holding.

A pool factor is the fraction of the original principal that is still outstanding, for example 0.95 once 5% of the pool has been repaid. LUSID multiplies the number of units held by the pool factor to calculate the current face, which it uses to calculate coupons, principal repayments and accrued interest.

Understanding when LUSID needs a pool factor

LUSID needs a pool factor at the start and end of each accrual period. A coupon uses the pool factor at the start of its accrual period. A principal repayment is the fall in the pool factor between the start and end of the period, multiplied by the units held.

For a bond with a monthly paymentFrequency, you should therefore load one pool factor per month, dated on the accrual period boundary. With the applyThirty360PayDelay setting in our example, this is the 1st of each month at midnight UTC:

Pool factor date

Value in our example

2024-12-01 (the instrument startDate)

1. LUSID needs a pool factor on this date to calculate the first coupon.

The 1st of each month thereafter

The pool factor published for that month, for example 0.95 on 2025-01-01.

2026-11-01 (the 1st of the month in which the instrument matures)

0, so the final principal event repays the whole outstanding face and LUSID can close the position.

Note the following:

  • LUSID does not assume a pool factor of 1 at the start date. You must load one.

  • Agencies typically publish pool factors a few business days into each month. Until you load a pool factor, LUSID cannot use it for operations on or after its date. You can tell LUSID to fall back on the previous month's pool factor in the meantime; see creating a suitable recipe.

  • If you load pool factors for future months, LUSID uses them when it generates instrument events and cashflows. Otherwise, LUSID carries the latest pool factor forward to maturity.

Loading pool factors into the LUSID Quote Store

The following call to the UpsertQuotes API loads the first two pool factors for our example MBS into a MyPoolFactors quote scope:

curl -X POST "https://<your-domain>.lusid.com/api/api/quotes/MyPoolFactors"
  -H "Authorization: Bearer <your-API-access-token>"
  -H "Content-Type: application/json-patch+json"
  -d '{
    "Quote-0001": {
      "quoteId": {
        "quoteSeriesId": {
          "provider": "Lusid",
          "instrumentIdType": "ClientInternal",
          "instrumentId": "MyMBSMarketDataIdentifier",
          "quoteType": "PoolFactor",
          "field": "mid"
        },
        "effectiveAt": "2024-12-01T00:00:00Z"
      },
      "metricValue": {
        "value": 1, "unit": "none"
      }
    },
    "Quote-0002": {
      "quoteId": {
        "quoteSeriesId": {
          "provider": "Lusid",
          "instrumentIdType": "ClientInternal",
          "instrumentId": "MyMBSMarketDataIdentifier",
          "quoteType": "PoolFactor",
          "field": "mid"
        },
        "effectiveAt": "2025-01-01T00:00:00Z"
      },
      "metricValue": {
        "value": 0.95, "unit": "none"
      }
    }
  }'

For general information on loading quotes into the Quote Store, see this article. Note the following about this example:

  • Both pool factors are loaded into a quote scope (specified in the URL) that is only used for pool factors, to avoid clashes.

  • The instrumentId must be the assetPoolIdentifier specified in the instrument definition, in this case MyMBSMarketDataIdentifier. LUSID does not use the instrument's own identifiers, unless you set one of them to the same value.

  • The instrumentIdType can be ClientInternal, Isin or LusidInstrumentId. Choose one and use it consistently, since your recipe must match it.

  • The quoteType must be PoolFactor.

  • The provider is set to Lusid and the field to mid, to avoid validation errors.

  • The metricValue.value is a fraction of the original principal (0.95, not 95). If your data supplier quotes pool factors as percentages, set a scaleFactor of 100 on each quote.

  • The effectiveAt must be the accrual period boundary, not the date on which the pool factor was published.

You can optionally load other quotes for the same assetPoolIdentifier to record credit events affecting the pool. Each is an amount for one accrual period, expressed as a fraction of the original principal and dated at the end of the period. If you do not load one, LUSID assumes 0.

quoteType

Records

PrincipalWriteOff

Principal written off as a credit loss rather than repaid.

InterestDeferred

Interest due but capitalised into the outstanding principal.

InterestShortfall

Interest due but not paid, and not capitalised.

Creating a suitable recipe

Your recipe must have a market data rule able to locate these pool factors. LUSID's default market data rule only locates prices, so you always need a dedicated rule, for example:

"market": {
  "marketRules": [
    {
      "key": "Quote.ClientInternal.*",
      "dataScope": "MyPoolFactors",
      "supplier": "Lusid",
      "quoteType": "PoolFactor",
      "field": "mid",
      "quoteInterval": "1M"
    },
    ...
  ]
},

For general information on recipes, start with this article. Note the following:

  • The key should be constructed as follows: Quote.<instrumentIdType>.<assetPoolIdentifier>. Use * in place of the assetPoolIdentifier to match all your MBS.

  • The quoteType must be PoolFactor. A rule with a quoteType of Price never locates a pool factor.

  • The dataScope must match the quote scope into which pool factors were loaded.

  • The other fields must match their respective quote fields exactly (values are case-sensitive).

  • The quoteInterval sets how far back LUSID looks if there is no pool factor on the exact date. If you omit it, LUSID only accepts a pool factor dated exactly on the accrual period boundary. A quoteInterval of 1M means LUSID uses the previous month's pool factor until you load the new one. We don't recommend a longer interval, since it can hide a missing pool factor, in which case LUSID calculates a principal repayment of 0 for that month.

  • If you load PrincipalWriteOff, InterestDeferred or InterestShortfall quotes, add a similar rule for each, with the quoteType set accordingly. Do not set a quoteInterval on these rules: each quote applies to a single accrual period, and a lookback could apply one period's amount to the next.

The same recipe can also locate market prices for valuation; see how to do this.

Registering the recipe with portfolios

You must register the recipe with every portfolio in which you intend to hold an MBS. See how to do this.

Important: If LUSID cannot locate a pool factor it needs, operations on the portfolio such as building transactions fail with a MarketResolverFailure error that names each missing pool factor and its date. Register the recipe before you book transactions.

You can use the same recipe for valuation operations if you wish.

Booking a transaction to establish a position

Once an instrument is mastered and you have loaded pool factors, you can book a transaction to record the acquisition of a quantity in a particular transaction portfolio. As mentioned above, we recommend unitising MBS instruments and specifying the original face amount on transactions.

For example, the following call to the BatchUpsertTransactions API acquires 100,000 units of our MBS:

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": 98,
      "type": "CleanPrice"
    },
    "totalConsideration": {
      "amount": 0,
      "currency": "USD"
    }
  }
}'

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 original face amount. LUSID multiplies it by the pool factor to calculate the current face.

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

  • The totalConsideration object:

    • Sets the settlement currency to USD.

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

  • LUSID has a transaction fee engine to help calculate fees and either capitalise or expense them.

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

We recommend that every transaction type you use to buy or sell an MBS includes the Txn:BondInterest and Txn:GrossConsideration transaction type calculations, so that LUSID calculates the current face and the amounts that depend on it for you. For example, you might 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 purchases",
      "transactionClass": "Trading",
      "transactionRoles": "LongLonger",
      "isDefault": false
    }
  ],
  "movements": [
    {
      "name": "Increase units of security",
      "movementTypes": "StockMovement",
      "side": "Side1",
      "direction": 1
    },
    {
      "name": "Decrease cash balance",
      "movementTypes": "CashCommitment",
      "side": "Side2",
      "direction": -1
    },
    {
      "name": "Report bond interest bought as a flow of value out of the security",
      "movementTypes": "Carry",
      "side": "BondInterest",
      "direction": -1
    }
  ],
  "calculations": [
    {
      "type": "Txn:BondInterest"
    },
    {
      "type": "Txn:GrossConsideration"
    },
    {
      "type": "DeriveTotalConsideration",
      "formula": "Txn:GrossConsideration + Txn:TotalFees"
    }
  ]
}'

Note the following:

  • The Txn:GrossConsideration calculation first calculates the current face as units * notional * pool factor, using the pool factor in force on the settlement date, and stores the result in the Transaction/default/CurrentFace system property. If a clean price is specified, it then calculates gross consideration according to the formula (Transaction/default/CurrentFace * price / priceScaleFactor) + Transaction/default/BondInterest, and stores the result in the Transaction/default/GrossConsideration system property.

  • The Txn:BondInterest calculation automatically calculates the amount of bond interest bought or sold on the current face, and stores the result in the Transaction/default/BondInterest system property, available for use in sides.

  • 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 the built-in Side1 side to establish a holding in the instrument with the specified number of units at a cost derived from the total consideration. Side1 also records the current face of the holding, taken from Transaction/default/CurrentFace.

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

  • The Carry movement uses a custom BondInterest side to record a flow of value out of the instrument equivalent to the interest purchased.

Important: If a transaction type for an MBS omits the Txn:GrossConsideration calculation, LUSID does not calculate the current face, and the holding records none. This applies to sales as well as purchases. Alternatively, you can supply the current face yourself in a Transaction/default/CurrentFace property on the transaction, or the pool factor in a Transaction/default/CurrentPoolFactor property, and LUSID uses your value instead.

You might create a BondInterest custom side as follows:

curl -X PUT 'https://<your-domain>.lusid.com/api/api/transactionconfiguration/sides/BondInterest?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:BondInterest"
}'

Note this is the same as Side1 except the amount field is set to Txn:BondInterest instead of Txn:TradeAmount, and there is no currentFace field.

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 current face is units * pool factor, using the pool factor of 0.95 for 1 January 2025 that is in force on the settlement date, so 100000 × 0.95 = 95000

  • The gross transaction amount is current face * price / priceScaleFactor, so 95000 × 98 / 100 = 93100

  • The amount of accrued interest purchased, for the 12 days from 1 January to the settlement date on the current face, is 95000 × 0.05 × 12 / 365 = 156.16

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

  • The total consideration is gross consideration plus total fees. There are no fees in this example, so this is 93256.16:

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 current face of 95000, and a cost calculated as total consideration minus accrued interest: 93256.16 - 156.16 = 93100

  • A USD currency holding with a negative amount reflecting the total consideration paid.

Valuing your position

The basic process of valuing a portfolio containing an asset-backed ComplexBond is the same as for a vanilla fixed-rate bond.

Note the following for an MBS:

  • We recommend the BondLookupPricer pricing model. LUSID multiplies the market price by the current face (units multiplied by the latest pool factor), and adds accrued interest on the current face. The Discounting and HullWhite1F pricing models cannot value an MBS.

  • Your valuation recipe must also be able to locate pool factors.

  • LUSID can calculate Analytic/YieldToMaturity, durations and Analytic/WeightedAverageLife for an MBS.

Assessing risk

The basic process of assessing risk while valuing an asset-backed ComplexBond is the same as for a vanilla fixed-rate bond.

Monitoring the lifecycle of the instrument

An asset-backed ComplexBond is tightly integrated into LUSID's instrument event framework. To enable this for your domain, you must:

  1. Register a recipe able to locate pool factors with every portfolio holding such an instrument.

  2. Create transaction types to determine the economic impact of the transactions automatically generated by the following events.

LUSID emits all of the following events automatically; you do not need to load them. The amounts are calculated from the pool factors and other quotes you load for the assetPoolIdentifier. LUSID dates each automatically-generated transaction on the event's ex date (in our example, the 1st of the month) rather than its payment date.

Instrument event

Event emission criteria

Effect of default transaction template

Recommendations for transaction types

MbsCouponEvent

Automatically emitted by LUSID each coupon date. The coupon is calculated on the current face, using the pool factor at the start of the accrual period.

One transaction of type MbsCoupon is automatically generated for the coupon amount.

Record the coupon as cash receivable, and report it as income. See our recommendation.

MbsPrincipalEvent

Automatically emitted by LUSID each coupon date. The principal is the fall in the pool factor over the accrual period, adjusted for any write-off or deferred interest.

One transaction of type MbsPrincipal is automatically generated for the principal amount.

Record the principal as cash receivable, and reduce the current face of the holding by the same amount without changing the units. See our recommendation.

MbsPrincipalWriteOffEvent

Automatically emitted by LUSID on a coupon date if you load a PrincipalWriteOff quote for the accrual period.

One transaction of type MbsPrincipalWriteOff is automatically generated for the amount written off.

Reduce the current face of the holding with no cash, and report the loss as a flow of value. See our recommendation.

MbsInterestDeferralEvent

Automatically emitted by LUSID on a coupon date if you load an InterestDeferred quote for the accrual period. The amount is negative.

One transaction of type MbsInterestDeferral is automatically generated for the interest deferred.

Increase the current face of the holding with no cash, and report the adjustment as a flow of value. See our recommendation.

MbsInterestShortfallEvent

Automatically emitted by LUSID on a coupon date if you load an InterestShortfall quote for the accrual period.

One transaction of type MbsInterestShortfall is automatically generated for the interest not paid.

Reduce the cash receivable, and report the shortfall as a flow of value. See our recommendation.

MaturityEvent

Automatically emitted by LUSID on the maturity date of the instrument.

One transaction of type Maturity is automatically generated for all units at zero cost to reduce the holding to zero.

As for a vanilla fixed-rate bond.

Several of these transaction types use a custom side, MbsCurrentFaceCustomSide, that adjusts the current face of a holding without changing its units. See how to create it.

In our example, the pool factor falls from 0.95 on 1 January 2025 to 0.94 on 1 February, so the principal repayment paid on 20 February is 100000 × 0.01 = 1000, and the current face of the holding falls from 95000 to 94000.