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:
.png?sv=2026-02-06&spr=https&st=2026-10-14T23%3A16%3A19Z&se=2026-10-14T23%3A45%3A19Z&sr=c&sp=r&sig=MUnH15xHinNyMNZ7g%2FtE0MTpfI%2BlgWF2XBNAYyh8M5k%3D)
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
identifiersfield uniquely identifies the instrument using aClientInternalidentifier.In the economic
definitionobject:The
instrumentTypemust beComplexBond.The
assetBackedfield must betrueto distinguish an MBS contract from other non-backed instruments mastered as typeComplexBond.The
assetPoolIdentifieris 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
notionalshould be unitised (set to1) and the amount bought or sold specified on the transaction.The
couponRateshould be expressed as a decimal rather than a percentage, so a bond paying:10% should have a
couponRateof0.15.0% should have a
couponRateof0.050.375% should have a
couponRateof0.00375and so on.
The
flowConventionsobject stores all the information necessary to determine coupon periods, accrued interest amounts and payment dates:The
paymentFrequencycan be any tenor, in this case1Mto signify once a month. Supported day count conventions.The
rollConventionfield should count back from maturity, so in this case20for a bond maturing on 20 November, which means the coupon dates are the 20th of every month. We also recommend setting abusinessDayConventionto 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
accrualDateAdjustmentfield is optional and defaults toAdjusted. This is suitable for European bonds but for US bonds we recommendUnadjusted.The
settleDaysfield in a flow convention is deprecated, andresetDaysis ignored for fixed schedules. Thescopeandcodefields can be ignored unless you are loading a flow convention from a library.
The
exDividendConfigurationobject sets the payment delay typical of agency MBS:The
exDividendDaysfield 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 is20 - 1 = 19. Note: Do not specify theexDividendDaysfield on its own (that is, outside this object).The
applyThirty360PayDelayfield should be set totrue. LUSID then ends each accrual periodexDividendDaysbefore 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
stubTypeobject, the first and last coupon periods are expected to be regular. See how to model irregular periods.The
tradingConventions.priceScaleFactorfield is set to 100 so market prices can be entered as a percentage of par.If you do not specify a
roundingConventionsobject, 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 |
|---|---|
|
|
The 1st of each month thereafter | The pool factor published for that month, for example |
|
|
Note the following:
LUSID does not assume a pool factor of
1at 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
instrumentIdmust be theassetPoolIdentifierspecified in the instrument definition, in this caseMyMBSMarketDataIdentifier. LUSID does not use the instrument's own identifiers, unless you set one of them to the same value.The
instrumentIdTypecan beClientInternal,IsinorLusidInstrumentId. Choose one and use it consistently, since your recipe must match it.The
quoteTypemust bePoolFactor.The
provideris set toLusidand thefieldtomid, to avoid validation errors.The
metricValue.valueis a fraction of the original principal (0.95, not95). If your data supplier quotes pool factors as percentages, set ascaleFactorof100on each quote.The
effectiveAtmust 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.
| Records |
|---|---|
| Principal written off as a credit loss rather than repaid. |
| Interest due but capitalised into the outstanding principal. |
| 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
keyshould be constructed as follows:Quote.<instrumentIdType>.<assetPoolIdentifier>. Use*in place of theassetPoolIdentifierto match all your MBS.The
quoteTypemust bePoolFactor. A rule with aquoteTypeofPricenever locates a pool factor.The
dataScopemust 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
quoteIntervalsets 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. AquoteIntervalof1Mmeans 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 of0for that month.If you load
PrincipalWriteOff,InterestDeferredorInterestShortfallquotes, add a similar rule for each, with thequoteTypeset accordingly. Do not set aquoteIntervalon 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
MarketResolverFailureerror 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
instrumentIdentifiersfield uses theClientInternalidentifier to resolve the transaction to the correct MBS instrument (but it could use the LUID).The
typefield invokes a customBuyMBStransaction type to confer a particular economic impact (see below).The
unitsfield specifies the original face amount. LUSID multiplies it by the pool factor to calculate the current face.The
transactionPriceobject 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
totalConsiderationobject:Sets the settlement currency to
USD.Specifies a cost of
0to 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:GrossConsiderationcalculation first calculates the current face asunits * notional * pool factor, using the pool factor in force on the settlement date, and stores the result in theTransaction/default/CurrentFacesystem 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 theTransaction/default/GrossConsiderationsystem property.The
Txn:BondInterestcalculation automatically calculates the amount of bond interest bought or sold on the current face, and stores the result in theTransaction/default/BondInterestsystem property, available for use in sides.The
DeriveTotalConsiderationcalculation 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 thetotalConsideration.amountof the transaction.The
StockMovementuses the built-inSide1side to establish a holding in the instrument with the specified number of units at a cost derived from the total consideration.Side1also records the current face of the holding, taken fromTransaction/default/CurrentFace.The
CashCommitmentmovement uses the built-inSide2to decrease the instrument currency holding by the total consideration.The
Carrymovement uses a customBondInterestside 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:GrossConsiderationcalculation, 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 aTransaction/default/CurrentFaceproperty on the transaction, or the pool factor in aTransaction/default/CurrentPoolFactorproperty, 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 of0.95for 1 January 2025 that is in force on the settlement date, so100000 × 0.95 = 95000The gross transaction amount is
current face * price / priceScaleFactor, so95000 × 98 / 100 = 93100The 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.16Since the price is clean, the gross consideration is the gross transaction amount plus accrued interest:
93100 + 156.16 = 93256.16The 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 = 93100A 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
BondLookupPricerpricing 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. TheDiscountingandHullWhite1Fpricing models cannot value an MBS.Your valuation recipe must also be able to locate pool factors.
LUSID can calculate
Analytic/YieldToMaturity, durations andAnalytic/WeightedAverageLifefor 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:
Register a recipe able to locate pool factors with every portfolio holding such an instrument.
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 |
|---|---|---|---|
| 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 | Record the coupon as cash receivable, and report it as income. See our recommendation. |
| 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 | 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. |
| Automatically emitted by LUSID on a coupon date if you load a | One transaction of type | Reduce the current face of the holding with no cash, and report the loss as a flow of value. See our recommendation. |
| Automatically emitted by LUSID on a coupon date if you load an | One transaction of type | Increase the current face of the holding with no cash, and report the adjustment as a flow of value. See our recommendation. |
| Automatically emitted by LUSID on a coupon date if you load an | One transaction of type | Reduce the cash receivable, and report the shortfall as a flow of value. See our recommendation. |
| Automatically emitted by LUSID on the maturity date of the instrument. | One transaction of type | 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.