Shifting a QuantLib Projection Curve for Swap DV01 Calculations
Summary
The document addresses how to calculate swap DV01 under shifts to both a discount curve and a three month USD LIBOR projection curve. The key issue is that the USD LIBOR index used by a vanilla swap takes a yield curve handle, so creating a separate index around a spreaded curve can cause a type mismatch. One answer proposes wrapping the shifted curve in a yield term structure handle when constructing a new index.
A second approach keeps the index and swap fixed while changing the curve they reference. This can be done with a relinkable handle that is pointed to a shifted curve and later restored, or with a zero spreaded term structure whose spread quote is adjusted up or down and reset to zero. These approaches let the existing swap reflect the changed projection curve when recalculated. The discussion focuses on QuantLib object wiring and curve sensitivity setup; it does not cover DV01 conventions, curve construction quality, or validation of the resulting risk measure.
Key ideas
- A QuantLib USD LIBOR index depends on a yield curve handle for its projection curve.
- A shifted curve can be wrapped in a handle when constructing a separate index.
- A relinkable handle allows an existing index and swap to use a shifted curve temporarily.
- A zero spreaded curve with a mutable quote can apply and reset curve shifts without replacing the index.
Tags
Full text
# Quantlib USDLibor() method
# Quantlib USDLibor() method
I'm attempting to shift both a discount and projection curve but am having trouble passing through the VanillaSwap() because of the Ibor input requirement -- I'm trying to calculate the dv01 of a US dollar swap, which requires the base and shifted (up, down) 3mL curve and SOFR curve. How do I pass the shifted 3mL curve through the ql.USDLibor() method? I am receiving an error.
I started with dates and discount factors to generate US3mL and SOFR objects.
I then created DiscountCurve (US3mL, SOFR) objects using the dates,df lists.
```
US3mL = ql.DiscountCurve(curve_US3mL_dates, curve_US3mL_df, ql.Thirty360(), ql.UnitedStates(ql.UnitedStates.GovernmentBond))
SOFR = ql.DiscountCurve(curve_USSOFR_dates, curve_USSOFR_df, ql.Actual360(), ql.UnitedStates(ql.UnitedStates.GovernmentBond))
shift = 0.0001
discount_curve = ql.YieldTermStructureHandle(SOFR)
discount_curve_shiftUp = ql.ZeroSpreadedTermStructure(discount_curve, ql.QuoteHandle(ql.SimpleQuote(shift)))
discount_curve_shiftDown = ql.ZeroSpreadedTermStructure(discount_curve, ql.QuoteHandle(ql.SimpleQuote(-shift)))
libor_curve = ql.YieldTermStructureHandle(US3mL)
libor_curve_shiftUp = ql.ZeroSpreadedTermStructure(libor_curve, ql.QuoteHandle(ql.SimpleQuote(shift)))
libor_curve_shiftDown = ql.ZeroSpreadedTermStructure(libor_curve, ql.QuoteHandle(ql.SimpleQuote(-shift)))
libor3M_index = ql.USDLibor(ql.Period('3M'),libor_curve)
#libor3M_index_shiftUp = ql.USDLibor(ql.Period('3M'), libor_curve_shiftUp)
#libor3M_index_shiftDown = ql.USDLibor(ql.Period('3M'), libor_curve_shiftDown)
```
The last two commented lines of code are failing but the Ibor object must be passed through VanillaSwap() as the "index" input.
## Answer by Luigi Ballabio (score 3)
https://quant.stackexchange.com/a/68435
Don't change the index, relink or modify the curve instead.
For the first method (relink), set up the index as:
```
libor_curve = ql.RelinkableYieldTermStructureHandle(US3mL)
libor3M_index = ql.USDLibor(ql.Period('3M'), libor_curve)
```
and build the vanilla swap using `libor3M_index`. When you want to shift the curve up:
```
libor_curve.linkTo(
ql.ZeroSpreadedTermStructure(ql.YieldTermStructureHandle(US3mL),
ql.QuoteHandle(ql.SimpleQuote(shift))))
```
the index, and indirectly the swap, will use the new curve next time you call their methods. When you are done, call
```
libor_curve.linkTo(US3mL)
```
to reset the curve to the original level.
For the second method, set up as:
```
spread = ql.SimpleQuote(0.0)
libor_curve = ql.ZeroSpreadedTermStructure(ql.YieldTermStructureHandle(US3mL),
ql.QuoteHandle(spread))
libor3M_index = ql.USDLibor(ql.Period('3M'), libor_curve)
```
so the spreaded curve equals `US3mL` (since the spread is 0). Build the swap, and when you want to shift the curve up or down execute:
```
spread.setValue(shift) # or spread.setValue(-shift)
```
Again, the curve will update and the swap will use the new level. To get back to square one, run
```
spread.setValue(0.0)
```
## Answer by David Duarte (score 2)
https://quant.stackexchange.com/a/68434
The `ql.ZeroSpreadedTermStructure` will return a YieldTermstructure but for the `ql.USDLibor contructor` you need a `ql.YieldTermStructureHandle`.
Change the last two lines to:
```
libor3M_index_shiftUp = ql.USDLibor(ql.Period('3M'), ql.YieldTermStructureHandle(libor_curve_shiftUp))
libor3M_index_shiftDown = ql.USDLibor(ql.Period('3M'), ql.YieldTermStructureHandle(libor_curve_shiftDown))
```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.