MtMCrossCurrencyBasisSwapRateHelper Produces Unrealistic Zero Rates When Using OIS Indices (Ester/SOFR)
Hi,
When building a mark-to-market (MtM) cross-currency basis curve between EUR (Ester) and USD (SOFR), the MtMCrossCurrencyBasisSwapRateHelper gives unrealistic zero rates and discount factors for the long end of the curve. The short end, calibrated using FX forwards, behaves as expected. The issue appears when extending the curve using MtMCrossCurrencyBasisSwapRateHelper.
I’m trying to build a EUR–USD MTM xccy basis curve where both indices are overnight (OIS).
For maturities up to 1Y, I use FX forwards, which produce correct and stable results. Beyond 1Y, using MtMCrossCurrencyBasisSwapRateHelper, the resulting zero rates explode (see data below).
helper = ql.MtMCrossCurrencyBasisSwapRateHelper( basis=ql.QuoteHandle(basisQuote), tenor=ql.Period("2Y"), fixingDays=2, calendar=ql.JointCalendar(ql.TARGET(), ql.UnitedStates()), convention=ql.ModifiedFollowing, endOfMonth=False, baseCurrencyIndex=ql.Estr(), quoteCurrencyIndex=ql.Sofr(), collateralCurve=ql.YieldTermStructureHandle(collateralCurve), isFxBaseCurrencyCollateralCurrency=False, isBasisOnFxBaseCurrencyLeg=True, isFxBaseCurrencyLegResettable=True, paymentFrequency=4, )
| Date | TimeFromRef | ZeroRate | Discount |
|---|---|---|---|
| 27/11/2026 | 1.025 | 1969.78 | 1.7e-09 |
| 26/11/2027 | 2.036 | 1025.54 | 8.5e-10 |
| 27/11/2028 | 3.056 | 705.92 | 4.3e-10 |
| 26/11/2030 | 5.081 | 451.33 | 1.1e-10 |
| 26/11/2035 | 10.153 | 254.89 | 5.8e-12 |
I noticed the constructor for MtMCrossCurrencyBasisSwapRateHelper expects IborIndex objects for the floating legs — not OISIndex (like Ester or Sofr).
Could this mismatch be the cause of the issue when both indices are OIS? or is it an issue with my inputs?
Hi!
Overnight indices should be handled properly - based on this https://github.com/lballabio/QuantLib/issues/1973.
How are you inserting the basis? Note that the constructor expects it in decimal format, e.g. 20 bps should be passed as 0.002. Perhaps, this should be made clearer in the documentation of the class.
Basis is input in actual terms meaning if 10bps it is 0.001
May you post the rest of your script so we can run it? Thanks.
Hi, attached is a self contained example that shows the curve building.
Thanks for the help.
Hi, thanks for sending over the example. By the first look, it seems that you are linking to the same handle 1) the basis curve you are bootstrapping 2) ESTR projection curve used by the index. ESTR should be linked to a separate ESTR curve, built out of ESTR swaps - basis curve should not be used to project ESTR fixings. Second, very minor thing, is that you set FX base leg (EUR in this case) as resettable, which I believe for these instruments is the USD leg.