---
title: "Modelling complex bonds in LUSID"
slug: "modelling-complex-bonds-in-lusid"
updated: 2026-07-01T11:59:59Z
published: 2026-07-01T11:59:59Z
canonical: "support.lusid.com/modelling-complex-bonds-in-lusid"
---

> ## Documentation Index
> Fetch the complete documentation index at: https://support.lusid.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Modelling complex bonds in LUSID

You can typically model a bond contract as an instrument of type `ComplexBond` in LUSID. [See all supported instruments](/v1/docs/supported-instruments-in-lusid).

Note LUSID has other instrument types better suited to certain kinds of bond:

| **Kind of bond** | **LUSID instrument type** | **More information** |
| --- | --- | --- |
| Fixed rate with irregular coupons (other than the first) Floating rate Fixed-to-floating rate Municipal Callable Puttable Sinkable Convertible | `ComplexBond` | Continue reading this article. |
| Mortgage-backed security (MBS) | [Read this dedicated article](/v1/docs/modelling-mortgage-backed-securities-in-lusid). |
| Fixed rate with regular coupons (only the first can be irregular) Zero coupon | `Bond` | [Read this dedicated article](/v1/docs/modelling-fixed-rate-vanilla-bonds-in-lusid). |
| Inflation-linked | `InflationLinkedBond` | [Read this dedicated article](/v1/docs/modelling-inflation-linked-bonds-in-lusid). |

## Mastering an instrument

There are [numerous tools](/v1/docs/how-do-i-master-an-instrument) you can use to master 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](/v1/docs/understanding-instrument-identifiers) of `identifiers`, and the facility to [store extra information](/v1/docs/properties) as `properties`.

Fields in the economic `definition` object are specific to `ComplexBond`. For more information on these fields, examine the [ComplexBond](https://www.lusid.com/docs/api/lusid/schemas/ComplexBond/) schema or alternatively select **ComplexBond** from the **definition** dropdown in the [UpsertInstruments](https://www.lusid.com/docs/api/lusid/endpoints/instruments/UpsertInstruments) API reference:

![](https://cdn.document360.io/d575ad81-c0ed-4980-bbd1-d59ac5c3de82/Images/Documentation/image(162).png)

### Understanding the economic definition of a complex bond

The composition of the `definition` object is highly dependent on the type of bond you want to master. Consider the following call to the [UpsertInstruments](https://www.lusid.com/docs/api/lusid/endpoints/instruments/UpsertInstruments) API; note this is actually a no-op request since the `schedules` array must be populated:

```json
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": "UKT 0 ⅜ 10/22/26",
    "identifiers": {"Figi": {"value": "BBG00ZF1T7P5"}},
    "definition": {
      "instrumentType": "ComplexBond",                   
      "calculationType": "Standard",
      "roundingConventions": [],
      "schedules": [],
      "assetBacked": "",
      "assetPoolIdentifier": ""
    }
  }
}'
```

Within the `definition` object:

- The `instrumentType` must be `ComplexBond`.
- The `calculationType` defaults to `Standard`. [More information](/v1/docs/modelling-complex-bonds-in-lusid#appendix-a-calculation-types).
- If you omit the `roundingConventions` array, no [rounding conventions](/v1/docs/how-does-lusid-handle-rounding-conventions) are applied at the instrument level.
- The `assetBacked` and `assetPoolIdentifier` fields are only applicable to [an MBS contract](/v1/docs/modelling-mortgage-backed-securities-in-lusid) and can otherwise be omitted or set to empty strings.
- The `schedules` array **must** contain at least one schedule. Depending on the type of bond, you may need to specify multiple schedules, of the same type or a mix of different ones:

| **Type of LUSID schedule** | **Type of bond** | **Rate fixing** |
| --- | --- | --- |
| **Fixed rate** | **Floating rate** | **Fixed-to-floating rate** |
| [FixedSchedule](/v1/docs/modelling-complex-bonds-in-lusid#specifying-a-fixed-schedule) | All | 1 or more | 0 | 1 or more |
| [FloatSchedule](/v1/docs/modelling-complex-bonds-in-lusid#specifying-a-floating-schedule) | All | 0 | 1 or more | 1 or more |
| [StepSchedule](/v1/docs/modelling-complex-bonds-in-lusid#specifying-a-step-schedule) | Callable | 0 or more | 0 or more | 0 or more |
| Puttable | 0 or more | 0 or more | 0 or more |
| Sinkable | 1 or more | 1 or more | 1 or more |
| Convertible | 0 or more | 0 or more | 0 or more |
| Municipal | 0 or more | 0 or more | 0 or more |
| [OptionalitySchedule](/v1/docs/modelling-complex-bonds-in-lusid#specifying-an-optionality-schedule) | Callable | 1 or more | 1 or more | 1 or more |
| Puttable | 1 or more | 1 or more | 1 or more |
| Sinkable | 0 or more | 0 or more | 0 or more |
| Convertible | 0 or more | 0 or more | 0 or more |
| Municipal | 0 or more | 0 or more | 0 or more |
  - A bond **must** have at least one `FixedSchedule` **or** at least one `FloatSchedule`, except a fixed-to-floating rate bond which **must** have at least one of each.
  - A bond **can** additionally have at least one `StepSchedule`, except a Sinkable bond which **must** have at least one.
  - A bond **can** additionally have at least one `OptionalitySchedule`, except a Callable or Puttable bond which **must** have at least one.

### Specifying a fixed schedule

You must specify a *fixed schedule* in the `schedules` array if a bond uses a fixed rate to calculate coupon payments for at least part of its lifetime.

You may need to specify more than one fixed schedule if some parameter of the bond other than the coupon rate changes, for example the payment frequency switches from semi-annually to quarterly. Note if only the coupon rate changes it may be easier to add a [step schedule](/v1/docs/modelling-complex-bonds-in-lusid#specifying-a-step-schedule) instead of another fixed schedule.

For more information on the fields, examine the [FixedSchedule](https://www.lusid.com/docs/api/lusid/schemas/FixedSchedule/) schema, or alternatively select **FixedSchedule** from the **definition > schedules** dropdown in the [UpsertInstruments](https://www.lusid.com/docs/api/lusid/endpoints/instruments/UpsertInstruments) API reference:

![](https://cdn.document360.io/d575ad81-c0ed-4980-bbd1-d59ac5c3de82/Images/Documentation/image(163).png)

Consider the following example of a single fixed schedule in the `schedules` array; note the fields specified below are the ***minimum required***:

```json
"schedules": [
  {
    "scheduleType": "FixedSchedule",
    "startDate": "2021-10-22T00:00:00+00:00",
    "maturityDate": "2028-10-22T00:00:00+00:00",
    "paymentCurrency": "GBP",
    "notional": 1,
    "couponRate": 0.025,
    "flowConventions": {
      "paymentFrequency": "6M",
      "dayCountConvention": "ActActIsma",
      "rollConvention": "22",
      "currency": "GBP",
      "paymentCalendars": [],
      "resetCalendars": []
    }
  }
]
```

Note the following:

- The `scheduleType` must be `FixedSchedule`.
- The `startDate` should be the accrual start date; that is, the date from which interest is calculated.
- The maximum `maturityDate` is 31 December 2140.
- The `couponRate` should be expressed as a decimal rather than a percentage, so a bond paying:
  - 10% should have a `couponRate` of `0.1`
  - 2.5% should have a `couponRate` of `0.025`
  - 0.375% should have a `couponRate` of `0.00375`.
- The `notional` can be any number, but we advise setting it to `1` to unitise the security and specifying the face or purchase amount on the transaction.
- The `flowConventions` object specifies all the information necessary to determine [payment date schedules](/v1/docs/flow-conventions-and-index-conventions#specifying-a-flow-convention).
- If the bond has an ex-dividend period, specify an `exDividendConfiguration` object, and within it (at a minimum) a number of `exDividendDays`. Do not specify the `exDividendDays` field on its own (that is, outside this object).
- You can set `stubType` to `ShortFront`, `ShortBack`, `LongFront`, `LongBack` or `Both`. Alternatively, you can model irregular first or last coupon periods (or both) using multiple schedules with different start and maturity dates. [See how to do this](/v1/docs/modelling-complex-bonds-in-lusid#appendix-b-modelling-irregular-first-or-last-coupon-periods).

### Specifying a floating schedule

You must specify a *floating schedule* in the `schedules` array if a bond uses a floating rate to calculate coupon payments for at least part of its lifetime.

> **Note**: You must load reference rate observations (fixings) into the LUSID Quote Store and provide a recipe that can locate these fixings in order to calculate accrued interest. [More information](/v1/docs/modelling-complex-bonds-in-lusid#specifying-a-floating-schedule).

For more information on the fields, examine the [FloatSchedule](https://www.lusid.com/docs/api/lusid/schemas/FloatSchedule/) schema, or alternatively select **FloatSchedule** from the **definition > schedules** dropdown in the [UpsertInstruments](https://www.lusid.com/docs/api/lusid/endpoints/instruments/UpsertInstruments) API reference:

![](https://cdn.document360.io/d575ad81-c0ed-4980-bbd1-d59ac5c3de82/Images/Documentation/image(165).png)

Note that floating rate bonds can be grouped into three categories based on the nature of the underlying reference index and how it is used in coupon payment calculations:

- **Category A**: The reference rate is observed once per coupon period. The observation can be made in advance of the interest rate period, or towards the end 'in arrears'. Older and LIBOR-referencing bonds are examples of this group.
- **Category B**: The reference rate is observed more than once per coupon period, up to daily for overnight indices like SONIA or SOFR, with the implication that the final coupon payment can be calculated only at the end of the period.
- **Category C**: The reference rate is a compounded index like the compounding SOFR index. These indices are published daily and they remove the necessity to capture daily fixings needed for a coupon amount calculation. The compounding indices are built from the daily SOFR fixings so the final coupon amount can be calculated only at the end of the coupon period.

> **Note**: For all three categories, the basic principle of specifying a floating rate schedule is the same, but note [these important differences](/v1/docs/modelling-complex-bonds-in-lusid#float-category-differences).

Consider the following example, of a single floating schedule for a bond in Category A above; note the fields specified below are the ***minimum required***:

```json
"schedules": [
  {
    "scheduleType": "FloatSchedule",
    "startDate": "2021-10-22T00:00:00+00:00",
    "maturityDate": "2028-10-22T00:00:00+00:00",
    "paymentCurrency": "GBP",
    "notional": 1,
    "indexConventions": {
      "fixingReference": "GBP1YBMK",
      "publicationDayLag": 1,
      "paymentTenor": "12M",
      "dayCountConvention": "ActActIsma",
      "currency": "GBP"
    },
    "flowConventions": {
      "paymentFrequency": "6M",
      "dayCountConvention": "ActActIsma",
      "rollConvention": "22",
      "currency": "GBP",
      "paymentCalendars": [],
      "resetCalendars": []
    }
  }
]
```

Note the following:

- The `scheduleType` must be `FloatSchedule`.
- The `startDate` should be the accrual start date; that is, the date from which interest is calculated.
- The maximum `maturityDate` is 31 December 2140.
- Unlike a fixed schedule, a `couponRate` is not required. Instead, you specify an `indexConvention` object that specifies all the information necessary to calculate [accrued interest amounts](/v1/docs/flow-conventions-and-index-conventions#specifying-an-index-convention) from observed rates in a reference index.
- The `spread` field defaults to `0` but you can specify a percentage as a decimal, for example `0.01` to record a spread of 1%.
- The `flowConventions` object stores all the information necessary to determine [payment date schedules](/v1/docs/flow-conventions-and-index-conventions#specifying-a-flow-convention).
- If the bond has an ex-dividend period, specify an `exDividendConfiguration` object, and within it (at a minimum) a number of `exDividendDays`. Do not specify the `exDividendDays` field on its own (that is, outside this object).
- You can set `stubType` to `ShortFront`, `ShortBack`, `LongFront`, `LongBack` or `Both`. Alternatively, you can model irregular first or last coupon periods (or both) using multiple schedules with different start and maturity dates. [See how to do this](/v1/docs/modelling-complex-bonds-in-lusid#appendix-b-modelling-irregular-first-or-last-coupon-periods).

Note the following differences between the three categories:

| [FloatSchedule](https://www.lusid.com/docs/api/lusid/schemas/FloatSchedule/) field | **Category A** | **Category B** | **Category C** |
| --- | --- | --- | --- |
| `resetConvention` | Can be `InAdvance` (the default) or `InArrears`. | Must be `InArrears`. | Must be `InArrears`. |
| `indexConventions.paymentTenor` | Can be any [tenor](/v1/docs/specifying-tenors-in-lusid). | Must be `1D`. | Must be `1D`. |
| `compounding.compoundingMethod` ([more information](/v1/docs/how-does-lusid-calculate-compound-interest)) | Do not set. | Can be `Averaging` or `Compounding`. | Must be `CompoundedIndex`. |
| `compounding.spreadCompoundingMethod` | Can be `Straight`, `Flat` or `SpreadExclusive` for `Compounding`. Do not set for `Averaging`. | Must be `SpreadExclusive`. |
| `compounding.averagingMethod` | Can be `Weighted` (the default) or `Unweighted` for `Averaging`. Do not set for `Compounding`. | Do not set. |
| `compounding.resetFrequency` | Can be any [tenor](/v1/docs/specifying-tenors-in-lusid). | Must be `1D`. |
| `compounding.calculationShiftMethod` | Can be `None`, `Lookback`, `NoShift`, `ObservationPeriodShift` or `Lockout`. | Can be `None` or `ObservationPeriodShift`. |

#### Loading fixings into the LUSID Quote Store

You must load the correct number of fixings into the Quote Store and provide a recipe that enables LUSID to locate these fixings every time an accrued interest calculation is required (which is each time you ask LUSID to value a holding in the bond or generate cashflows):

| **Category** | **Number of fixings to load** |
| --- | --- |
| A | One per coupon period. This should be at the start if `resetConvention` is set to `InAdvance`, or at the end if `InArrears`. |
| B | One per day. |
| C | Two per *calculation* period, one at the start of the coupon period and one at the effective at datetime of the valuation or cashflow generation request. Note this means if you make such a request every day you will need to load a fixing every day. |

Consider the example of a Category C bond with a quarterly coupon period and the following `fixingReference`:

```json
"indexConventions": {
   "fixingReference": "USD-1D-SOFRINDEX",
   ...
 }
```

The following call to the [UpsertQuotes](https://www.lusid.com/docs/api#operation/UpsertQuotes) API loads two fixings for this bond for Q1 2023 into a `MyFixings` quote scope (in the URL), one on the first day of this period and one on the last day:

```json
curl -X POST "https://<your-domain>.lusid.com/api/api/quotes/MyFixings"
  -H "Authorization: Bearer <your-API-access-token>"
  -H "Content-Type: application/json-patch+json"
  -d '{
    "Quote-0001": {
      "quoteId": {
        "quoteSeriesId": {
          "provider": "Lusid",
          "instrumentIdType": "RIC",
          "instrumentId": "USD-1D-SOFRINDEX",
          "quoteType": "Rate",
          "field": "mid"
        },
        "effectiveAt": "2023-01-01T17:00:00Z"
      },
      "metricValue": {
        "value": 1.111648, "unit": "rate"
      }
    },
    "Quote-0002": {
      "quoteId": {
        "quoteSeriesId": {
          "provider": "Lusid",
          "instrumentIdType": "RIC",
          "instrumentId": "USD-1D-SOFRINDEX",
          "quoteType": "Rate",
          "field": "mid"
        },
        "effectiveAt": "2023-03-31T17:00:00Z"
      },
      "metricValue": {
        "value": 1.123209, "unit": "rate"
      }
    }
  }'
```

For general information on loading fixings into the Quote Store, see [this article](/v1/docs/how-do-i-load-an-interest-rate-fixing-into-the-lusid-quote-store). Note that a fixing:

- Must have an `instrumentIdType` of `RIC`.
- Must have an `instrumentId` that is the `fixingReference` specified in the bond's index convention.
- Should be loaded into a quote scope that you only use for fixings, to avoid clashes.
- Should have a `provider` of `Lusid`, a `quoteType` of `Rate` and a `field` of `mid`, to avoid validation errors.

When you value a holding in this bond or generate cashflows, you must provide a [recipe with a market data rule](/v1/docs/recipes-locating-market-data) that is able to locate these fixings, for example:

```json
"market": {
  "marketRules": [
    {
      "key": "Quote.RIC.USD-1D-SOFRINDEX",
      "dataScope": "MyFixings",
      "supplier": "Lusid",
      "quoteType": "Rate",
      "field": "mid"
    },
    ...
  ]
},
```

Note the following:

- The `key` must have a prefix of `Quote.RIC.` and a suffix of the bond's `fixingReference`.
- The `dataScope` must match the quote scope into which fixings were loaded.
- The other fields must match their respective quote fields exactly (values are case-sensitive).

### Specifying a step schedule

You must specify at least one *step schedule* in the `schedules` array to record notional change for a Sinkable bond, in addition to its `FixedSchedule` and/or `FloatSchedule`. You can optionally specify a step schedule for any other type of bond, perhaps if the coupon rate or spread changes during its lifetime.

For more information on the fields, examine the [StepSchedule](https://www.lusid.com/docs/api/lusid/schemas/StepSchedule/) schema, or alternatively select **StepSchedule** from the **definition > schedules** dropdown in the [UpsertInstruments](https://www.lusid.com/docs/api/lusid/endpoints/instruments/UpsertInstruments) API reference:

![](https://cdn.document360.io/d575ad81-c0ed-4980-bbd1-d59ac5c3de82/Images/Documentation/image(166).png)

Consider the example of a Sinakable bond whose original notional value of 1000 changes four times according to the following published schedule:

- 800 on 15 November 2022
- 500 on 15 November 2023
- 600 on 15 November 2024
- 200 on 17 November 2025.

One step is required in the schedule for each date on which the notional value changes.

You can capture notional change using one of four 'level types'. The one to choose depends largely on the information available to you and the convenience of translating it into LUSID:

| `LevelType` field | **How to calculate step quantities** | **Step quantities for this example** | **How LUSID calculates notionals from step quantities** |
| --- | --- | --- | --- |
| **15 Nov 2022** | **15 Nov 2023** | **15 Nov 2024** | **17 Nov 2025** |
| `Absolute` | step quantity = new notional | 800 | 500 | 600 | 200 | new notional = step quantity |
| `AbsoluteShift` | step quantity = current notional - new notional | 200 | 300 | -100 | 400 | new notional = current notional - step quantity |
| `Percentage` | step quantity = ((current notional - new notional) / original notional) * 100 | 20 | 30 | -10 | 40 | new notional = current notional - (step quantity / 100) * original notional |
| `AbsolutePercentage` | step quantity = (new notional / original notional) * 100 | 80 | 50 | 60 | 20 | new notional = (step quantity / 100) * original notional |

A step schedule to capture the notional change in this example using a `LevelType` of `Absolute` would look like this:

```json
"schedules": [
  {
    "scheduleType": "StepSchedule",
    "levelType": "Absolute",
    "stepScheduleType": "Notional",
    "steps": [
      {"date": "2022-11-15", "quantity": 800},
      {"date": "2023-11-15", "quantity": 500},
      {"date": "2024-11-15", "quantity": 600},
      {"date": "2025-11-17", "quantity": 400}
    ]
  }
]
```

Note the following:

- The `scheduleType` must be `StepSchedule`.
- The `stepScheduleType` in this case is `Notional` but it can be `Coupon` or `Spread`.
- The `steps` array must contain one step object (consisting of a `date` and `quantity`) for each change to the notional; that is, one fewer than the total number of coupon periods with different notional amounts.

### Specifying an optionality schedule

You must specify at least one *optionality schedule* in the `schedules` array to capture exercise dates for a Callable or Puttable bond, in addition to its `FixedSchedule` and/or `FloatSchedule`. You can optionally specify an optionality schedule for any other type of bond.

For more information on the fields, examine the [OptionalitySchedule](https://www.lusid.com/docs/api/lusid/schemas/OptionalitySchedule/) schema, or alternatively select **OptionalitySchedule** from the **definition > schedules** dropdown in the [UpsertInstruments](https://www.lusid.com/docs/api/lusid/endpoints/instruments/UpsertInstruments) API reference:

![](https://cdn.document360.io/d575ad81-c0ed-4980-bbd1-d59ac5c3de82/Images/Documentation/image(167).png)

*More information coming soon*

## Booking a transaction to establish a position

The process of booking a transaction in a `ComplexBond` is the same as for a [vanilla fixed-rate bond](/v1/docs/modelling-fixed-rate-vanilla-bonds-in-lusid#booking-a-transaction-to-establish-a-position).

## Valuing your position

The basic process of valuing a portfolio containing a `ComplexBond` is the same as for a [vanilla fixed-rate bond](/v1/docs/modelling-fixed-rate-vanilla-bonds-in-lusid#valuing-your-position). We recommend switching to the dedicated `BondLookupPricer` pricing model in your recipe.

## Monitoring the lifecycle of the instrument

The process of handling instrument events for a `ComplexBond` is the same as for a [vanilla fixed-rate bond](/v1/docs/modelling-fixed-rate-vanilla-bonds-in-lusid#monitoring-the-lifecycle-of-the-instrument).

## Appendix A: Understanding calculation types

By default, a `ComplexBond` has a `calculationType` of `Standard`, which means a coupon is calculated as `rate * units / frequency`. You can change this to:

- `DayCountCoupon` to calculate a coupon as `rate * units * yearfraction(accrualStart, accrualEnd)`.
- `BrazilFixedCoupon` to apply the Brazilian compounding rule to the rate and pay semi-annually (for example, if the rate is 10% on the instrument then this is converted to semi-annual payments that compound annually to 10%, instead of 5% twice). The formula is `adjustedRate = (1+r)^1/2 - 1`, so 10% is approximately 4.88% semi-annually.

Please do not specify `NoCalculationFloater` or `StandardWithCappedAccruedInterest` at this time.

## Appendix B: Modelling irregular first or last coupon periods

Consider the example of a fixed-rate bond with a coupon paid on the 20th of each month but an irregular first coupon period of fifteen days. To model this, we can set two `FixedSchedule` objects in the `schedules` array; note they **must be specified in this order**:

1. From the start date of the bond (5 January 2025) to the first coupon date (20 January 2025). Note the `maturityDate` is set to the first coupon date and the flow convention has a `paymentFrequency` of `1T`
2. From the first coupon date (20 January 2025) to the maturity date of the bond (20 December 2026). Note the `startDate` is set to the first coupon date and the flow convention has a `paymentFrequency` of `1M`

```json
"schedules": [
  {
    "scheduleType": "FixedSchedule",
    "startDate": "2024-01-05T00:00:00+00:00",
    "maturityDate": "2024-01-20T00:00:00+00:00",
    "paymentCurrency": "GBP",
    "notional": 1,
    "couponRate": 0.05,
    "flowConventions": {
      "paymentFrequency": "1T",
      "dayCountConvention": "Actual365",
      "rollConvention": "20",
      "currency": "GBP",
      "paymentCalendars": [],
      "resetCalendars": []
    }
  },
  {
    "scheduleType": "FixedSchedule",
    "startDate": "2024-01-20T00:00:00+00:00",
    "maturityDate": "2026-12-20T00:00:00+00:00",
    "paymentCurrency": "GBP",
    "notional": 1,
    "couponRate": 0.05,
    "flowConventions": {
      "paymentFrequency": "1M",
      "dayCountConvention": "Actual365",
      "rollConvention": "20",
      "currency": "GBP",
      "paymentCalendars": [],
      "resetCalendars": []
    }
  }
]
```
