QL-ORE ON coupons alignment
This PR tries to be an alignment for the difference of the overnight coupons and ON coupons pricers in QuantLib and ORE.
Changes to note for ORE (@pcaspers ):
- the
OvernightIndexedCouponPricercode is in theovernightindexedcouponpricer.hppfile not in theovernightindexedcoupon.hppfile anymore. rateCutoffin ORE islockoutDaysin QuantLibfixingDaysin ORE islookbackDaysin QuantLib- there is no
AverageONIndexedCouponin QuantLib the averaging method is passed as a argument in theOvernightIndexedCouponand it is an enum type:
struct RateAveraging {
enum Type {
Simple,
Compound
};
};
- The QL
OvernightIndexedCouponPricerspricing logic haven't been changes since I haven't noticed substantial differences (apart from the rateCutoff and fixingDays naming conventions). The only thing that I'm unsure of is the following line. I haven't seen such thing in QuantLib.
Changes to note in QL (@lballabio ):
- Added
CappedFlooredOvernightIndexedCouponclass underovernigthindexcoupon.hpp(imported from ORE) - Added
BlackOvernightIndexedCouponPricerandBlackAverageONIndexedCouponPricerfor pricing capped / floored compounded ON coupons (imported from ORE) - Added a bunch of other methods in the
OvernightLegclass (includeSpread,withCaps,withFloors,withNakedOption, ...) imported from ORE - Added
includeSpread,rateComputationStartDate,rateComputationEndDatearguments to theOvernightIndexedCouponconstructor (missing args from ORE)
coverage: 74.162% (+0.3%) from 73.873% when pulling e10dd4e8fe0576b4fa87bd965b899ab72be7505a on paolodelia99:feature/ql-ore-coupons-alignment into 7a6a9a221f871bd1aef443a94fe4cb1ac6f44af2 on lballabio:master.
@lballabio I actually need help with the test testOvernightLegWithCapsAndFloors in overnightindexedcoupon.hpp. Don't know why I'm getting different results on different builds, at first I thought that I was due to the usingAtParCoupons optional, but apparently I was wrong. Can you please take a look at it when you have time?
Not a lot of time now unfortunately, but I would try checking that we're not accessing some vector out of its bounds and getting garbage numbers.
I pushed a fix for the failing tests—a couple of bools were left uninitialized.
I still have to do a proper review...
Thank you @lballabio! I didn't have much time to debug the tests lately
Apologies for the long delay. One thing: currently the pricers are defined this way:
classDiagram
class FloatingRateCouponPricer
class CompoundingOvernightIndexedCouponPricer
class ArithmeticAveragedOvernightIndexedCouponPricer
class CappedFlooredOvernightIndexedCouponPricer
class BlackOvernightIndexedCouponPricer
class BlackAverageONIndexedCouponPricer
FloatingRateCouponPricer <|-- CompoundingOvernightIndexedCouponPricer
FloatingRateCouponPricer <|-- ArithmeticAveragedOvernightIndexedCouponPricer
FloatingRateCouponPricer <|-- CappedFlooredOvernightIndexedCouponPricer
CappedFlooredOvernightIndexedCouponPricer <|-- BlackOvernightIndexedCouponPricer
CappedFlooredOvernightIndexedCouponPricer <|-- BlackAverageONIndexedCouponPricer
Instead, I would try to turn it into
classDiagram
class FloatingRateCouponPricer
class CompoundingOvernightIndexedCouponPricer
class ArithmeticAveragedOvernightIndexedCouponPricer
class BlackOvernightIndexedCouponPricer
class BlackAverageONIndexedCouponPricer
FloatingRateCouponPricer <|-- CompoundingOvernightIndexedCouponPricer
FloatingRateCouponPricer <|-- ArithmeticAveragedOvernightIndexedCouponPricer
CompoundingOvernightIndexedCouponPricer <|-- BlackOvernightIndexedCouponPricer
ArithmeticAveragedOvernightIndexedCouponPricer <|-- BlackAverageONIndexedCouponPricer
or maybe, if we need to share something between compounded and averaged, something like
classDiagram
class FloatingRateCouponPricer
class OvernightIndexedCouponPricer
class CompoundingOvernightIndexedCouponPricer
class ArithmeticAveragedOvernightIndexedCouponPricer
class BlackOvernightIndexedCouponPricer
class BlackAverageONIndexedCouponPricer
FloatingRateCouponPricer <|-- OvernightIndexedCouponPricer
OvernightIndexedCouponPricer <|-- CompoundingOvernightIndexedCouponPricer
OvernightIndexedCouponPricer <|-- ArithmeticAveragedOvernightIndexedCouponPricer
CompoundingOvernightIndexedCouponPricer <|-- BlackOvernightIndexedCouponPricer
ArithmeticAveragedOvernightIndexedCouponPricer <|-- BlackAverageONIndexedCouponPricer
What do you think?
2nd and 3rd case make more sense to me. I would opt for the 3rd case. One question: is OvernightIndexedCouponPricer gonna be a virtual class that is just defining the interface for the child classes?
The interface is already declared in FloatingRateCouponPricer. The base class might perhaps store the volatility term structure, but otherwise there's not a lot in common, which is why the existing pricers don't have a common base.
Got it. So the OvernightIndexedCoupon pricer class should do the same thing as the IborCouponPricer class, where the volatility term structure is set by the setCapletVolatility method. In this way, we are going to have a consistent interface among those "base classes".
One problem that I have noticed @lballabio: If BlackOvernightIndexedCouponPricer is going to become a child of CompoundingOvernightIndexedCouponPricer there is going to be a clash in the type of coupon_ attribute, since the the former is a CappedFlooredOvernightIndexedCoupon and in the latter is OvernightIndexedCoupon (They are both child of FloatingRateCoupon). How would you suggest me to tackle this problem?
I think you can copy the approach in CappedFlooredIborCoupon. It inherits from CappedFlooredCoupon, not FloatingRateCoupon directly, and the setPricer method in the base class is overridden so that it sets the passed pricer to its underlying instead of the capped coupon itself. It should work in this case as well. Let me know if you need more details.
@pcaspers no hurry, just bumping this in case it fell to the bottom of your inbox and got lost, we had a few questions for you above. Thanks!
I will take a look - I have flagged it in my mailbox :-)
Thanks for doing all this. @paolodelia99 do you plan to reintegrate the updated ql coupon classes in ore, so that we can get rid of the QuantExt versions of these classes?
Do mean in the ORE's QL fork? @pcaspers But yeah, it shoudn't be that big of a problem to integrate those changes in ORE.
Do mean in the ORE's QL fork? @pcaspers But yeah, it shoudn't be that big of a problem to integrate those changes in ORE.
Ultimately yes. Maybe it is enough if you migrate the code to official ql such that we can merge this later into our ql fork and get rid of the QuantExt classes.
@lballabio don't know is it better to set lookbackDays = 0 in the OvernightIndexedCoupon ctor instead of Null<Natural>(), because If I had to use ORE's logic (ORE is using a Period instead of a Natural) in the conditional that I've just imported I would have a bug:
if (lookbackDays != 0) { //by default lookbackDays is 2147483647
BusinessDayConvention bdc = lookbackDays > 0 ? Preceding : Following;
valueStart = overnightIndex->fixingCalendar().advance(valueStart, -lookbackDays, Days, bdc); //error is throw in here: QuantLib::Error: Date's serial number (366) outside allowed range [367-109574]
valueEnd = overnightIndex->fixingCalendar().advance(valueEnd, -lookbackDays, Days, bdc);
}
Hi Paolo, I'm not sure I understand the question. Right now you have if (lookbackDays != Null<Natural>()) which is correct. Why should it become if (lookbackDays != 0)?
I was just wondering whether to set lookbackDays = Null<Natural>() was the correct approach instead of setting it to 0. Anyway the initial check is correct, so there is no need to change it. Just asking for consistency reasons since lockoutDays = 0 by the ctor, by default
I fixed the issues you guys pointed out. Whenever you have time to review it @lballabio @pcaspers
Thanks, I'm looking at it now so hopefully it goes into 1.41.
Hmm, there seems to be a problem that didn't surface until I tried to use just one pricer for both capped and non-capped coupons. If one sets a Black coupon pricer to an OvernightIndexedCoupon (not the capped one), this triggers an infinite recursion. BlackCompoundingOvernightIndexedCouponPricer::initialize calls coupon_->effectiveIndexFixing(), which calls back pricer.initialize(). I'll look into it a bit more. If you have any ideas, they're welcome.
You can leave it to me if you want, just give me a couple of days
Sure, thanks.
I figured out what the error is due to: in ORE the BlackCompoundingOvernightIndexedCouponPricer::initialize method is calling
effectiveIndexFixing_ = coupon_->underlying()->effectiveIndexFixing();
but since now that we also need to be able to price an OvernigthIndexedCoupon with that class we the initialize method is calling:
effectiveIndexFixing_ = coupon_->effectiveIndexFixing();
thus leading to that infinite recursion.
I think we should call the compute method defined in the CompoundingOvernightIndexedCouponPricer parent class, since the BlackCompoundingOvernightIndexedCouponPricer does calculate only the caplet/floorlet rate.
auto [swapletRate, effectiveSpread, effectiveIndexFixing] = compute(coupon_->accrualEndDate());
Sounds good - did you try that?
Yeah, it worked. This is the code that I've added:
auto [swapletRate, effectiveSpread, effectiveIndexFixing] = CompoundingOvernightIndexedCouponPricer::compute(coupon_->accrualEndDate());
swapletRate_ = swapletRate;
effectiveSpread_ = effectiveSpread;
effectiveIndexFixing_ = effectiveIndexFixing;
Are we sure that we want to trigger the CompoundingOvernightIndexedCouponPricer::compute in the initialize method in the BlackCompoundingOvernightIndexedCouponPricer?
makes sense to me, we need these results for the underlying coupon, so why not set them here
Thanks! Another thing turned up: I added a few more checks in the tests, and one of them fails because setting a Black arithmetic-averaging pricer to a vanilla coupon results in a rate = 0 being returned. I'm pushing it so you can have a look.