Debugging FX Swap Yield Curve Bootstrapping from Discount Factors
Summary
The document describes an attempt to derive a GBP yield curve from a bootstrapped USD overnight indexed swap curve and FX forward quotes using QuantLib. The USD discount factors match a separate library, but the GBP discount factors differ slightly. The code shows how the author builds rate helpers from FX swap prices and settlement dates, then bootstraps a piecewise forward curve.
The suggested diagnostic is to compute forward FX values directly from the two currencies’ discount factors at each swap maturity and compare those values with the market quotes. This first-principles check can identify whether the curve construction or the comparison library is inconsistent. The discussion does not establish which implementation is wrong: market data and full conventions are absent, so it offers a debugging approach rather than a definitive fix.
Key ideas
- Matching USD discount factors does not by itself validate the GBP curve derived from FX swaps.
- Calculate implied forward FX rates from discount factor ratios at the swap maturity dates.
- Compare those implied rates with the quoted FX forwards to localize discrepancies.
- Without the underlying data and conventions, the source of the mismatch cannot be determined.
Tags
Full text
# Bootstrapping yield curve with forward rates using QuantLib
# Bootstrapping yield curve with forward rates using QuantLib
I'm attempting to calculate a GBP yield curve using a USD OIS rate curve and the FX Forward rates using Quantlib. I am trying to replicate the output of a different library, and am close but can't seem to quite get it right.
Firstly, bootstrapping the USD yield curve from the OIS swap rates:
```
# Set the calculation date
start_date = ql.Date(1, 2, 2024)
ql.Settings.instance().evaluationDate = start_date
# Define calendar
calendar = ql.UnitedStates(0)
convention = ql.Following
endOfMonth = False
spot_date = calendar.advance(start_date, 2, ql.Days)
# Fixed leg payment frequency and conventions
fixed_frequency = ql.Annual
fixed_day_count = ql.Actual360()
fixed_convention = convention
fixed_payment_convention = convention
# Rule for generating the schedule
rule = ql.DateGeneration.Backward
# Define the overnight index as Fed Funds
overnight_index = ql.FedFunds()
# Market data for OIS rates and their specific maturity dates
maturity_dates = ois_data["end_date"].to_list() # Specific maturity dates
ois_rates = ois_data["Value"].to_list() # Corresponding OIS rates
# Create OIS Rate Helpers with specific maturity dates for the Fed Funds rate
ois_helpers = [
ql.OISRateHelper(
2,
ql.Period(maturity_date - spot_date, ql.Days),
ql.QuoteHandle(ql.SimpleQuote(rate)),
overnight_index,
paymentLag=0,
paymentFrequency=fixed_frequency,
paymentConvention=fixed_payment_convention,
paymentCalendar=calendar,
endOfMonth=endOfMonth,
)
for rate, maturity_date in zip(ois_rates, maturity_dates)
]
# Bootstrap the OIS curve
day_count = ql.Actual365Fixed()
ois_curve = ql.PiecewiseLogLinearDiscount(start_date, ois_helpers, day_count)
usd_yield_curve = ql.YieldTermStructureHandle(ois_curve)
```
When I compare the discount factors, I am getting the correct figures.
Next, I attempt to bootstrap the GBP yield curve:
```
fx_prices = fx_fwd["Value"].tolist()
settlement_dates = fx_fwd["Settlement Date"].dt.date.tolist()
joint_calendar = ql.JointCalendar(
ql.UnitedKingdom(), calendar, ql.JoinHolidays
)
rate_helpers = [
ql.FxSwapRateHelper(
ql.QuoteHandle(ql.SimpleQuote(price - spot_price)),
ql.QuoteHandle(ql.SimpleQuote(spot_price)),
ql.Period(ql.Date.from_date(settlement_date) - start_date, ql.Days),
0,
joint_calendar,
ql.ModifiedFollowing,
True,
False,
usd_yield_curve,
)
for price, settlement_date in zip(fx_prices, settlement_dates)
]
# Bootstrap the OIS curve
day_count = ql.Actual365Fixed()
# day_count = ql.Actual360()
gbp_curve = ql.PiecewiseFlatForward(start_date, rate_helpers, day_count)
gbp_yield_curve = ql.YieldTermStructureHandle(gbp_curve)
```
This time when I plot the discount factors, compared with the other library, they are different. Only by between 1-50 bp, but that is enough to throw off instrument pricing further down the line when using these yield curves.
I've tried a few different iterations of the arguments to the helpers but can't seem to get it to work. Any help would be really appreciated.
## Answer by Denys Usynin (score 0)
https://quant.stackexchange.com/a/78327
While there is not enough information in the question to answer exactly what is asked (it might be the other library giving the wrong answer, we don't have the data, etc), I suggest debugging this from the first principles.
Luckily the setup here is very simple - in the sense that if you compute forward FX values by hand (as ratio of discount factors) on FX swap maturity dates you can then immediately compare these to your FX swap quotes. This will very quickly tell you which of the two libraries is giving the wrong answer (might be both!) and also should give you some clues as to why.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.