Building an ESTR Curve and Euribor Basis Curve for FRA Pricing
Summary
The document explains how to turn central bank meeting rate expectations into an overnight ESTR curve, then combine that curve with a separate ESTR-to-Euribor basis curve to estimate six-month Euribor rates for FRA pricing. Its example uses meeting dates as curve nodes and calibrates the curves with short-dated swaps and spreads, followed by six-month swaps at quarterly dates.
The main caveat is that meeting changes and a current Euribor fixing alone do not provide enough information for a proper Euribor curve: the ESTR/Euribor basis is also needed. The example assumes a particular basis interpolation and reports that its resulting six-month curve differs by a few basis points between reference dates from simple linear interpolation of six-month rates. It outlines a curve-building approach rather than giving a specific 1x7 FRA price, and the illustrative calibration inputs should not be treated as universal market data.
Key ideas
- Meeting-driven policy expectations can be represented as nodes in an ESTR curve.
- A Euribor projection curve requires information about the ESTR-to-Euribor basis.
- The example constructs a composite six-month curve by adding a basis curve to the overnight curve.
- Curve interpolation choices can produce different intervening forward rates.
Tags
Full text
# Using QuantLib to build Flat Forward Curve using Changes assumed from central bank meetings to price FRAs
# Using QuantLib to build Flat Forward Curve using Changes assumed from central bank meetings to price FRAs
What I am trying to do is price EURIBOR6M FRAs using a curve built in quantlib with changes in rate due to central bank meetings.
For concreteness, my goal is to price EURIBOR6M FRAs, say 1x7 FRA, given changes from the meetings. Assume the following details:
```
meeting_dates = [
dt.date(2024, 3, 7),
dt.date(2024, 4, 11),
dt.date(2024, 6, 6),
dt.date(2024, 7, 18),
dt.date(2024, 9, 12),
dt.date(2024, 10, 17),
dt.date(2024, 12, 12)
]
rate_change = [-0.6, -5.7, -18.4, -14.6, -18.6, -17.8, -22.8]
curve_date = dt.date(2024, 1, 8)
euribor6m_rate = 3.929
estr_rate = 3.904
```
Given these how I do build the flat forward curve in quantlib, then price the 1x7 FRA?
## Answer by Attack68 (score 3, accepted)
https://quant.stackexchange.com/a/78521
You don't really have enough information. You need the ESTR/6MEuribor basis to do this properly.
You can start by creating an ESTR curve from your policy meeting steps:
(For a Quantlib - Rateslib conversion review here: https://rateslib.readthedocs.io/en/stable/z_quantlib.html)
```
from rateslib import *
m = [
dt(2024, 1, 8),
dt(2024, 3, 7),
dt(2024, 4, 11),
dt(2024, 6, 6),
dt(2024, 7, 18),
dt(2024, 9, 12),
dt(2024, 10, 17),
dt(2024, 12, 12),
]
curve = Curve(
nodes={
dt(2024, 1, 8):1.0,
dt(2024, 3, 7):1.0,
dt(2024, 4, 11):1.0,
dt(2024, 6, 6):1.0,
dt(2024, 7, 18):1.0,
dt(2024, 9, 12):1.0,
dt(2024, 10, 17):1.0,
dt(2024, 12, 12):1.0,
dt(2025, 1, 31):1.0
},
convention="act360",
calendar="tgt"
)
solver = Solver(
curves=[curve],
instruments=[
IRS(m[0], "1b", spec="eur_irs", curves=curve),
Spread(IRS(m[0], "1b", spec="eur_irs", curves=curve),
IRS(m[1], "1b", spec="eur_irs", curves=curve)),
Spread(IRS(m[1], "1b", spec="eur_irs", curves=curve),
IRS(m[2], "1b", spec="eur_irs", curves=curve)),
Spread(IRS(m[2], "1b", spec="eur_irs", curves=curve),
IRS(m[3], "1b", spec="eur_irs", curves=curve)),
Spread(IRS(m[3], "1b", spec="eur_irs", curves=curve),
IRS(m[4], "1b", spec="eur_irs", curves=curve)),
Spread(IRS(m[4], "1b", spec="eur_irs", curves=curve),
IRS(m[5], "1b", spec="eur_irs", curves=curve)),
Spread(IRS(m[5], "1b", spec="eur_irs", curves=curve),
IRS(m[6], "1b", spec="eur_irs", curves=curve)),
Spread(IRS(m[6], "1b", spec="eur_irs", curves=curve),
IRS(m[7], "1b", spec="eur_irs", curves=curve)),
],
s=[3.90, -1, -7.5, -19.8, -16.7, -20, -18.2, -23.2]
)
curve.plot("1b")
```
Then you can add a spread curve on top of this, suppose the ESTR/6M spread is linearly interpolated between the IMM dates:
```
spread_curve = Curve(
nodes={
dt(2024, 1, 8): 1.0,
dt(2024, 3, 20): 1.0,
dt(2024, 6, 19): 1.0,
dt(2024, 9, 18): 1.0,
dt(2024, 12, 19): 1.0,
},
convention="act360",
calendar="tgt"
)
curve_6m_composite = CompositeCurve([curve, spread_curve])
solver2 = Solver(
pre_solvers=[solver],
curves=[curve_6m_composite, spread_curve],
instruments=[
IRS(dt(2024, 1, 8), "6m", spec="eur_irs", curves=curve_6m_composite),
IRS(dt(2024, 3, 20), "6m", spec="eur_irs", curves=curve_6m_composite),
IRS(dt(2024, 6, 19), "6m", spec="eur_irs", curves=curve_6m_composite),
IRS(dt(2024, 9, 18), "6m", spec="eur_irs", curves=curve_6m_composite),
],
s=[3.925, 3.88, 3.5725, 3.2125]
)
curve_6m_composite.plot("6m", comparators=[curve], labels=["6M Euribor", "6M ESTR"])
```
These images are too large a scale to highlight what this is doing, but if your compare this 6m curve to one that has just linearly interpolation of 6m rate between depo and IMM rates, you will observes a few basis points of difference in the intervening months.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.