Skip to content
All library documents

Diagnosing Bloomberg and QuantLib USD OIS Discount Curve Differences

Article Quant Q&A · Author: scorpio

Summary

The document investigates why a QuantLib USD overnight indexed swap curve built from Bloomberg par rates reproduces Bloomberg discount factors at short maturities but diverges slightly at longer tenors. The example constructs OIS rate helpers and a log-linear discount curve, then compares its outputs with Bloomberg values. The reported differences grow gradually with maturity, while the short end agrees to the displayed precision.

The accepted explanation suggests that Bloomberg may use input rates with more precision than it displays, or that its continuous step-forward interpolation differs from log-linear interpolation of discount factors. A cross-check against Rateslib is cited: it agrees closely with QuantLib when both use the same market data and interpolation approach. The discussion therefore identifies Bloomberg's conventions or hidden precision as the likely source of the mismatch, rather than a clear error in QuantLib. The evidence is suggestive rather than conclusive because the terminal settings and full-precision inputs are unavailable, so it does not establish the exact cause or a definitive adjustment to make the curves match.

Key ideas

  • A log-linear discount curve can match Bloomberg closely at short OIS tenors yet differ slightly at longer maturities.
  • Displayed market rates may be rounded, while Bloomberg calculations may use more precise inputs.
  • Different interpolation conventions can produce different discount factors from the same swap rates.
  • Agreement between QuantLib and Rateslib under the same inputs supports checking Bloomberg settings and conventions.

Tags

Full text
# Quantlib OIS USD discount rates don't match Bloomberg discount rates


# Quantlib OIS USD discount rates don't match Bloomberg discount rates












The Bloomberg USD OIS discount factors for 2024-03-01 do not match the values calculated using Quantlib beyond the 18M tenor. What do I need to do get them to match?

Sorry, I am unable to paste a screenshot from the Bloomberg terminal.

My code is as follows:

```
import typing
import datetime
import math
import pandas
import QuantLib

if __name__ == "__main__":
    day_counter: QuantLib.DayCounter = QuantLib.Actual360()
    calendar: QuantLib.Calendar = QuantLib.UnitedStates(QuantLib.UnitedStates.FederalReserve)
    reference_date: QuantLib.Date = QuantLib.Date(1, 3, 2024)
    settlement_date: QuantLib.Date = calendar.advance(reference_date, QuantLib.Period(2, QuantLib.Days))
    QuantLib.Settings.instance().evaluationDate = reference_date
    overnightIndex: QuantLib.FedFunds = QuantLib.FedFunds()

    # Bloomberg USD OIS Swap rates
    swaps: typing.Mapping[QuantLib.Period, float] = {
        QuantLib.Period("1W"): 5.33000,
        QuantLib.Period("2W"): 5.33260,
        QuantLib.Period("3W"): 5.33300,
        QuantLib.Period("1M"): 5.33535,
        QuantLib.Period("2M"): 5.34110,
        QuantLib.Period("3M"): 5.33440,
        QuantLib.Period("4M"): 5.30550,
        QuantLib.Period("5M"): 5.27900,
        QuantLib.Period("6M"): 5.24035,
        QuantLib.Period("9M"): 5.12030,
        QuantLib.Period("1Y"): 4.98700,
        QuantLib.Period("18M"): 4.65575,
        QuantLib.Period("2Y"): 4.43105,
        QuantLib.Period("3Y"): 4.13593,
        QuantLib.Period("4Y"): 3.97872,
        QuantLib.Period("5Y"): 3.88900,
        QuantLib.Period("6Y"): 3.83837,
        QuantLib.Period("7Y"): 3.80548,
        QuantLib.Period("8Y"): 3.78547,
        QuantLib.Period("9Y"): 3.77427,
        QuantLib.Period("10Y"): 3.76773,
        QuantLib.Period("12Y"): 3.76733,
        QuantLib.Period("15Y"): 3.77146,
        QuantLib.Period("20Y"): 3.73336,
        QuantLib.Period("25Y"): 3.64168,
        QuantLib.Period("30Y"): 3.54345,
        QuantLib.Period("40Y"): 3.33623,
        QuantLib.Period("50Y"): 3.11935
    }
    ois_helpers: typing.List[QuantLib.OISRateHelper] = [
        QuantLib.OISRateHelper(
            settlementDays=2,
            tenor=tenor,
            rate=QuantLib.QuoteHandle(QuantLib.SimpleQuote(rate / 100)),
            index=overnightIndex,
        )
        for tenor, rate in swaps.items()
    ]

    curve: QuantLib.PiecewiseLogLinearDiscount = QuantLib.PiecewiseLogLinearDiscount(0, calendar, ois_helpers, QuantLib.Actual360())
    tenors: typing.List[QuantLib.Period] = []
    rates: typing.List[float] = []
    maturity_dates: typing.List[datetime.date] = []
    zero_rates: typing.List[float] = []
    discount_factors: typing.List[float] = []
    for tenor, rate in swaps.items():
        tenors.append(tenor)
        rates.append(rate)
        maturity_date: QuantLib.Date = calendar.advance(settlement_date, tenor, QuantLib.ModifiedFollowing, True)
        maturity_dates.append(datetime.date(maturity_date.year(), maturity_date.month(), maturity_date.dayOfMonth()))
        discount_factor: float = curve.discount(maturity_date)
        discount_factors.append(discount_factor)
        zero_rate: float = -100.0 * math.log(discount_factor) * 365.0 / (maturity_date - reference_date)
        zero_rates.append(zero_rate)

    result: pandas.DataFrame = pandas.DataFrame(
        data={
            "Tenor": tenors,
            "Maturity Date": maturity_dates,
            "Market Rate": rates,
            "Zero Rate": zero_rates,
            "Discount": discount_factors,
            "Bloomberg Discount": [
                0.998374,
                0.997340,
                0.996309,
                0.994838,
                0.990299,
                0.985967,
                0.981757,
                0.977478,
                0.973338,
                0.961789,
                0.951308,
                0.932241,
                0.915541,
                0.884058,
                0.853814,
                0.824673,
                0.795974,
                0.768084,
                0.740793,
                0.714084,
                0.688356,
                0.638668,
                0.570186,
                0.477321,
                0.409793,
                0.357900,
                0.288171,
                0.250724
            ]
        }
    )
    result["Discount Difference"] = round(result["Discount"] - result["Bloomberg Discount"], 6)
    print(result.to_string(index=False))
```

The output is as follows:

```
Tenor Maturity Date  Market Rate  Zero Rate  Discount  Bloomberg Discount  Discount Difference
   1W    2024-03-12      5.33000   5.401229  0.998374            0.998374            -0.000000
   2W    2024-03-19      5.33260   5.401102  0.997340            0.997340            -0.000000
   3W    2024-03-26      5.33300   5.399085  0.996309            0.996309            -0.000000
   1M    2024-04-05      5.33535   5.397540  0.994838            0.994838            -0.000000
   2M    2024-05-06      5.34110   5.391176  0.990299            0.990299            -0.000000
   3M    2024-06-05      5.33440   5.373175  0.985967            0.985967             0.000000
   4M    2024-07-05      5.30550   5.333618  0.981757            0.981757            -0.000000
   5M    2024-08-05      5.27900   5.295914  0.977478            0.977478            -0.000000
   6M    2024-09-05      5.24035   5.246586  0.973338            0.973338             0.000000
   9M    2024-12-05      5.12030   5.096887  0.961789            0.961789             0.000000
   1Y    2025-03-05      4.98700   4.937667  0.951308            0.951308            -0.000000
  18M    2025-09-05      4.65575   4.631071  0.932241            0.932241             0.000000
   2Y    2026-03-05      4.43105   4.388100  0.915538            0.915541            -0.000003
   3Y    2027-03-05      4.13593   4.092998  0.884053            0.884058            -0.000005
   4Y    2028-03-06      3.97872   3.935097  0.853806            0.853814            -0.000008
   5Y    2029-03-05      3.88900   3.845092  0.824662            0.824673            -0.000011
   6Y    2030-03-05      3.83837   3.794754  0.795961            0.795974            -0.000013
   7Y    2031-03-05      3.80548   3.762279  0.768070            0.768084            -0.000014
   8Y    2032-03-05      3.78547   3.743008  0.740777            0.740793            -0.000016
   9Y    2033-03-07      3.77427   3.732890  0.714067            0.714084            -0.000017
  10Y    2034-03-06      3.76773   3.727594  0.688339            0.688356            -0.000017
  12Y    2036-03-05      3.76733   3.730669  0.638652            0.638668            -0.000016
  15Y    2039-03-07      3.77146   3.739299  0.570172            0.570186            -0.000014
  20Y    2044-03-07      3.73336   3.692611  0.477288            0.477321            -0.000033
  25Y    2049-03-05      3.64168   3.565595  0.409681            0.409793            -0.000112
  30Y    2054-03-05      3.54345   3.423741  0.357667            0.357900            -0.000233
  40Y    2064-03-05      3.33623   3.112584  0.287588            0.288171            -0.000583
  50Y    2074-03-05      3.11935   2.773300  0.249608            0.250724            -0.001116
```

## Answer by Attack68 (score 2, accepted)

https://quant.stackexchange.com/a/79047

I think this may be an issue with Bloomberg rounding its values and not displaying exactly what it is using as input or possibly that the Bloomberg "Step Forward (Cont)" does something that is not exactly equal to the log-linear interpolation of discount factors at the Maturity dates.

Your cross posted issue at Rateslib https://github.com/attack68/rateslib/issues/145 shows that Quantlib and Rateslib, which are both implenting log-linear interpolations on the same market data are almost equal.

So here the outlier seems to be Bloomberg, if two other independent sources converge on the same answer.

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.