Replicating the Single Currency Curve in Pricing and Trading Interest Rate Derivatives#
The Bloomberg SWPM Curve is obviously not the only way to build an interest rate curve. In Pricing and Trading Interest Rate Derivatives: A Practical Guide to Swaps an example is given in Chapter 6: Single Currency Curve Modelling of a curve that uses IMM instruments. Different modes of interpolation are exemplified in the Table 6.2, and this page will show how to replicate those curves using rateslib.
First, here is a screenshot of the data from Table 6.2. Since no details of any of the conventions are stated in the data we will assume that it is constructed with:
Convention: “Act365F”,
Calendar: “all” (which is all days and no holidays)
Frequency: “A”

The nodes needed for this curve are directly specified. We will build a Curve factory function below so that it is easier later to build all the Curves with different interpolation.
In [1]: def curve_factory(t):
...: return Curve(
...: nodes={
...: dt(2022, 1, 1): 1.0,
...: dt(2022, 3, 15): 1.0,
...: dt(2022, 6, 15): 1.0,
...: dt(2022, 9, 21): 1.0,
...: dt(2022, 12, 21): 1.0,
...: dt(2023, 3, 15): 1.0,
...: dt(2023, 6, 21): 1.0,
...: dt(2023, 9, 20): 1.0,
...: dt(2023, 12, 20): 1.0,
...: dt(2024, 3, 15): 1.0,
...: dt(2025, 1, 1): 1.0,
...: dt(2027, 1, 1): 1.0,
...: dt(2029, 1, 1): 1.0,
...: dt(2032, 1, 1): 1.0,
...: },
...: convention="act365f",
...: calendar="all",
...: t=t,
...: )
...:
To calibrate the Curves the Instruments are all swaps an their effective and termination dates only need to be properly input. Their rates are also given. Below we make a Solver factory function so that later we can solve different Curves without having to repeat all the code and the setup.
In [2]: def solver_factory(curve):
...: args = dict(calendar="all", frequency="a", convention="act365f", payment_lag=0, curves=curve)
...: return Solver(
...: curves=[curve],
...: instruments=[
...: IRS(dt(2022, 1, 1), "1b", **args),
...: IRS(dt(2022, 3, 15), dt(2022, 6, 15), **args),
...: IRS(dt(2022, 6, 15), dt(2022, 9, 21), **args),
...: IRS(dt(2022, 9, 21), dt(2022, 12, 21), **args),
...: IRS(dt(2022, 12, 21), dt(2023, 3, 15), **args),
...: IRS(dt(2023, 3, 15), dt(2023, 6, 21), **args),
...: IRS(dt(2023, 6, 21), dt(2023, 9, 20), **args),
...: IRS(dt(2023, 9, 20), dt(2023, 12, 20), **args),
...: IRS(dt(2023, 12, 20), dt(2024, 3, 15), **args),
...: IRS(dt(2022, 1, 1), "3y", **args),
...: IRS(dt(2022, 1, 1), "5y", **args),
...: IRS(dt(2022, 1, 1), "7y", **args),
...: IRS(dt(2022, 1, 1), "10y", **args)
...: ],
...: s=[
...: 1.0,
...: 1.05,
...: 1.12,
...: 1.16,
...: 1.21,
...: 1.27,
...: 1.45,
...: 1.68,
...: 1.92,
...: 1.68,
...: 2.10,
...: 2.20,
...: 2.07
...: ]
...: )
...:
We will now build and solve the three Curves with the different types of interpolation to match
the book’s values.
In order to add cubic spline interpolation we only need to add the knot sequence as the t
parameter.
In [3]: log_linear_curve = curve_factory(t=NoInput(0))
In [4]: log_cubic_curve = curve_factory(t=[dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 1, 1), dt(2022, 3, 15), dt(2022, 6, 15), dt(2022, 9, 21), dt(2022, 12, 21), dt(2023, 3, 15), dt(2023, 6, 21),dt(2023, 9, 20), dt(2023, 12, 20), dt(2024, 3, 15), dt(2025, 1, 1), dt(2027, 1, 1), dt(2029, 1, 1), dt(2032, 1, 1), dt(2032, 1, 1), dt(2032, 1, 1), dt(2032, 1, 1)])
In [5]: mixed_curve = curve_factory(t=[dt(2024, 3, 15), dt(2024, 3, 15), dt(2024, 3, 15), dt(2024, 3, 15), dt(2025, 1, 1), dt(2027, 1, 1), dt(2029, 1, 1), dt(2032, 1, 1), dt(2032, 1, 1), dt(2032, 1, 1), dt(2032, 1, 1)])
In [6]: solver_factory(log_linear_curve)
SUCCESS: `func_tol` reached after 6 iterations (levenberg_marquardt), `f_val`: 2.3850358181323094e-16, `time`: 0.0145s
Out[6]: <rl.Solver:465f7_ at 0x7a3a95dd62a0>
In [7]: solver_factory(log_cubic_curve)
SUCCESS: `func_tol` reached after 6 iterations (levenberg_marquardt), `f_val`: 2.787476321745212e-16, `time`: 0.0151s
Out[7]: <rl.Solver:786a0_ at 0x7a3a8ffcdf10>
In [8]: solver_factory(mixed_curve)
SUCCESS: `func_tol` reached after 6 iterations (levenberg_marquardt), `f_val`: 2.55771486834464e-16, `time`: 0.0148s
Out[8]: <rl.Solver:e4733_ at 0x7a3a8ffdbc20>
The discount factors for each Curve are stated as below:
In [9]: df = DataFrame(
...: index=[_ for _ in log_linear_curve.nodes.keys()],
...: data={
...: "log-linear": [float(_) for _ in log_linear_curve.nodes.values()],
...: "log-cubic": [float(_) for _ in log_cubic_curve.nodes.values()],
...: "mixed": [float(_) for _ in mixed_curve.nodes.values()],
...: }
...: )
...:
In [10]: with option_context("display.float_format", lambda x: '%.6f' % x):
....: print(df)
....:
log-linear log-cubic mixed
2022-01-01 1.000000 1.000000 1.000000
2022-03-15 0.998002 0.997990 0.998002
2022-06-15 0.995368 0.995355 0.995368
2022-09-21 0.992383 0.992371 0.992383
2022-12-21 0.989522 0.989509 0.989522
2023-03-15 0.986774 0.986762 0.986774
2023-06-21 0.983421 0.983408 0.983421
2023-09-20 0.979878 0.979866 0.979878
2023-12-20 0.975791 0.975779 0.975791
2024-03-15 0.971397 0.971385 0.971397
2025-01-01 0.950979 0.950979 0.950979
2027-01-01 0.900384 0.900395 0.900384
2029-01-01 0.857395 0.857430 0.857422
2032-01-01 0.814369 0.814470 0.814460
The Curves are plotted.
In [11]: log_linear_curve.plot("1b", comparators=[log_cubic_curve, mixed_curve], labels=["log_linear", "log_cubic", "mixed"])
Out[11]:
(<Figure size 640x480 with 1 Axes>,
<Axes: >,
[<matplotlib.lines.Line2D at 0x7a3a8ffaa5a0>,
<matplotlib.lines.Line2D at 0x7a3a95d84740>,
<matplotlib.lines.Line2D at 0x7a3a95d84a40>])
(Source code
, png
, hires.png
, pdf
)

The same graph as shown in Pricing and Trading Interest Rate Derivatives (Table 6.2)#