I have been attempting to bootstrap zero rates using quantlib
, but I am perplexed by the significant discrepancies between my calculated zero rates and those obtained from Bloomberg's bootstrapping process. I would greatly appreciate any insights or suggestions regarding potential reasons for this mismatch. Below is the reproducible example :
import QuantLib as ql
calculation_date = ql.Date().todaysDate() #When I posted this on Quant exchange the date was 26/5/2023
ql.Settings.instance().evaluationDate = calculation_date
index = ql.OvernightIndex("USD Overnight Index", 0, ql.USDCurrency(), ql.UnitedStates(ql.UnitedStates.Settlement), ql.Actual360())
swaps = {
ql.Period("1W"): 0.05064,
ql.Period("2W"): 0.05067,
ql.Period("3W"): 0.05072,
ql.Period("1M"): 0.051021000000000004,
ql.Period("2M"): 0.051391,
ql.Period("3M"): 0.051745,
ql.Period("4M"): 0.05194,
ql.Period("5M"): 0.051980000000000005,
ql.Period("6M"): 0.051820000000000005,
ql.Period("7M"): 0.051584000000000005,
ql.Period("8M"): 0.05131,
ql.Period("9M"): 0.050924,
ql.Period("10M"): 0.050603999999999996,
ql.Period("11M"): 0.050121,
ql.Period("12M"): 0.049550000000000004,
ql.Period("18M"): 0.04558500000000001,
ql.Period("2Y"): 0.042630999999999995,
ql.Period("3Y"): 0.038952,
ql.Period("4Y"): 0.036976,
ql.Period("5Y"): 0.035919,
ql.Period("6Y"): 0.03535,
ql.Period("7Y"): 0.034998,
ql.Period("8Y"): 0.034808,
ql.Period("9Y"): 0.034738000000000005,
ql.Period("10Y"): 0.034712,
ql.Period("12Y"): 0.034801,
ql.Period("15Y"): 0.034923,
ql.Period("20Y"): 0.034662,
ql.Period("25Y"): 0.03375,
ql.Period("30Y"): 0.032826,
ql.Period("40Y"): 0.030834999999999998,
ql.Period("50Y"): 0.02896
}
rate_helpers = []
for tenor, rate in swaps.items():
helper = ql.OISRateHelper(0, tenor, ql.QuoteHandle(ql.SimpleQuote(rate)), index)
rate_helpers.append(helper)
yts = ql.RelinkableYieldTermStructureHandle()
curve = ql.PiecewiseFlatForward(calculation_date, rate_helpers, ql.Actual360())
yts.linkTo(curve)
index = index.clone(yts)
engine = ql.DiscountingSwapEngine(yts)
print("maturity | market | model | zero rate | discount factor")
for tenor, rate in swaps.items():
schedule = ql.Schedule(calculation_date,
calculation_date + tenor,
ql.Period('1Y'),
ql.UnitedStates(ql.UnitedStates.GovernmentBond),
ql.ModifiedFollowing,
ql.ModifiedFollowing,
ql.DateGeneration.Forward,
False)
swap = ql.OvernightIndexedSwap(ql.OvernightIndexedSwap.Payer,
1.0,
schedule,
0.01,
ql.Actual360(),
index)
swap.setPricingEngine(engine)
maturity_date = calculation_date + tenor
zero_rate = curve.zeroRate(maturity_date, ql.Actual360() , ql.Compounded).rate()
discount_factor = curve.discount(maturity_date)
print(f" {tenor} | {rate*100:.6f} | {swap.fairRate()*100:.6f} | {zero_rate*100:.6f} | {discount_factor:.6f}")
The output of this code is :
maturity | market | model | zero rate | discount factor
1W | 5.064000 | 5.064000 | 5.191792 | 0.999016
2W | 5.067000 | 5.067000 | 5.192324 | 0.998033
3W | 5.072000 | 5.072000 | 5.194951 | 0.997050
1M | 5.102100 | 5.102100 | 5.222740 | 0.995626
However, when referring to the output displayed on Bloomberg, I find it important to mention that the information presented, which I believe to be accurate, is as follows:
I am inclined to believe that the issue at hand could potentially be attributed to parameters.
I would greatly appreciate any suggestions, insights that could help me understand and resolve the disparities between my calculated zero rates and the accurate rates shown on Bloomberg. Thank you for your valuable assistance!
Below is the information I have from bloomberg regarding conventions