rateslib icon indicating copy to clipboard operation
rateslib copied to clipboard

FloatRateNote is failing to calculate cashflows

Open lcohan opened this issue 10 months ago • 8 comments

Hi! I've been trying to follow the example in the FloatingRateNote Guide, but there seems to be a bug in the definition of the fixing

I have not been able to calculate a working cashflow trying many combinations of the parameters

image

lcohan avatar Apr 23 '24 20:04 lcohan

cc @attack68

lcohan avatar Apr 24 '24 01:04 lcohan

Hi the example in the user guide can probably be improved, actually. The specific example you seem to have taken was designed to show the calculation of accrued when some fixings in a period are provided and some are not.

E.g.

from pandas import Series, date_range
fixings = Series(2.0, index=date_range(dt(1999, 12, 1), dt(2000, 6, 2)))

frn = FloatRateNote(
    effective=dt(1998, 12, 7),
    termination=dt(2015, 12, 7),
    frequency="S",
    currency="gbp",
    convention="Act365F",
    ex_div=3,
    fixings=fixings,
    fixing_method="rfr_observation_shift",
    method_param=5,
)
frn.accrued(dt(2000, 6, 4))
# 0.9911540636168727

If you try to calculate cashflows without a curve to forecast NPVs it will display the structure of the bond and display the values it can but it cant forecast floating rates and DF, and NPVs...

Screenshot 2024-04-24 at 07 21 18

If instead you build a Curve which extends to the end of the bond and starts where the fixings have ended and try to get cashflows:

curve = Curve(
    {dt(2000, 6, 3): 1.0,
     dt(2020, 6, 3): 0.5,},
    calendar=NoInput(0),
    convention="act365f",
)
frn.cashflows(curve)
# ValueError: RFRs could not be calculated, have you missed providing `fixings` 
# or does the `Curve` begin after the start of a `FloatPeriod` includingthe `method_param` adjustment?

This error is not the clearest but essentially what it is indicating is that rateslib is trying to calculate the rate for the periods before the fixings start, i.e. between Dec 1998 and Dec 1999, using a Curve that begins in Jun 2000. The solution is to provide all the fixing data for all of those prior Periods:

fixings = Series(2.0, index=date_range(dt(1997, 12, 1), dt(2000, 6, 2)))
frn = FloatRateNote(
    effective=dt(1998, 12, 7),
    termination=dt(2015, 12, 7),
    frequency="S",
    currency="gbp",
    convention="Act365F",
    ex_div=3,
    fixings=fixings,
    fixing_method="rfr_observation_shift",
    method_param=5,
)
frn.cashflows(curve)

Screenshot 2024-04-24 at 07 33 20

attack68 avatar Apr 24 '24 05:04 attack68

This may sound like a silly question but, why do I need a curve if I have a full list of fixing values, including those in the future?

On Wed, Apr 24, 2024, 02:34 JHM Darbyshire @.***> wrote:

Hi the example in the user guide can probably be improved, actually. The specific example you seem to have taken was designed to show the calculation of accrued when some fixings in a period are provided and some are not.

E.g.

from pandas import Series, date_rangefixings = Series(2.0, index=date_range(dt(1999, 12, 1), dt(2000, 6, 2))) frn = FloatRateNote( effective=dt(1998, 12, 7), termination=dt(2015, 12, 7), frequency="S", currency="gbp", convention="Act365F", ex_div=3, fixings=fixings, fixing_method="rfr_observation_shift", method_param=5, )frn.accrued(dt(2000, 6, 4))# 0.9911540636168727

If you try to calculate cashflows without a curve to forecast NPVs it will display the structure of the bond and display the values it can but it cant forecast floating rates and DF, and NPVs...

Screenshot.2024-04-24.at.07.21.18.png (view on web) https://github.com/attack68/rateslib/assets/24256554/107f9ef8-2b4f-4fa4-abcd-d69565c3d1d1

If instead you build a Curve which extends to the end of the bond and starts where the fixings have ended and try to get cashflows:

curve = Curve( {dt(2000, 6, 3): 1.0, dt(2020, 6, 3): 0.5,}, calendar=NoInput(0), convention="act365f", )frn.cashflows(curve)# ValueError: RFRs could not be calculated, have you missed providing fixings # or does the Curve begin after the start of a FloatPeriod includingthe method_param adjustment?

This error is not the clearest but essentially what it is indicating is that rateslib is trying to calculate the rate for the periods before the fixings start, i.e. between Dec 1998 and Dec 1999, using a Curve that begins in Jun 2000. The solution is to provide all the fixing data for all of those prior Periods:

fixings = Series(2.0, index=date_range(dt(1997, 12, 1), dt(2000, 6, 2))) frn = FloatRateNote( effective=dt(1998, 12, 7), termination=dt(2015, 12, 7), frequency="S", currency="gbp", convention="Act365F", ex_div=3, fixings=fixings, fixing_method="rfr_observation_shift", method_param=5, ) frn.cashflows(curve)

Screenshot.2024-04-24.at.07.33.20.png (view on web) https://github.com/attack68/rateslib/assets/24256554/b37fe677-fd78-4059-896c-f594cc2a5a2e

— Reply to this email directly, view it on GitHub https://github.com/attack68/rateslib/issues/149#issuecomment-2074066365, or unsubscribe https://github.com/notifications/unsubscribe-auth/AF7AZXD2OQ2XXZFU3QLDD5LY64745AVCNFSM6AAAAABGVUP66WVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANZUGA3DMMZWGU . You are receiving this because you authored the thread.Message ID: @.***>

lcohan avatar Apr 24 '24 10:04 lcohan

If you provide all the fixings for all the periods. It should work. Provide a reproducible example otherwise it is impossible to bug fix.

attack68 avatar Apr 24 '24 11:04 attack68

The discounting Curve is used as reference for discounting cashflows to the present day. The immediate date is measured as the first node on the curve. This displays all cashflows and values them at zero becuase they are historical relative to the Curve.

frn = FloatRateNote(
    effective=dt(2000, 12, 7),
    termination=dt(2002, 12, 7),
    frequency="S",
    currency="gbp",
    convention="Act365F",
    ex_div=3,
    fixings=[2.5, 3.5, 4.5, 6.5],
    fixing_method="ibor",
    method_param=5,
)
curve = Curve({dt(2022, 1, 1): 1.0})
frn.cashflows(curve)

image

attack68 avatar Apr 24 '24 11:04 attack68

just to give you an example

In the code above, if you expand fixings to 2020 you would have the full range covered, and no need for a curve

fixings = Series(2.0, index=date_range(dt(1997, 12, 1), dt(2020, 6, 2)))

but I am still getting Nones

image

lcohan avatar Apr 24 '24 11:04 lcohan

This is ultimately due to the evaluation of cashflows of a single FloatPeriod. The code contains:

        if curve is not NoInput.blank:
            cashflow = float(self.cashflow(curve))
            rate = float(100 * cashflow / (-self.notional * self.dcf))
            npv = float(self.npv(curve, disc_curve_))
            npv_fx = npv * float(fx)
        else:
            cashflow, rate, npv, npv_fx = None, None, None, None

Even if the rate can be known if there are enough fixings, it is still not generated. Maybe a design flaw - maybe not. In any case, if you want to trigger the evaluation of the rates and their display use a Curve with a reference date.

fixings = Series(2.0, index=date_range(dt(1997, 12, 1), dt(2020, 6, 2)))

frn = FloatRateNote(
    effective=dt(1998, 12, 7),
    termination=dt(2015, 12, 7),
    frequency="S",
    currency="gbp",
    convention="Act365F",
    ex_div=3,
    fixings=fixings,
    fixing_method="rfr_observation_shift",
    method_param=5,
)
frn.cashflows(Curve({dt(2020, 6, 3): 1.0}))

attack68 avatar Apr 24 '24 12:04 attack68

Maybe an idea to add a psuedo curve to generate results..

attack68 avatar Aug 08 '24 18:08 attack68