FloatPeriod#

class rateslib.periods.FloatPeriod(*args, float_spread=NoInput.blank, fixings=NoInput.blank, fixing_method=NoInput.blank, method_param=NoInput.blank, spread_compound_method=NoInput.blank, **kwargs)#

Bases: BasePeriod

Create a period defined with a floating rate index.

Parameters:
  • args (dict) – Required positional args to BasePeriod.

  • float_spread (float or None, optional) – The float spread applied to determine the cashflow. Can be set to None and set later, typically after a mid-market float spread for all periods has been calculated. Expressed in basis points (bps).

  • spread_compound_method (str, optional) – The method to use for adding a floating rate to compounded rates. Available options are {“none_simple”, “isda_compounding”, “isda_flat_compounding”}.

  • fixings (float, list, or Series, optional) – If a float scalar, will be applied as the determined fixing for the whole period. If a list of n fixings will be used as the first n RFR fixings in the period and the remaining fixings will be forecast from the curve and composed into the overall rate. If a datetime indexed Series will use the fixings that are available in that object, and derive the rest from the curve. Must be input excluding float_spread.

  • fixing_method (str, optional) – The method by which floating rates are determined, set by default. See notes.

  • method_param (int, optional) – A parameter that is used for the various fixing_method s. See notes.

  • kwargs (dict) – Required keyword arguments to BasePeriod.

Notes

The cashflow is defined as follows;

\[C = -Ndr(r_i, z)\]

The npv() is defined as;

\[P = Cv(m) = -Ndr(r_i, z)v(m)\]

The analytic_delta() is defined as;

\[A = - \frac{\partial P}{\partial z} = Ndv(m) \frac{\partial r}{\partial z}\]

Fixing Methods

Floating period rates depend on different fixing_method to determine their rates. For further info see ISDA RFR Compounding Memo (2006). The available options provided here are:

  • “rfr_payment_delay”: this is the standard convention adopted by interbank RFR derivative trades, such as SOFR, SONIA, and ESTR OIS etc. method_param is not used for this method and defaults to zero, payment_lag serves as the appropriate parameter for this method.

  • “rfr_observation_shift”: typical conventions of FRNs. The method_param is the integer number of business days by which both the observation rates and the DCFs are shifted.

  • “rfr_lockout”: this is a convention typically used on floating rate notes (FRNs), the method_param as integer number of business days is the number of locked-out days. E.g. SOFR based FRNs generally have 4.

  • “rfr_lookback”: this is also a convention typically used on FRNs. The method_param as integer number of business days defines the observation offset, the DCFs remain static, measured between the start and end dates.

  • “rfr_payment_delay_avg”, “rfr_observation_shift_avg”, “rfr_lockout_avg”, “rfr_lookback_avg”: these are the same as the previous conventions except that the period rate is defined as the arithmetic average of the individual fixings, weighted by the relevant DCF depending upon the method.

  • “ibor”: this the convention for determining IBOR rates from a curve. The method_param is the number of fixing lag days before the accrual start when the fixing is published. For example, Euribor or Stibor have 2.

The first two are the only methods recommended by Alternative Reference Rates Comittee (AARC), although other methods have been implemented in financial instruments previously.

Spread Compounding Methods

The spread compounding methods operate as follows:

  • “none_simple”: the float spread added in a simple way to the determined compounded period rate.

  • “isda_compounding”: the float spread is added to each individual rate and then everything is compounded.

  • “isda_flat_compounding”: the spread is added to each rate but is not used when compounding each previously calculated component.

The first is the most efficient and most encountered. The second and third are rarely encountered in modern financial instruments. For further info see ISDA Compounding Memo (2009).

Fixings

Warning

Providing fixings as a Series is best practice.

But, RFR and IBOR fixings provided as datetime indexed Series require different formats:

  • IBOR fixings are indexed by publication date and fixing value.

  • RFR fixings are indexed by reference value date and fixing value.

If an “ibor” fixing method is given the series should index the published IBOR rates by publication date, which usually lags the reference value dates. For example, EURIBOR lags its value dates by two business days. 3M EURIBOR was published on Thu-2-Mar-2023 as 2.801%, which is applicable to the start date of Mon-6-Mar-2023 with value end date of Tue-6-Jun-2023.

In [1]: ibor_curve = Curve(
   ...:     nodes={dt(2023, 3, 6): 1.0, dt(2024, 3, 6): 0.96},
   ...:     calendar="bus"
   ...: )
   ...: 

In [2]: fixings = Series(
   ...:     [1.00, 2.801, 1.00, 1.00],
   ...:     index=[dt(2023, 3, 1), dt(2023, 3, 2), dt(2023, 3, 3), dt(2023, 3, 6)]
   ...: )
   ...: 

In [3]: float_period = FloatPeriod(
   ...:     start=dt(2023, 3, 6),
   ...:     end=dt(2023, 6, 6),
   ...:     payment=dt(2023, 6, 6),
   ...:     frequency="Q",
   ...:     fixing_method="ibor",
   ...:     method_param=2,
   ...:     fixings=fixings
   ...: )
   ...: 

In [4]: float_period.rate(ibor_curve)  # this will return the fixing published 2-Mar-23
Out[4]: 2.801

In [5]: float_period.fixings_table(ibor_curve)
Out[5]: 
              notional   dcf  rates
obs_dates                          
2023-03-02 -1000000.00  None   2.80

RFR rates tend to be maintained by central banks. The modern tendency seems to be to present historical RFR data indexed by reference value date and not publication date, which is usually 1 business day in arrears. If the fixing_method is “rfr” based then the given series should be indexed in a similar manner. ESTR was published as 2.399% on Fri-3-Mar-2023 for the reference value start date of Thu-2-Mar-2023 (and end date of Fri-3-Mar-2023).

In [6]: rfr_curve = Curve(
   ...:     nodes={dt(2023, 3, 3): 1.0, dt(2024, 3, 3): 0.96},
   ...:     calendar="bus"
   ...: )
   ...: 

In [7]: fixings = Series(
   ...:     [1.00, 1.00, 2.399],
   ...:     index=[dt(2023, 2, 28), dt(2023, 3, 1), dt(2023, 3, 2)]
   ...: )
   ...: 

In [8]: float_period = FloatPeriod(
   ...:     start=dt(2023, 3, 2),
   ...:     end=dt(2023, 3, 3),
   ...:     payment=dt(2023, 3, 3),
   ...:     frequency="A",
   ...:     fixing_method="rfr_payment_delay",
   ...:     fixings=fixings
   ...: )
   ...: 

In [9]: float_period.rate(rfr_curve)  # this will return the fixing for reference 2-Mar-23
Out[9]: 2.3989999999969314

In [10]: float_period.fixings_table(rfr_curve)
Out[10]: 
            notional  dcf  rates
obs_dates                       
2023-03-02      0.00 0.00   2.40

Examples

Create a stepped (log-linear interpolated) curve with 2 relevant steps:

In [11]: curve = Curve(nodes={dt(2022, 1, 1): 1.0, dt(2022, 2, 1): 0.999, dt(2022, 3, 1): 0.997})

In [12]: curve.rate(dt(2022, 1, 1), "1D")
Out[12]: 1.1618901045684638

In [13]: curve.rate(dt(2022, 2, 1), "1D")
Out[13]: 2.5766748046498478

A standard “rfr_payment_delay” period, which is the default for this library.

In [14]: period = FloatPeriod(
   ....:     start=dt(2022, 1, 1),
   ....:     end=dt(2022, 3, 1),
   ....:     payment=dt(2022, 3, 1),
   ....:     frequency="M",
   ....: )
   ....: 

In [15]: period.fixing_method
Out[15]: 'rfr_payment_delay'

In [16]: period.rate(curve)
Out[16]: 1.8360165241487176

An “rfr_lockout” period, here with 28 business days lockout (under a curve with a no holidays) meaning the observation period ends on the 1st Feb 2022 and the 1D rate between 31st Jan 2022 and 1st Feb is used consistently as the fixing for the remaining fixing dates.

In [17]: period = FloatPeriod(
   ....:     start=dt(2022, 1, 1),
   ....:     end=dt(2022, 3, 1),
   ....:     payment=dt(2022, 3, 1),
   ....:     frequency="M",
   ....:     fixing_method="rfr_lockout",
   ....:     method_param=28,
   ....: )
   ....: 

In [18]: period.rate(curve)
Out[18]: 1.162978262569729

An “rfr_lookback” period, here with 5 days offset meaning the observation period starts on 27th Jan and ends on 6th Feb.

In [19]: period = FloatPeriod(
   ....:     start=dt(2022, 2, 1),
   ....:     end=dt(2022, 2, 11),
   ....:     payment=dt(2022, 2, 11),
   ....:     frequency="M",
   ....:     fixing_method="rfr_lookback",
   ....:     method_param=5,
   ....: )
   ....: 

In [20]: period.rate(curve)
Out[20]: 1.8697123392650281

An “ibor” period, with a 2 day fixing lag

In [21]: period = FloatPeriod(
   ....:     start=dt(2022, 1, 3),
   ....:     end=dt(2022, 3, 3),
   ....:     payment=dt(2022, 3, 3),
   ....:     frequency="B",
   ....:     fixing_method="ibor",
   ....:     method_param=2,
   ....: )
   ....: 

In [22]: period.rate(curve)
Out[22]: 1.8841190295698882

In [23]: curve.rate(dt(2022, 1, 3), "2M")
Out[23]: 1.8841190295698882

Methods Summary

analytic_delta([curve, disc_curve, fx, base])

Return the analytic delta of the FloatPeriod.

cashflow(curve)

cashflows([curve, disc_curve, fx, base])

Return the cashflows of the FloatPeriod.

fixings_table(curve[, approximate, disc_curve])

Return a DataFrame of fixing exposures.

npv([curve, disc_curve, fx, base, local])

Return the NPV of the FloatPeriod.

rate(curve)

Calculating the floating rate for the period.

Methods Documentation

analytic_delta(curve=NoInput.blank, disc_curve=NoInput.blank, fx=NoInput.blank, base=NoInput.blank)#

Return the analytic delta of the FloatPeriod. See BasePeriod.analytic_delta()

cashflow(curve)#
cashflows(curve=NoInput.blank, disc_curve=NoInput.blank, fx=NoInput.blank, base=NoInput.blank)#

Return the cashflows of the FloatPeriod. See BasePeriod.cashflows()

fixings_table(curve, approximate=False, disc_curve=NoInput.blank)#

Return a DataFrame of fixing exposures.

Parameters:
  • curve (Curve, LineCurve, IndexCurve dict of such) – The forecast needed to calculate rates which affect compounding and dependent notional exposure.

  • approximate (bool, optional) – Perform a calculation that is broadly 10x faster but potentially loses precision upto 0.1%.

  • disc_curve (Curve) – A curve to make appropriate DF scalings. If None and curve contains DFs that will be used instead, otherwise errors are raised.

Return type:

DataFrame

Notes

IBOR and RFR fixing_method have different representations under this method.

For “ibor” based floating rates the fixing exposures are indexed by publication date and not by reference value date. IBOR fixings tend to occur either in advance, or the same day.

For “rfr” based floating rates the fixing exposures are indexed by the reference value date and not by publication date. RFR fixings tend to publish in arrears, usually at 9am the following business day. Central banks tend to publish data aligning the fixing rate with the reference value date and not by the publication date which is why this format is chosen. It also has practical application when constructing curves.

Examples

In [1]: rfr_curve = Curve(
   ...:     nodes={dt(2022, 1, 1): 1.00, dt(2022, 1, 13): 0.9995},
   ...:     calendar="bus"
   ...: )
   ...: 

A regular rfr_payment_delay period.

In [2]: constants = {
   ...:     "start": dt(2022, 1, 5),
   ...:     "end": dt(2022, 1, 11),
   ...:     "payment": dt(2022, 1, 11),
   ...:     "frequency": "Q",
   ...:     "notional": -1000000,
   ...:     "currency": "gbp",
   ...: }
   ...: 

In [3]: period = FloatPeriod(**{
   ...:     **constants,
   ...:     "fixing_method": "rfr_payment_delay"
   ...: })
   ...: 

In [4]: period.fixings_table(rfr_curve)
Out[4]: 
             notional  dcf  rates
obs_dates                        
2022-01-05 1000000.00 0.00   1.50
2022-01-06 1000041.68 0.00   1.50
2022-01-07 1000083.36 0.01   1.50
2022-01-10 1000208.41 0.00   1.50

A 2 business day rfr_observation_shift period. Notice how the above had 4 fixings spanning 6 calendar days, but the observation shift here attributes 4 fixings spanning 4 calendar days so the notional exposure to those dates is increased by effectively 6/4.

In [5]: period = FloatPeriod(**{
   ...:     **constants,
   ...:     "fixing_method": "rfr_observation_shift",
   ...:     "method_param": 2,
   ...:  })
   ...: 

In [6]: period.fixings_table(rfr_curve)
Out[6]: 
             notional  dcf  rates
obs_dates                        
2022-01-03 1499749.96 0.00   1.50
2022-01-04 1499812.46 0.00   1.50
2022-01-05 1499874.97 0.00   1.50
2022-01-06 1499937.49 0.00   1.50

A 2 business day rfr_lookback period. Notice how the lookback period adjusts the weightings on the 6th January fixing by 3, and thus increases the notional exposure.

In [7]: period = FloatPeriod(**{
   ...:     **constants,
   ...:     "fixing_method": "rfr_lookback",
   ...:     "method_param": 2,
   ...:  })
   ...: 

In [8]: period.fixings_table(rfr_curve)
Out[8]: 
             notional  dcf  rates
obs_dates                        
2022-01-03  999916.64 0.00   1.50
2022-01-04  999958.32 0.00   1.50
2022-01-05 2999749.95 0.01   1.50
2022-01-06 1000041.67 0.00   1.50

A 2 business day rfr_lockout period. Notice how the exposure to the final fixing which then spans multiple days is increased.

In [9]: period = FloatPeriod(**{
   ...:     **constants,
   ...:     "fixing_method": "rfr_lockout",
   ...:     "method_param": 2,
   ...:  })
   ...: 

In [10]: period.fixings_table(rfr_curve)
Out[10]: 
             notional  dcf  rates
obs_dates                        
2022-01-05  999999.99 0.00   1.50
2022-01-06 4999958.32 0.00   1.50
2022-01-07       0.00 0.01   1.50
2022-01-10       0.00 0.00   1.50

An IBOR fixing table

In [11]: ibor_curve = Curve(
   ....:     nodes={dt(2022, 1, 1): 1.00, dt(2023, 1, 1): 0.99},
   ....:     calendar="bus",
   ....: )
   ....: period = FloatPeriod(**{
   ....:     **constants,
   ....:     "fixing_method": "ibor",
   ....:     "method_param": 2,
   ....:  })
   ....: period.fixings_table(ibor_curve)
   ....: 
Out[11]: 
            notional   dcf  rates
obs_dates                        
2022-01-03   1000000  None   0.99
npv(curve=NoInput.blank, disc_curve=NoInput.blank, fx=NoInput.blank, base=NoInput.blank, local=False)#

Return the NPV of the FloatPeriod. See BasePeriod.npv()

rate(curve)#

Calculating the floating rate for the period.

Parameters:

curve (Curve, LineCurve, IndexCurve, dict of curves) – The forecasting curve object.

Return type:

float, Dual, Dual2

Examples

Using a single forecast Curve.

In [1]: period.rate(curve)
Out[1]: 2.157969412869587

Using a dict of Curves for stub periods calculable under an “ibor” fixing_method.

In [2]: period.rate({"1m": curve, "3m": curve, "6m": curve, "12m": curve})
Out[2]: 2.157969412869587