> For the complete documentation index, see [llms.txt](https://bluegamma.io/documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bluegamma.io/documentation/methodology/central-bank-probabilities.md).

# Central Bank Decision Probabilities

How BlueGamma derives market-implied hike, hold and cut probabilities for scheduled central bank decisions, as served by `GET /v1/central_bank_probabilities` and `GET /v1/central_bank_probability_matrix` in the [Interest Rate API](https://www.bluegamma.io/interest-rate-api).

Covered banks: Federal Reserve (USD), European Central Bank (EUR), Bank of England (GBP), Bank of Japan (JPY), Bank of Canada (CAD), Reserve Bank of Australia (AUD), Swiss National Bank (CHF), Reserve Bank of New Zealand (NZD).

***

## Summary of the construction

1. Take the scheduled decision calendar for the currency.
2. Read one overnight index swap forward per inter-meeting window off the currency's own OIS curve.
3. Convert those forwards into policy-rate space with a single additive spread.
4. Difference the resulting path to get the expected incremental move at each decision.
5. Split each expected move across the two adjacent grid levels that bracket it.
6. Convolve those per-meeting distributions forward to get, at every decision, a full distribution over policy rate levels.

Steps 4 to 6 are pure arithmetic; steps 1 to 3 are the market-data work. The per-meeting cut/hold/hike table and the expected rate path are both read back out of the matrix, so the three outputs cannot disagree with one another.

The result looks like this (illustrative USD excerpt, values in percent, one column per decision, each column summing to 100):

| Rate level        | 16 Sep | 28 Oct | 9 Dec | ... | 8 Dec 2027 (final published) |
| ----------------- | ------ | ------ | ----- | --- | ---------------------------- |
| 4.375             | -      | -      | 8.5   | ... | null                         |
| 4.125             | -      | 15.4   | 37.3  | ... | null                         |
| 3.875             | 60.4   | 55.1   | 41.0  | ... | null                         |
| 3.625 *(current)* | 39.6   | 29.5   | 13.2  | ... | null                         |

A dash is a genuine zero: that level is unreachable by that decision. The null column is different, and is explained in decision 2.

***

## 1. The output is a recursive convolution, not a per-meeting two-way split

**Decision.** Each decision gets a conditional distribution over moves. The distribution of the *level* at decision *i* is the level distribution at decision *i-1* convolved with the move distribution at decision *i*. The result is a matrix whose columns are decisions, whose rows are 25bp-spaced rate levels, and whose every column sums to 1.

**Rationale.** A per-meeting two-way split ("62% chance of a hike in October") answers a different question from the one a rates desk actually asks, which is "where is the policy rate in October". Only the second question aggregates across meetings, and only the convolution produces it. It also produces the observable signature of a correct implementation: the distribution starts on two levels at the first decision and widens by at most one level per decision thereafter, so by the fourth decision it spans five levels and by the eighth it spans up to nine.

**A worked convolution.** Suppose the first decision prices a 4.4% chance of a 25bp hike and the second, conditionally, an 18.8% chance. The second column is every path weighted through:

| Level after decision 2 | Paths                             | Probability                          |
| ---------------------- | --------------------------------- | ------------------------------------ |
| +50bp                  | hike, hike                        | 4.4% x 18.8% = 0.8%                  |
| +25bp                  | hike then hold, or hold then hike | 4.4% x 81.2% + 95.6% x 18.8% = 21.6% |
| unchanged              | hold, hold                        | 95.6% x 81.2% = 77.6%                |

Each further decision repeats this step, which is why the distribution fans out and stays humped in the middle: reaching an extreme level requires every decision to move the same way.

**Assumption made explicit.** The conditional move distribution at each meeting is assumed independent of the path taken to reach it. This is the standard simplification, and it is what makes one forward per meeting sufficient to pin the entire matrix. It means the model does not capture, for example, that a bank which has already hiked twice is more likely to hike again. Capturing that would require meeting-level option data (caplet or swaption skew across the strip), not the swap curve, and is out of scope for this deliverable.

***

## 2. Expected moves come from meeting-dated forward windows

**Decision.** The rate the market implies for the period after decision *i* is the simple annualised OIS forward over the window `[first business day after decision i, first business day after decision i+1)`.

```
today           decision 1          decision 2          decision 3
  │                 │                   │                   │
  ▼                 ▼                   ▼                   ▼
  ├─────────────────┼───────────────────┼───────────────────┤
   pre-decision        window 1            window 2
   (current policy)   (rate set at d1)    (rate set at d2)
```

One OIS forward is read over each window. The expected move at decision 1 is the window 1 forward minus the pre-decision forward; at decision 2, window 2 minus window 1; and so on. Each decision's expected move is therefore isolated from its neighbours', whatever the meeting spacing.

**Rationale.** That window is close to the span over which the rate set at decision *i* is in force: for most of the eight banks a policy change bites from the business day after the announcement and stays in force until the day after the next announcement. Any other window mixes two policy settings together.

**One deliberate simplification.** The ECB is the exception: a change to its deposit facility rate takes effect at the start of the next reserve maintenance period, typically around six days after the announcement rather than the next business day. We keep the uniform one-business-day convention for all eight banks anyway. The difference is worth under half a basis point on the euro strip, six days of the old rate inside a seven-week window, which is below the dead zone and far below the allocation error in limitation 1. Uniformity is worth more here than a per-bank effective-date table that would have to be maintained and would move nothing a consumer can see.

**Consequence for the last meeting.** The final decision a central bank has published has no successor, so its window has no closing date and it cannot be priced. It is left out rather than given a guessed window: inventing a closing date would put a number in the last column that is an artefact of the guess, not a market price.

It is still a real scheduled decision, so it is published rather than hidden -- as a **null column**. It appears in `meetings` in chronological position with its known facts filled in (date, days until, provisional flag) and every derived field null, and every matrix row carries a matching cell with a null probability.

Null is a different statement from zero, and the distinction is load-bearing. Null means "this decision is not yet priceable"; it resolves itself the moment the bank schedules the decision after it. A zero inside a priced column means "that level is unreachable by that decision", a real market statement, never missing data. Rendering one as the other would misstate the market.

The derived fields go null together, never partially, so a consumer can test one and trust the rest. At most one null column appears, and only when that final published decision falls inside the horizon; decisions beyond the horizon are simply absent, being out of scope rather than unpriceable.

***

## 3. The first step is measured against the pre-decision window, not against spot

**Decision.** The reference rate for the first decision is the OIS forward from the curve reference date to the first business day after the first decision. The expected move at the first decision is the difference between the first inter-meeting forward and this pre-decision forward.

**Rationale.** The pre-decision window is the only window in the strip that contains no decision at all, so the forward over it is the market's view of the average overnight fixing under the *current* policy setting. Comparing forward to forward keeps the first step in the same units as every later step, and means no level conversion enters the move calculation.

**Edge case.** When the first decision is fewer than five calendar days away the window is too short to read cleanly, and the curve's spot overnight rate is substituted as the reference instead. The substitution changes only how the reference level is read; every step of the construction downstream is unchanged.

***

## 4. One additive spread converts OIS space into policy-rate space

**Decision.** Probabilities are computed in policy-rate space. A single fixing-to-policy spread `b` is measured once, as

```
b = OIS forward over the pre-decision window  -  current policy rate
```

and every inter-meeting forward is shifted by `-b` before differencing. The spread is an internal step of the calculation, not a published figure: every rate on the responses, the current level, the grid levels, the per-meeting expected rates, is already a policy rate, never a fixing.

**Rationale.** The overnight fixing and the policy rate are not the same number. The sterling overnight rate fixes a couple of basis points below Bank Rate; the euro overnight rate fixes several basis points below the deposit facility rate; the Canadian and US overnight fixings print above their respective policy anchors. Feeding raw OIS forwards into a grid anchored on the policy rate would shift the whole distribution by that spread, which at typical magnitudes of 2 to 7bp is up to a quarter of a grid step, and would show as a permanent tilt in the hold probability.

**Why the spread is measured, not hardcoded.** Corridor spreads move with collateral conditions, reserve abundance and quarter-end effects. Measuring the spread from the same curve that produces the forwards means any front-end distortion is common to both sides of the difference and cancels. It also means the calculation needs no per-currency spread table to maintain.

**Why the spread is held constant across the horizon.** Extrapolating a term structure of corridor spreads from a swap curve is not identified: the curve prices the sum of policy expectations and the spread, not the two separately. Holding the spread flat is the assumption that adds no unsupported information. It is also self-correcting in the direction that matters, because the matrix is built from *differences* of forwards, so a constant spread drops out of every expected move except through the level anchor.

***

## 5. USD levels are target-range mids; other currencies are plain policy rates

**Decision.** The Federal Reserve's published policy rate is the top of its target range. We subtract half the 25bp range width to get the mid, use the mid as the grid anchor, and quote every USD rate level as a mid (3.375, 3.625, 3.875, ...). The range bounds are returned alongside as `target_range`. Every other covered bank publishes a single policy rate, which is used directly.

**Rationale.** The overnight fixing trades inside the range, not at its top, so anchoring on the top would put the whole distribution roughly 12.5bp off. Mids are also what the market quotes when it discusses the level. `target_range` is returned so a consumer can render "3.50-3.75%" alongside "mid 3.625%".

***

## 6. A 25bp grid for all eight currencies

**Decision.** One standard move is 25bp for every covered bank. The grid spacing is published on the matrix response as `step_bp`, so a consumer never has to infer it from the level differences, and it is per-currency configuration.

**Rationale.** Every one of the eight has moved exclusively in whole 25bp multiples over the current cycle. Fifty-basis-point moves, which the Reserve Bank of New Zealand and the Swiss National Bank have both made recently, are representable as two grid steps and need no separate treatment; the convolution handles them through the multi-move split in decision 9.

***

## 7. The Bank of Japan is on the 25bp grid, deliberately

**Decision.** JPY uses the same 25bp grid as the other seven.

**Rationale.** The Bank of Japan used sub-25bp increments while normalising out of negative rates: -0.10 to 0.10 was a 20bp move and 0.10 to 0.25 a 15bp move. Every move since January 2025 has been a clean 25bp, and the policy rate is now at 1.00%, well clear of the region where the smaller increments were used. A 25bp grid is the market convention at this level.

**Effect if this is wrong.** The expected rate path is unaffected: it is the probability-weighted level, and the weighting is constructed to reproduce the implied move exactly whatever the grid. Only the probability decomposition changes. If the Bank of Japan were to revert to 15bp moves, a genuine 15bp expected move would be reported as a 60% chance of a 25bp hike rather than a near-certain 15bp hike. The grid spacing is per-currency configuration, so switching JPY is a one-line change and requires no other code to move.

***

## 8. Thin markets: a 1bp dead zone, tapered, but no aggressive clamping

**Decision.** Expected moves below 1bp are suppressed towards zero. Suppression is a linear taper rather than a cut: a move is scaled by `min(1, |move| / 1bp)`, so it passes through unchanged at or above 1bp, shrinks quadratically below it, and reaches zero only at zero. Nothing at or above the threshold is touched. Every published figure, the matrix, the cumulative table, the expected path -- is computed from the tapered moves, so they stay mutually consistent with one another and with the published implied changes. Only the tapered figure is published; there is no second, unadjusted number on the response to reconcile against.

**Why a taper and not a cut.** A hard cut put a discontinuity in the published number exactly at the threshold. A currency sitting near 1bp flipped between a 0% and a 4% probability of a move on ordinary intraday curve refreshes, seen live on AUD and CHF, which reads as instability in the market rather than in the model. The taper removes the step while keeping the philosophy: sub- resolution noise still goes to zero, it just gets there smoothly. It costs a small downward bias inside the band, which is the conservative direction for a number we are declaring to be noise.

The currencies whose OIS market is thinnest are CHF, JPY and NZD. Their output is built exactly the same way as the other five; the difference is that a given implied move there rests on fewer quoted instruments, so it should be read with wider tolerance and is more likely to sit near the dead zone.

**Rationale.** Sub-basis-point differences between adjacent inter-meeting forwards are below the resolution of the instruments the curve is built from and are interpolation artefacts, not information. Publishing them as, say, a 2% probability of a hike would be false precision.

Clamping harder was considered and rejected. A 5bp expected move is a genuine 20% probability of a move and is exactly the kind of signal a user of this endpoint is paying for; suppressing it because the currency is thin would misrepresent the market. Thin markets are handled by telling the consumer the market is thin, not by editing the number.

The Swiss National Bank's quarterly cadence is worth calling out separately: six scheduled assessments over a two-year calendar means the matrix is only five or six columns wide, and each inter-meeting window is about three months long. Longer windows are *better* conditioned, not worse, the noise problem in CHF is the flatness of the curve near zero, not the meeting spacing.

***

## 9. Moves larger than one step are split across adjacent grid levels

**Decision.** For an expected move `d` on a grid of step `s`, let `k = d / s` and `n = floor(k)`. The conditional move distribution is

```
P(move = n·s)     = 1 - (k - n)
P(move = (n+1)·s) =     (k - n)
```

**Rationale.** This is the unique distribution supported on the two grid points bracketing the expected move whose mean equals that expected move. Minimum support is the parsimonious choice: it adds no variance the market data does not imply.

Worked examples, on a 25bp grid:

| Expected move | Reads as                     |
| ------------- | ---------------------------- |
| +32bp         | 72% one hike, 28% two hikes  |
| +10bp         | 40% one hike, 60% hold       |
| -10bp         | 40% one cut, 60% hold        |
| -60bp         | 60% three cuts, 40% two cuts |
| +25bp         | 100% one hike                |

The formula is sign-symmetric because the floor is taken in signed space, so cuts and hikes are handled by the same branch of code with no special-casing.

***

## 10. Per-meeting cut/hold/hike are cumulative against today's level

**Decision.** In the `meetings` block, `hike_probability` for a given decision is the sum of every matrix cell **above** the current policy level in that column, `cut_probability` the sum of every cell below, and `hold_probability` the single cell at the current level.

**Rationale.** This is the question the numbers are read as answering: "what is the chance rates are higher than today by March". It is also the only aggregation under which the per-meeting table and the matrix are the same object, which makes the whole response internally checkable, a consumer can verify our table against our matrix with a column sum.

The consequence, which is intended, is that hold probability decays monotonically along the strip even if no individual meeting is likely to move: holding at the current level through eight decisions requires eight consecutive holds.

For the first decision the cumulative and conditional readings coincide, which is why the headline `GET /v1/central_bank_probabilities` figures are exactly the first matrix column aggregated.

***

## 11. Horizon, and what falls outside it

**Decision.** Eighteen months by default, configurable from 1 to 36 months. A decision is priced if it falls inside the horizon *and* a subsequent decision exists in the calendar to close its window.

**Rationale.** Eighteen months covers the part of the strip that trades. Beyond it, calendars are unpublished for most banks and, more importantly, the curve stops being informative at meeting granularity, see the limitation below.

***

## 12. Meeting dates come from a maintained calendar, not from a news feed

**Decision.** Scheduled decision dates come from a maintained calendar of each bank's own published schedule, refreshed from source and read fresh on every request. Every read is bounded below by the *valuation date* of the calculation rather than by today, so a request for a past valuation time reproduces the calendar as it applied then rather than as it stands now.

Any date the publishing bank has not finalised is flagged as provisional -- `provisional_meeting_date` on the next-decision response, `provisional` on each row of the matrix response. Provisional dates are still returned and still priced, because the market prices them regardless.

**Rationale.** The meeting-dated construction is only as good as the dates. A wrong date does not degrade gracefully: it silently mis-windows two consecutive forwards and moves probability between two columns, with no error to notice. The previous approach, reconciling a generated news table with a third-party economic calendar, resolves only the next decision, cannot supply a forward strip at all, and has no audit trail back to the bank. Note that these endpoints use the maintained calendar; the existing website and in-app callers are unchanged.

**The flag is data-dependent, so read it as a positive signal only.** A date is flagged when the calendar we hold records it as unconfirmed. Coverage of that marking varies by bank and improves over time, so an unflagged date means "we have no indication this one is unconfirmed", never "this date is confirmed".

Banks that publish unconfirmed forward dates today include the Federal Reserve (tentative until confirmed at the preceding meeting), the Bank of England (which labels its second-year dates provisional), and the Reserve Bank of New Zealand (whose decision cadence rises from seven to eight a year and which has already revised one published date). Whether any individual date carries the flag depends on what the calendar records for it.

***

## 13. Curve source

**Decision.** These endpoints price off the same OIS curve construction that serves `GET /v1/forward_curve` and `POST /v1/forward_rates` for the same index.

**Rationale.** A consumer must be able to reconcile the two. Pulling the inter-meeting forward from `/v1/forward_curve` for the same two dates should reproduce, to rounding, the forward this endpoint used. Any other curve choice would make the endpoints mutually unverifiable.

***

## Data cadence per currency

Seven of the eight currencies price off curves that refresh through the trading day. NZD's underlying curve updates once per day at the prior close, so its timestamp can trail the valuation time by up to a day. Each response declares this per currency as observation\_basis ("intraday" or "eod"), so a consumer can caption per-currency freshness correctly. Staleness itself is always the difference between valuation\_time and timestamp; the basis explains the expected size of that gap. A day-behind NZD timestamp is the declared cadence at work, not a fault.

***

## Known limitations

1. **The curve carries no meeting-dated instruments, so the per-meeting split is a smooth allocation.** This is the most important limitation and it applies at every tenor, not only the long end. The OIS curves these numbers come from are bootstrapped from quoted swap tenors. Walking the forward curve day by day through a scheduled decision date shows no step at that date, the interpolation carries the expected move smoothly across it, because nothing in the curve's inputs knows a meeting happens there.

   What this means in practice:

   * The **cumulative path** is genuinely market-implied. The total expected change between today and any future date is what the curve prices, and that is a real number.
   * The **per-meeting split** is not separately identified. Differencing the forward across window boundaries allocates that cumulative move across decisions in proportion to where the interpolation happens to put it, not according to any meeting-by-meeting market view.

   Sizing the error: perturbing the window boundaries moves the front meeting's implied change by roughly +/-2bp, which is about +/-8 percentage points of probability at a 25bp grid. That band widens with tenor as the pillar spacing coarsens. Read a single column's probability as accurate to within several percentage points, and read the cumulative path and the expected rate as the numbers that hold.

   A construction that did identify the split would need meeting-dated instruments in the bootstrap, meeting-dated OIS or the relevant futures strip, which is a curve-building change, not a change to this model.
2. **Physical measure versus risk-neutral measure.** These are risk-neutral probabilities implied by traded rates. They embed whatever term premium is in the curve and are not forecasts.
3. **Unscheduled decisions are not represented.** Inter-meeting cuts happen, and the market prices some probability of them into the pre-decision window. This construction attributes that entirely to the next scheduled decision.
4. **Path independence.** As set out in decision 1, the conditional move distribution at each meeting does not depend on the path.
5. **Range width is assumed constant for USD.** The mid is derived by subtracting half of a 25bp range width. A change in the width of the target range would need the configuration updated.

***

## Internal consistency guarantees

These hold by construction and are covered by unit tests. They are scoped to priced columns; a null column (see decision 2) carries no probability mass and takes no part in any of them.

* Every cell is self-describing. Each row of `matrix` carries its own `rate_level` and a list of `{meeting_date, probability}` cells, so a cell identifies both of its coordinates without reference to any other array. No separate levels or dates arrays are published because nothing needs them.
* Cells and columns correspond one for one. Every row carries exactly one cell per priced decision, in the same chronological order as `meetings`, with `meeting_date` values matching `meetings[].meeting_date` exactly. This is a consistency property a consumer can check, not something they have to rely on to read the response.
* Every matrix column sums to 1, to within display rounding.
* The next-decision figures from `GET /v1/central_bank_probabilities` equal the first matrix column aggregated against the current level.
* For each meeting, `hike + hold + cut = 1`, with `hike` the sum of matrix cells above the current level, `hold` the cell at it, `cut` the sum below.
* `meetings[i].expected_rate` equals the current policy rate plus the cumulative sum of `meetings[..i].implied_change_bp`, the implied changes are incremental, so the running total is the consumer's own cross-check, and equals the probability-weighted level of matrix column `i` to within the two-decimal rounding of the published cells (under a tenth of a basis point on the widest columns).
* The support of column `i` is at most `i + 2` levels, with columns indexed from zero as they are in the response arrays: the distribution starts on two levels at the first decision and widens by at most one level per decision after that.

Every figure needed to verify these is on the response. The intermediate quantities the construction passes through, the fixing-to-policy spread, the inter-meeting forwards themselves, the pre-dead-zone moves, are not published, because none of them is needed to reproduce or check the published numbers, and each would invite a reconciliation the API does not owe. The inter-meeting forward for any two dates can be pulled directly from the forward curve endpoint (decision 13) by anyone who wants to audit the input.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://bluegamma.io/documentation/methodology/central-bank-probabilities.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
