Skip to content
All library documents

Simultaneous Calibration of Forecasting and Discount Curves

Article Quant Q&A · Author: user80349

Summary

The answer recommends calibrating interconnected interest-rate curves with a global solver instead of bootstrapping each curve in sequence. The workflow defines curves and calibration instruments, assigns each instrument its forecasting and discounting curves, and solves the resulting pricing residuals together using least squares, with Levenberg–Marquardt or Gauss–Newton methods as examples.

An illustrative setup first solves a standalone SOFR curve, then jointly calibrates an overnight forecast curve and a cross-currency collateral discount curve using interest-rate and cross-currency swaps. FX forwards connect the curves and provide rates needed to price mark-to-market cross-currency swaps. The example uses European overnight and USD curves as an analogue for the Chilean curve problem. It demonstrates the framework but does not give a Camara-specific implementation, discuss solver diagnostics or market-data quality, or compare calibration accuracy against sequential bootstrapping.

Key ideas

  • Global curve solvers can calibrate linked forecast and discount curves in one least-squares problem.
  • Each instrument must be mapped to the curves it uses for forecasting and discounting.
  • A standalone SOFR curve can be solved first, then used while jointly calibrating other curves.
  • FX forwards derived from curve objects support pricing mark-to-market cross-currency swaps.
  • The European-currency example is a framework analogue and does not supply a Camara-specific implementation.

Tags

Full text
# Dual Bootstrapping Python


# Dual Bootstrapping Python












I am trying to perform a dual bootstrapping for the Chilean curves in order to generate the Camara curves for Compound Index Swap and Cross CC Basis Swap, collateralized. In this case, for the cbas sofr-camara curve, I have two floating legs: one projects and discounts to SOFR, and the other projects with Camara and discounts with Cross.

On the other hand, for the irs-camara curve, I have a fixed leg and a floating leg. The fixed leg clearly does not project, but discounts with the cross USD-CLP curve. Meanwhile, the floating leg projects with the Camara CLP curve and discounts with the cross USD-CLP curve.

My question highlights how I could implement this methodology in Python or C++. My idea would be something like this (assuming I have the information for SOFR, swaps, etc.):

- Build a database to calculate floating rates and interpolate discount factors, both for Camara and SOFR with semi-annual payments.

- For the first tenors, use an explicit formula since there are no coupon payments. For tenors greater than 2 years, perform dual bootstrapping.

- Calculate the fixed and floating legs for each swap and try to optimize both FDs simultaneously, aiming for PV fix - PV float = 0 in the case of Camara, and PV float SOFR - PV float Camara = 0.

However, I have not been able to come up with something coherent.

## Answer by Attack68 (score 2)

https://quant.stackexchange.com/a/81719

This situational complexity (and other scenarios) is why I prefer to operate using global curve solvers that simultaneously solve all parameters rather than rely on sequential bootstrapping.

What I do is:

- Setup the Curves I am going to solve for, then

- Define the Instruments I want to use to calibrate those Curves,

- Map all of the Instruments pricing details to each Curve

- Solve a least squares problem using the Levenberg-Marquardt method or Gauss-Newton method.

Suppose you rephrase you question replacing Chilean Camara with European Estr, then the following framework would be identical to your situation in terms of your forecast / discount and collateral curves.

#### 1) Solve an SOFR Curve independently using SOFR IRS.

```
from rateslib import Curve, IRS, Solver, dt  # Python 3.12, rateslib 1.7.0

usdusd = Curve(
    nodes={dt(2025, 1, 21): 1.0, dt(2026, 1, 21): 1.0, dt(2027, 1, 21): 1.0},
    calendar="nyc",
    convention="act360",
    id="usdusd"
)
solver = Solver(
    curves=[usdusd],
    instruments=[
        IRS(dt(2025, 1, 21), "1y", spec="usd_irs", curves="usdusd"),
        IRS(dt(2025, 1, 21), "2y", spec="usd_irs", curves="usdusd")
    ],
    s=[4.10, 3.90]
)
```

#### 2) Define your ESTR forecast curve and ESTR-USD collateral Curves without calibrating them:

```
eureur = Curve(
    nodes={dt(2025, 1, 21): 1.0, dt(2026, 1, 21): 1.0, dt(2027, 1, 21): 1.0},
    calendar="tgt",
    convention="act360",
    id="eureur"
)
eurusd = Curve(
    nodes={dt(2025, 1, 21): 1.0, dt(2026, 1, 21): 1.0, dt(2027, 1, 21): 1.0},
    calendar="all",
    convention="act360",
    id="eurusd",
)
```

#### 3) Define an FX-Forwards Market from the Curve objects and FXRates

This is used to generate forward FX rates which are needed by the MTM cross currency swaps that will be defined in the calibration stage.

```
fxf = FXForwards(
    fx_rates=FXRates({"eurusd": 1.03}, settlement=dt(2025, 1, 23)),
    fx_curves={
        "usdusd": usdusd,
        "eureur": eureur,
        "eurusd": eurusd
    }
)
```

#### 4) Simultaneously calibrate the remaining Curves from your IRS and XCS instruments defined with those Curves :

```
solver2 = Solver(
    pre_solvers=[solver],  # need to pass pre_solver becuase it contains the required usdusd curve
    curves=[eureur, eurusd],
    instruments=[
        IRS(dt(2025, 1, 21), "1y", spec="eur_irs", curves=["eureur", "eurusd"]),  # this specifies forecasting and discounting curve
        IRS(dt(2025, 1, 21), "2y", spec="eur_irs", curves=["eureur", "eurusd"]),
        XCS(dt(2025, 1, 21), "1y", spec="eurusd_xcs", curves=["eureur", "eurusd", "usdusd", "usdusd"]),
        XCS(dt(2025, 1, 21), "2y", spec="eurusd_xcs", curves=["eureur", "eurusd", "usdusd", "usdusd"]),
    ],
    s=[2.4, 2.1, -4, -8.5],
    fx=fxf,  # need the FXForwards object to generate FX rates for XCS
)
```

This system has now calibrated all of the 6 parameters given the 6 instrument prices in the definitions required. All of the curves can be plotted, including the implied USD cashflows collateralised in EUR.

```
usdusd.plot("1b", comparators=[eureur, eurusd, fxf.curve("usd", "eur")], labels=["usdusd", "eureur", "eurusd", "usdeur"])
```

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.