Curves#

Inheritance diagram of rateslib.curves.Curve, rateslib.curves.LineCurve, rateslib.curves.CompositeCurve, rateslib.curves.MultiCsaCurve, rateslib.curves.ProxyCurve, rateslib.curves._BaseCurve, rateslib.curves._WithMutability, rateslib.curves.CreditImpliedCurve, rateslib.curves.TranslatedCurve, rateslib.curves.RolledCurve, rateslib.curves.ShiftedCurve

The rateslib.curves module allows flexible curve objects to be created, which can then, also, be calibrated by a Solver and market instruments.

Rateslib makes a distinction between two fundamentally different _CurveType. One is values based and one is discount factor (DF) based.

The fundamental object is the _BaseCurve abstract base class (which provides a generic object to allow users to implement their own custom Curve). All curve types in rateslib inherit this class and provide its methods and operations. All that is required for an object to inherit a _BaseCurve is that it provides a __getitem__() method.

The methods available to any _BaseCurve, based on its specified binary _CurveType classification are described below:

Operation

_CurveType.values

_CurveType.dfs

__getitem__(date)

Must return rates.

Must return DFs (or survival probabilities implying hazard rates).

rate()

Returns just the rate associated with effective.

Returns rates with more features; can imply rates of different tenors or add float_spread under different compounding methods, derived from DFs.

plot()

Creates a (date, rate) plot.

Creates a (date, rate) plot with the additional features as above.

shift()

Add a spread to the rate.

Add a spread to the overnight rates implied by the curve.

roll()

Translate the rate space in time.

Translate the rate space in time.

translate()

Translate only the initial node date forward in time.

Translate only the initial node date forward in time.

index_value()

Not available.

Returns index values provided the _CurveMeta contains an index_base value.

plot_index()

Not available.

Creates a (date, index_value) plot provided the above requirements.

The two main user curve classes are listed below:

rateslib.curves.Curve(*args, **kwargs)

A _BaseCurve with DF parametrisation at given node dates with interpolation.

rateslib.curves.LineCurve(*args, **kwargs)

A _BaseCurve with value parametrisation at given node dates with interpolation.

Introduction#

To create a simple curve, with localised interpolation, minimal configuration is required, only the nodes are required.

In [1]: from rateslib import dt

In [2]: curve = Curve(
   ...:     nodes={
   ...:         dt(2022,1,1): 1.0,  # <- initial DF (/survival probability) should always be 1.0
   ...:         dt(2023,1,1): 0.99,
   ...:         dt(2024,1,1): 0.979,
   ...:         dt(2025,1,1): 0.967,
   ...:         dt(2026,1,1): 0.956,
   ...:         dt(2027,1,1): 0.946,
   ...:     },
   ...:     interpolation="log_linear",
   ...: )
   ...: 

We can also use a similar configuration for a generalised curve constructed from connecting lines between values.

In [3]: linecurve = LineCurve(
   ...:     nodes={
   ...:         dt(2022,1,1): 0.975,  # <- initial value is general
   ...:         dt(2023,1,1): 1.10,
   ...:         dt(2024,1,1): 1.22,
   ...:         dt(2025,1,1): 1.14,
   ...:         dt(2026,1,1): 1.03,
   ...:         dt(2027,1,1): 1.03,
   ...:     },
   ...:     interpolation="linear",
   ...: )
   ...: 

Initial Node Date#

The initial node date for either curve type is important because it is implied to be the date of the construction of the curve (i.e. today’s date). When a Curve acts as a discount curve any net present values (NPVs) might assume other features from this initial node, e.g. the regular settlement date of securities. This is the also the reason the initial discount factor should also be exactly 1.0 on a Curve.

The only exception to this is when building a curve used to forecast values, such as index values and inflation prints, it may be practical to start the curve using the most recent inflation print which is usually assigned to the start of the month, thus this may be before today.

Get Item#

As mentioned, any _BaseCurve type has a __getitem__() method appropriate to its _CurveType.

Note

DFs (and values) before the curve’s initial node date return zero, in order to value historical cashflows at zero.

Warning

DFs and values after the curve’s final node date will return a value that is an extrapolation. This may not be a sensible or well constrained value depending upon the interpolation method.

In [4]: curve[dt(2022, 9, 26)]
Out[4]: 0.9926477364206718

In [5]: curve[dt(1999, 12, 31)]  # <- before the curve initial node date
Out[5]: 0.0

In [6]: curve[dt(2032, 1, 1)]  # <- extrapolated after the curve final node date
Out[6]: 0.8975214680350941
In [7]: linecurve[dt(2022, 9, 26)]
Out[7]: 1.0667808219178083

In [8]: linecurve[dt(1999, 12, 31)]  # <- before the curve initial node date
Out[8]: 0.0

In [9]: linecurve[dt(2032, 1, 1)]  # <- extrapolated after the curve final node date
Out[9]: 1.03

Visualization#

Visualization methods, of rates, are also available via _BaseCurve.plot(). This allows the easy inspection of curves directly. Below we demonstrate a plot highlighting the differences between our parametrised Curve and LineCurve.

In [10]: curve.plot(
   ....:     "1D",
   ....:     comparators=[linecurve],
   ....:     labels=["Curve", "LineCurve"]
   ....: )
   ....: 
Out[10]: 
(<Figure size 640x480 with 1 Axes>,
 <Axes: >,
 [<matplotlib.lines.Line2D at 0x1201e6710>,
  <matplotlib.lines.Line2D at 0x1201e6850>])

(Source code, png, hires.png, pdf)

_images/c_curves-1.png

Interpolation#

Rateslib treats curve interpolation in two ways;

  • it allows a _CurveSpline with defined knot sequence for interpolating nodes with a cubic PPSpline.

  • it allows local interpolation which uses some function to derive a result from only the immediately neighbouring nodes to the input date.

If a spline is specified and date falls between its knots it will take precedence. Otherwise, if the date falls outside of the knots or if a spline is not specified then local interpolation functions are used.

The available local interpolation options are described in the documentation for each curve class, and also in supplementary materials, generally they allow the commonly used “linear”, “log_linear”, “flat_forward” varieties as well as others.

interpolation can also be specified as a user defined function, which allows more flexibility than just local interpolation if required. See class documentation for required argument signature.

In [11]: def linear_with_randomness(date, curve):
   ....:     from rateslib.curves.interpolation import index_left
   ....:     from random import random
   ....:     i = index_left(curve.nodes.keys, curve.nodes.n, date)
   ....:     x_1, x_2 = curve.nodes.keys[i], curve.nodes.keys[i + 1]
   ....:     y_1, y_2 = curve.nodes.values[i], curve.nodes.values[i + 1]
   ....:     return (random() -0.5) * 0.05 + y_1 + (y_2 - y_1) * (date - x_1) / (x_2 - x_1)
   ....: 

In [12]: random_lc = LineCurve(
   ....:     nodes={
   ....:         dt(2022,1,1): 0.975,  # <- initial value is general
   ....:         dt(2023,1,1): 1.10,
   ....:         dt(2024,1,1): 1.22,
   ....:         dt(2025,1,1): 1.14,
   ....:         dt(2026,1,1): 1.03,
   ....:         dt(2027,1,1): 1.03,
   ....:     },
   ....:     interpolation=linear_with_randomness,
   ....: )
   ....: 

In [13]: random_lc.plot("1D", comparators=[linecurve], labels=["Random", "LineCurve"])
Out[13]: 
(<Figure size 640x480 with 1 Axes>,
 <Axes: >,
 [<matplotlib.lines.Line2D at 0x120593ed0>,
  <matplotlib.lines.Line2D at 0x1205b4050>])

(Source code, png, hires.png, pdf)

_images/c_curves-2_00_00.png

Spline Interpolation#

Splines can be automatically created by adding interpolation="spline" to the initialization of a curve. This will define a default knot sequence that encompasses the whole of the nodes domain. DF based curves’ splines will interpolate over the logarithm of DFs, whilst values based curves’ splines interpolate directly over those values.

Greater customisation is achieved by directly supplying the knot sequence as the t argument to a curve initialization. This is a list of datetimes and follows the appropriate mathematical convention for such sequences (see pp splines).

Mixed Interpolation#

Prior to the initial knot in the sequence the local interpolation method is used. This allows curves to be constructed with a mixed interpolation in two parts of the curve. This is common practice for interest rate curves usually with a log-linear short end and a log-cubic spline longer end.

In [14]: mixed_curve = Curve(
   ....:     nodes={
   ....:         dt(2022,1,1): 1.0,
   ....:         dt(2023,1,1): 0.99,
   ....:         dt(2024,1,1): 0.979,
   ....:         dt(2025,1,1): 0.967,
   ....:         dt(2026,1,1): 0.956,
   ....:         dt(2027,1,1): 0.946,
   ....:     },
   ....:     interpolation="log_linear",
   ....:     t = [dt(2024,1,1), dt(2024,1,1), dt(2024,1,1), dt(2024,1,1),
   ....:          dt(2025,1,1),
   ....:          dt(2026,1,1),
   ....:          dt(2027,1,1), dt(2027,1,1), dt(2027,1,1), dt(2027,1,1)]
   ....: )
   ....: 

In [15]: curve.plot("1D", comparators=[mixed_curve], labels=["log-linear", "log-cubic-mix"])
Out[15]: 
(<Figure size 640x480 with 1 Axes>,
 <Axes: >,
 [<matplotlib.lines.Line2D at 0x1207a4410>,
  <matplotlib.lines.Line2D at 0x1207a4550>])

(Source code, png, hires.png, pdf)

_images/c_curves-3.png

IBOR or RFR#

The different Instruments in rateslib may require different interest rate index types, be it IBOR or RFR based. These are fundamentally different and require care dependent on which curve type: Curve or LineCurve is used. This is also similar to fixing input for FloatPeriod (see here).

Curve Type

RFR Based

IBOR Based

Curve

DFs are value date based. For an RFR rate applicable between a start and end date, the start and end date DFs will reflect this rate, regardless of the publication timeframe of the rate.

DFs are value date based. For an IBOR rate applicable between a start and end date, the start and end date DFs will reflect this rate, regardless of the publication timeframe of the rate.

LineCurve

Rates are labelled by reference value date, not publication date.

Rates are labelled by publication date, not reference value date.

Since DF based curves behave similarly for each index type we will give an example of constructing an IRS under the different methods.

For an RFR curve the nodes values are by reference date. The 3.0% value which is applicable between the reference date of 2nd Jan ‘22 and end date 3rd Jan ‘22, is indexed according to the 2nd Jan ‘22.

In [16]: rfr_curve = LineCurve(
   ....:     nodes={
   ....:         dt(2022, 1, 1): 2.0,
   ....:         dt(2022, 1, 2): 3.0,
   ....:         dt(2022, 1, 3): 4.0
   ....:     }
   ....: )
   ....: 

In [17]: irs = IRS(
   ....:     dt(2022, 1, 2),
   ....:     "1d",
   ....:     "A",
   ....:     leg2_fixing_method="rfr_payment_delay"
   ....: )
   ....: 

In [18]: irs.rate(rfr_curve)
Out[18]: np.float64(3.0000000000036664)

For an IBOR curve the nodes values are by publication date. The curve below has a lag of 2 business days. and the publication on 1st Jan ‘22 is applicable to the reference value date of 3rd Jan.

In [19]: ibor_curve = LineCurve(
   ....:     nodes={
   ....:         dt(2022, 1, 1): 2.5,
   ....:         dt(2022, 1, 2): 3.5,
   ....:         dt(2022, 1, 3): 4.5
   ....:     }
   ....: )
   ....: 

In [20]: irs = IRS(
   ....:     dt(2022, 1, 3),
   ....:     "3m",
   ....:     "A",
   ....:     leg2_fixing_method="ibor",
   ....:     leg2_method_param=2
   ....: )
   ....: 

In [21]: irs.rate(ibor_curve)
Out[21]: 2.5

Mutable Pricing Objects#

The only curves with parameters that are mutated and solved by a Solver are Curve and LineCurve. These are classed as Pricing Objects.

These curves inherit the _WithMutability mixin.

Pricing Containers#

Other objects that are available, that are constructed via manipulations of the base Pricing Objects (or other Pricing Containers) are the so called Pricing Containers.

The main user curve classes are listed below:

rateslib.curves.CompositeCurve(curves[, id, ...])

A dynamic composition of a sequence of other _BaseCurve.

rateslib.curves.MultiCsaCurve(curves[, id])

A dynamic composition of a sequence of other _BaseCurve.

rateslib.curves.CreditImpliedCurve([...])

Imply a _BaseCurve from credit components.

rateslib.curves.ProxyCurve(cashflow, ...[, id])

A _BaseCurve which returns dynamic DFs from an FXForwards object and FX parity.

These objects allow complex curve features and scenarios to be modelled in a recognisable and easily parametrised format.

The following Pricing Containers are also created as the result of certain operations, which any _BaseCurve can inherit using the _WithOperations mixin.

rateslib.curves.ShiftedCurve(curve, shift[, id])

Create a new _BaseCurve type by compositing an input with another flat curve of a set number of basis points.

rateslib.curves.RolledCurve(curve, roll_days)

Create a new _BaseCurve type by translating the rate space of an input curve horizontally in time.

rateslib.curves.TranslatedCurve(curve, start)

Create a new _BaseCurve type by maintaining the rate space of an input curve but shifting the initial node date forwards in time.