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.0236s
Out[6]: <rl.Solver:a24c2_ at 0x7f2e966d4f50>
In [7]: solver_factory(log_cubic_curve)
SUCCESS: `func_tol` reached after 6 iterations (levenberg_marquardt), `f_val`: 2.787476321745212e-16, `time`: 0.0238s
Out[7]: <rl.Solver:3fbbe_ at 0x7f2e821b9100>
In [8]: solver_factory(mixed_curve)
SUCCESS: `func_tol` reached after 6 iterations (levenberg_marquardt), `f_val`: 2.55771486834464e-16, `time`: 0.0237s
Out[8]: <rl.Solver:c41ed_ at 0x7f2e966e0b90>
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 0x7f2e962d3b00>,
<matplotlib.lines.Line2D at 0x7f2e962e5310>,
<matplotlib.lines.Line2D at 0x7f2e962e4380>])
(Source code
, png
, hires.png
, pdf
)