Curves#
The rateslib.curves
module allows the fundamental Curve
or
LineCurve
class
to be defined with parameters.
These curve objects are slightly different in what they
represent and how they operate.
This module relies on the ultility modules splines and dual.

|
Curve based on DF parametrisation at given node dates with interpolation. |
|
Curve based on value parametrisation at given node dates with interpolation. |
|
A dynamic composition of a sequence of other curves. |
|
A subclass of |
|
A dynamic composition of a sequence of other curves. |
|
Return the interval index of a value from an ordered input list on the left side. |
|
Determine an index value from a reference date using combinations of known fixings and forecast from a Curve. |
Each fundamental curve type has rate()
, plot()
, shift()
, roll()
,
translate()
, update
and update_node
methods. A Curve
with included index_base
and
index_lag
can also calculate future index_value()
and plot_index()
.
|
Calculate the rate on the Curve using DFs. |
|
Plot given forward tenor rates from the curve. |
|
Create a new curve by vertically adjusting the curve by a set number of basis points. |
|
Create a new curve with its shape translated in time but an identical initial node date. |
|
Create a new curve with an initial node date moved forward keeping all else constant. |
|
Update a curves nodes with new, manually input values. |
|
Update a single node value on the Curve. |
|
Return the curve value for a given date. |
|
Plot given forward tenor rates from the curve. |
|
Create a new curve by vertically adjusting the curve by a set number of basis points. |
Create a new curve with its shape translated in time |
|
|
Create a new curve with an initial node date moved forward keeping all else constant. |
|
Update a curves nodes with new, manually input values. |
|
Update a single node value on the Curve. |
|
Calculate the accrued value of the index from the |
|
Plot given index values on a curve. |
The main parameter that must be supplied to either type of curve is its nodes
. This
provides the curve with its degrees of freedom and represents a dict indexed by
datetimes, each with a given value. In the case of a Curve
these values are discount factors (DFs) or, for credit purposes, survival probabilities,
and in the case of a LineCurve
these are specific values, usually rates associated with that curve.
Curve#
A Curve
can only be used for interest rates (or credit
hazard rates). It is a more specialised
object because of the way it is defined by discount factors (DFs) (or survival probabilities).
These DFs maintain an inherent interpolation technique, which is often log-linear or log-cubic
spline. These are generally the most efficient
type of curve, and most easily parametrised, when working with compounded RFR rates.
The initial node on a Curve
should always have value 1.0,
and it will not
be varied by a Solver
. Curve
s must
be used with
FXForwards
since FX forwards calculation rely on the existence
of DFs.
LineCurves#
A LineCurve
is a more general object which can be
used to represent other forms of datetime indexed values. The values maintain
interpolation
techniques where the most common are likely to be linear and splines. These are
generally quite inefficient, and more difficult to parametrise, when dealing with RFR
rates, but may be superior when dealing with legacy IBOR rates or inflation etc.
The initial node on a LineCurve
can take any value and it will
be varied by a Solver
.
Introduction#
To create a simple curve, with localised interpolation, minimal configuration is 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 index vales, such as inflation forecasts, 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#
Curves
have a get item method so that DFs from a Curve
or values from a LineCurve
can easily be extracted
under the curve’s specified interpolation scheme.
Note
Curve
DFs (and
LineCurve
values), before the curve’s initial node
date return
zero, in order to value historical cashflows at zero.
Warning
Curve
DFs, and
LineCurve
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.
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 are also available via
Curve.plot()
and
LineCurve.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 0x119f34910>,
<matplotlib.lines.Line2D at 0x119f34410>])
(Source code
, png
, hires.png
, pdf
)

Interpolation#
The available basic local interpolation options are:
“linear”: this is most suitable, and the default, for
LineCurve
. Linear interpolation for DF based curves usually produces spurious underlying curves.“log_linear”: this is most suitable, and the default, for
Curve
. It produces overnight rates that are constant betweennodes
. This is not usually suitable forLineCurve
.“linear_zero_rate”: this is a legacy option for linearly interpolating continuously compounded zero rates, and is only suitable for
Curve
, but it is not recommended and tends also to produce spurious underlying curves.“flat_forward”: this is only suitable for
LineCurve
, and it maintains the previous value betweennodes
. It will produce a stepped curve similar to aCurve
with “log_linear” interpolation.“flat_backward”: same as above but in reverse.
In [11]: linecurve.interpolation = "flat_forward"
In [12]: curve.plot("1D", comparators=[linecurve], labels=["Curve", "LineCurve"])
Out[12]:
(<Figure size 640x480 with 1 Axes>,
<Axes: >,
[<matplotlib.lines.Line2D at 0x11a0bc550>,
<matplotlib.lines.Line2D at 0x11a0bc690>])
(Source code
, png
, hires.png
, pdf
)

interpolation
can also be specified as a user defined function. It must
have the argument signature (date, nodes) where nodes
are passed internally as
those copied from the curve.
In [13]: from rateslib.curves import index_left
In [14]: def flat_backward(x, nodes):
....: """Project the rightmost node value as opposed to leftmost."""
....: node_dates = [key for key in nodes.keys()]
....: if x < node_dates[0]:
....: return 0 # then date is in the past and DF is zero
....: l_index = index_left(node_dates, len(node_dates), x)
....: return nodes[node_dates[l_index + 1]]
....:
In [15]: linecurve.interpolation = flat_backward
In [16]: curve.plot("1D", comparators=[linecurve], labels=["Curve", "LineCurve"])
Out[16]:
(<Figure size 640x480 with 1 Axes>,
<Axes: >,
[<matplotlib.lines.Line2D at 0x11a2b1f90>,
<matplotlib.lines.Line2D at 0x11a2b20d0>])
(Source code
, png
, hires.png
, pdf
)

Spline Interpolation#
There is also an option to interpolate with a cubic polynomial spline.
If applying spline interpolation to a Curve
then it is
applied logarithmically resulting in a log-cubic spline over DFs.
If it is applied to a LineCurve
then it results in a
standard cubic spline over values.
In order to instruct this mode of interpolation a knot sequence is required
as the t
argument. 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 [17]: 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 [18]: curve.plot("1D", comparators=[mixed_curve], labels=["log-linear", "log-cubic-mix"])
Out[18]:
(<Figure size 640x480 with 1 Axes>,
<Axes: >,
[<matplotlib.lines.Line2D at 0x11a477890>,
<matplotlib.lines.Line2D at 0x11a4779d0>])
(Source code
, png
, hires.png
, pdf
)

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 |
---|---|---|
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. |
|
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 [19]: rfr_curve = LineCurve(
....: nodes={
....: dt(2022, 1, 1): 2.0,
....: dt(2022, 1, 2): 3.0,
....: dt(2022, 1, 3): 4.0
....: }
....: )
....:
In [20]: irs = IRS(
....: dt(2022, 1, 2),
....: "1d",
....: "A",
....: leg2_fixing_method="rfr_payment_delay"
....: )
....:
In [21]: irs.rate(rfr_curve)
Out[21]: 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 [22]: ibor_curve = LineCurve(
....: nodes={
....: dt(2022, 1, 1): 2.5,
....: dt(2022, 1, 2): 3.5,
....: dt(2022, 1, 3): 4.5
....: }
....: )
....:
In [23]: irs = IRS(
....: dt(2022, 1, 3),
....: "3m",
....: "A",
....: leg2_fixing_method="ibor",
....: leg2_method_param=2
....: )
....:
In [24]: irs.rate(ibor_curve)
Out[24]: 2.5