Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swap floating leg basis-point sensitivity includes coupons based on cashflow date - should it be fixing date? #1779

Open
tomwhoiscontrary opened this issue Aug 31, 2023 · 16 comments

Comments

@tomwhoiscontrary
Copy link
Contributor

If you have a VanillaSwap which started in the past, and you ask for the floatingLegBPS, then the value returned includes the effect of a rate shift on all the coupons which have yet to be paid, even if they have already fixed.

The same holds for overnight index swaps - coupons drop out atomically on the cashflow date, rather than diminishing in effect as they progressively fix.

That means you can't use this number to estimate change in NPV for a shift in the curve. Was that the intention? What are these numbers for?

I would like to be able to estimate change in NPV like this. Is there any scope for changing QuantLib's behaviour here? Perhaps simply changing it is out for backwards compatibility reasons. But this could be controlled via a boolean option in the same way as includeSettlementDateFlows / includeReferenceDateEvents?

@lballabio
Copy link
Owner

Hello Tom, I think the idea was not to use that figure to estimate changes for a shift but rather to calculate a spread to add to the coupon to make the swap fair. I'm not sure if the latter calculation makes a lot of sense in the case of seasoned swaps, though, so we might try and see if we can support both use cases without making the code too complex.

@tomwhoiscontrary
Copy link
Contributor Author

I am working on implementing it in my fork - i have raised a PR for an enabling change, and will perhaps raise more, and will show you what i end up with for this change.

@tomwhoiscontrary
Copy link
Contributor Author

I had a look at this, and i think there is a fairly simple change. In CashFlows::npvbps, we currently have:

                if (cp != nullptr)
                    bps += cp->nominal() * cp->accrualPeriod() * df;

We pull out the accrual period into a variable, and set it a bit more conditionally, based on the specific runtime type of the coupon. If it is an IborCoupon (dynamic_cast into icp), set it to icp->hasFixed() ? 0 : cp->accrualPeriod() (). If it is an OvernightIndexCoupon, set it to cp->accrualPeriod() - cp->accruedPeriod(settlementDate).

We could pull out a accrualPeriodRemaining() method on the coupon to make this more uniform.

@tomwhoiscontrary
Copy link
Contributor Author

Edits:

  • Should be be hasFixed(settlementDate).

  • Should be settlementDate > cp->accrualEndDate() ? 0 : cp->accrualPeriod() - cp->accruedPeriod(settlementDate), because accruedPeriod drops back to zero after accrual finishes.

There are probably more errors on the details here, but hopefully the gist of the idea should be clear.

Copy link
Contributor

This issue was automatically marked as stale because it has been open 60 days with no activity. Remove stale label or comment, or this will be closed in two weeks.

@github-actions github-actions bot added the stale label Nov 12, 2023
@tomwhoiscontrary
Copy link
Contributor Author

PR #1781 is related to this, and i would still like to work on it, as we are carrying a patch locally at the moment.

@github-actions github-actions bot removed the stale label Nov 13, 2023
Copy link
Contributor

This issue was automatically marked as stale because it has been open 60 days with no activity. Remove stale label or comment, or this will be closed in two weeks.

@tomwhoiscontrary
Copy link
Contributor Author

The npv, bps, and npvbps methods in Cashflows all take optional settlement date and NPV date parameters. What are those supposed to mean? I think i know, but i would like to confirm before i go on.

I understand that the settlement date is when a trade in the instrument settles, and that the NPV date is the date we are asking for the NPV as of.

So the evaluation date might be today, 14 June 2024. If we have a one-year IMM-rolls swap, it could start on 18 September 2024 and end on 17 September 2025. We might consider the situation where we trade it on the 31st of October 2024 (as a seasoned swap), and for some reason want to know what that would mean for the NPV as of the 1st of July 2024.

The current code excludes coupons if they have already been paid as of the settlement date. That makes sense, because those payments would not be exchanged if the swap was traded after their payment dates.

I'm trying to work out how these dates interact with BPS calculation. BPS is how much the NPV will change for a one basis point change in coupon rates. My rule of thumb is that BPS should match what happens if you bump the curve enough to produce a one basis point change in coupons, then calculate NPV again and measure the change in that. With a bumped curve:

  • Changing the evaluation date changes which coupons are affected by the curve shift, because we only forecast fixings after the evaluation date
  • Changing the NPV date changes the NPV of each coupon
  • Changing the settlement date changes which coupons are included in the NPV, and so the BPV

But changing the settlement date would not have any effect on which coupons used a historical fixing, and which used a forecast fixing.

So i think that in the cashflows functions, fixed coupons should be excluded on the basis of the evaluation date, not the settlement date. Paid coupons should still be excluded on the basis of the settlement date, of course.

Does this make sense?

@lballabio
Copy link
Owner

As for settlement date and npv date, they're probably more useful for things like bonds. The settlement date is the settlement date; passing it as the npv date as well gives you the price of the bond, passing today's date instead gives you the value as of today of holding the bond once you bought it.

This said: thinking about it, I'm getting the idea that what you need might be a separate method. BPS as implemented today is not what happens if you bump the curve enough to produce a one basis point change in coupons, then calculate NPV again and measure the change in that; because if you bump the curve, you also change the discounting (at least in OIS), and the BPS calculation doesn't do that. It's what happens if your fixed-rate coupons pay 1 bp more, or if your floating-rate coupons have an added spread of 1 bp, with the curve staying the same. That's not the sensitivity to movements of the curve(s).

The sensitivity to a bump in the curve has value, of course! But it's a different calculation.

@tomwhoiscontrary
Copy link
Contributor Author

What you say about the dates makes sense, so i will not write those up to the has-fixed logic.

I take your point about this method not really simulating a bump for OISs. We do indeed do that different calculation of bumping the curve. But this bump-the-coupons number is also useful, and it would be most useful if its treatment of fixings was similar to that of bumping.

@lballabio
Copy link
Owner

What if we provided some helper functions to get those numbers more easily from the existing functions? Something like

CashFlows::bps(forecast_cashflows(leg), discount_curve, ...);

where forecast_cashflows returns the vector of coupons from and not including the current?

@tomwhoiscontrary
Copy link
Contributor Author

At the moment, in my code we ultimately get the BPS by calling swap.floatingLegBPS(). For me, the ideal solution would change (IMHO, fix) the behaviour of that method. But it would be possible to do CashFlows::bps(forecast_cashflows(swap.floatingLeg), etc) - just awkward because i would have to get all the parameters together for each swap, when that is already done by QuantLib during pricing.

Would you prefer not to change the current behaviour?

@lballabio
Copy link
Owner

fairSpread relies on the current behavior.

@lballabio
Copy link
Owner

Also, for consistency, floatingLegBPS should work like fixedLegBPS. And for the latter, there's no difference between the current coupon and the future ones—they're all already fixed anyway.

@tomwhoiscontrary
Copy link
Contributor Author

Fair enough.

I would also like to get the "right" result for OISs. I'm not sure that can be done just by filtering the cashflows, because it needs to account for the BPS of the first cashflow gradually decreasing as it incrementally fixes. I suppose i could make a copy of the leg with the first cashflow modified in some way?

@lballabio
Copy link
Owner

The easier way is probably to use two different handles for OIS forecasting and OIS discounting so you can bump the one and not the other.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants