Diagnosing Bloomberg and QuantLib USD OIS Discount Curve Differences
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.