Align OIS Curve Helpers, Index Fixings, and Swap Schedules in QuantLib
Summary
The document diagnoses why a QuantLib overnight indexed swap built to check an OIS curve may request a fixing dated before the evaluation date. The example combines two fixing days in the overnight index with two settlement days in the rate helpers and then constructs comparison swaps using a daily schedule. These conventions do not align, so the floating leg can require a historical fixing that was not supplied.
The accepted explanation recommends matching the index fixing-day and helper settlement-day assumptions to the intended swap start date. To reproduce rates from instruments starting on the calculation date, use zero for both; alternatively, retain two settlement days and start the comparison swaps two business days later. It also corrects the payment schedule: overnight rates compound daily, but the swap payments are annual, so the schedule should use a one-year tenor rather than one day. The reported outcome is that these adjustments reproduce the quoted rates, though the example is specific to its dates and conventions.
Key ideas
- Index fixing days determine which historical overnight fixing a swap leg may require.
- Rate-helper settlement days determine when the helper instruments begin accruing.
- The helper and comparison swap start dates must follow consistent settlement conventions.
- Daily overnight compounding does not imply daily payment dates; the example uses annual payments.
- The suggested convention changes reproduce the quoted rates in the provided example.
Tags
Full text
# Yield curve bootstrapping not producing expected cash flow start date
# Yield curve bootstrapping not producing expected cash flow start date
I'm currently working on building a yield curve using OIS swaps. However, I'm encountering an issue with the cash flow start date that I'm struggling to understand. Here's a simplified version of my code:
Reproducible example
```
import QuantLib as ql
calculation_date = ql.Date().todaysDate() #When I posted this on Quant exchange the date was 25/5/2023
ql.Settings.instance().evaluationDate = calculation_date
index = ql.OvernightIndex("USD Overnight Index", 2, 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(2, tenor, ql.QuoteHandle(ql.SimpleQuote(rate)), index)
rate_helpers.append(helper)
yts = ql.RelinkableYieldTermStructureHandle()
curve = ql.PiecewiseSplineCubicDiscount(calculation_date, rate_helpers, ql.Actual360())
yts.linkTo(curve)
index = index.clone(yts)
engine = ql.DiscountingSwapEngine(yts)
print("maturity | market | model")
for tenor, rate in swaps.items():
schedule = ql.Schedule(calculation_date,
calculation_date + tenor,
ql.Period('1D'),
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)
print(f" {tenor} | {rate:.6f} | {swap.fairRate():.6f}")
```
The issue I'm facing is that the cash flow start date for the swaps is appearing two days before the evaluation date (May 23rd, 2023 and Evaluation date May 25rd, 2023), which doesn't align with my expectations. I have set the evaluation date as the current date in my code, so the cash flow start date should be either on the evaluation date or after it.
> ` File "AAA", line 173, in build_curve_v2 print(f" {tenor} | {rate:.6f} | {swap.fairRate():.6f}") ^^^^^^^^^^^^^^^ File "AAA", line 12, in <module> bootstrap.Curve_Build() RuntimeError: 2nd leg: Missing USD Overnight IndexSN Actual/360 fixing for May 23rd, 2023 `
```
File "AAA", line 173, in build_curve_v2
print(f" {tenor} | {rate:.6f} | {swap.fairRate():.6f}")
^^^^^^^^^^^^^^^
File "AAA", line 12, in <module>
bootstrap.Curve_Build()
RuntimeError: 2nd leg: Missing USD Overnight IndexSN Actual/360 fixing for May 23rd, 2023
```
I suspect there might be something wrong with my bootstrapping method or the way I'm constructing the yield curve. Any guidance or suggestions on how to resolve this issue would be greatly appreciated.
A Possible explanation : When an `OvernightIndexedSwap` is created, the floating leg's schedule is generated with its first date being T+2 days before the start of the swap, where T is the start of the swap. In my case, if the swap starts on the evaluation date of May 25th, 2023, the floating leg's schedule will begin on May 23rd, 2023. As a result, a fixing for the overnight rate on May 23rd, 2023, is required.
## Answer by Luigi Ballabio (score 5, accepted)
https://quant.stackexchange.com/a/75660
There are a few consistency problems.
One is that you're passing 2 fixing days to the `ql.OvernightIndex` constructor. This way the schedule starts correctly on the calculation date, but the index will look for fixings two days before that. I'd use 0 days instead.
Another is that, when you're creating the helpers, you're passing 2 settlement days. This means that their schedule will start 2 days after the calculation date. For consistency, since you want to reproduce these rates, you need 0 here as well (or you might keep the 2, but in this case the swap schedules will need to start two business days after the calculation date too.)
Finally, when you create the swaps at the end, you're passing a tenor of 1D to the schedule. That would mean payments each day. Instead, the rate is compounded each day but the payments are annual so you need to pass 1Y instead.
With these changes, I'm getting the same rates.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.