attack68 / rateslib

A fixed income library for pricing bonds and bond futures, and derivatives such as IRS, cross-currency and FX swaps. Contains tools for full Curveset construction with market standard optimisers and automatic differention (AD) and risk sensitivity calculations including delta and cross-gamma.
https://rateslib.readthedocs.io/en/latest/
Other
146 stars 27 forks source link

FloatRateNote is failing to calculate cashflows #149

Open lcohan opened 6 months ago

lcohan commented 6 months ago

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 commented 6 months ago

cc @attack68

attack68 commented 6 months ago

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

lcohan commented 6 months ago

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: @.***>

attack68 commented 6 months ago

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 commented 6 months ago

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

lcohan commented 6 months ago

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

attack68 commented 6 months ago

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 commented 2 months ago

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